3

I use this code to load my html file with text in WKWebView:

do {
   guard let filePath = Bundle.main.path(forResource: "\(readBookNumber)", ofType: "html")
       else { 
           print ("File reading error")
           return
       }
   var content =  try String(contentsOfFile: filePath, encoding: .utf8)
   let baseUrl = URL(fileURLWithPath: filePath)
            
   content.changeHtmlStyle(font: "Iowan-Old-Style", fontSize:  UserDefaults.standard.integer(forKey: "textSize"), fontColor: textColor)
   webView.loadHTMLString(headerString+content, baseURL: baseUrl)
}
catch {
    print ("File HTML error")
}

and this code to load the page where the user stopped reading last time:

self.webView.scrollView.contentOffset.x = CGFloat(UserDefaults.standard.integer(forKey: "pageToLoad"))

I use code for loading last page in this method:

func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
    DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
         self.webView.scrollView.contentOffset.x = CGFloat(UserDefaults.standard.integer(forKey: "pageToLoad"))
    }
}

At first I used deadline: .now() + 0.1, but that didn't work. Because the last read page was loaded initially, and after a few seconds I see my text on the first page. I change it to deadline: .now() + 0.5 and the text loads fine from the last page read. Its was 700 pages. But now I want to load another text with 1700 pages. And I have same problem like first time. I can change deadline: .now() + 1.0 and my text will load fine. But I think this is not the best solution. I run it on my iPhone X. But maybe if I run it on iPad mini 2 I should change deadline: .now() + 10.0 because iPad mini 2 not very powerful. How to solve the problem?

Update based on @DPrice code:

If I use this code:

override func viewDidLoad() {
    super.viewDidLoad()
    webView.addObserver(self, forKeyPath: #keyPath(WKWebView.estimatedProgress), options: .new, context: nil)

....
}

override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
    if (keyPath == "estimatedProgress") {
        if webView.estimatedProgress == 1.0 {
            self.webView.scrollView.contentOffset.x = CGFloat(UserDefaults.standard.integer(forKey: "pageToLoad\(self.readBookNumber)"))
        }
    }
}

I have same bad result like in my code.

But if I use this code:

override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
    if (keyPath == "estimatedProgress") {
        if webView.estimatedProgress == 1.0 {
            DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
                self.webView.scrollView.contentOffset.x = CGFloat(UserDefaults.standard.integer(forKey: "pageToLoad\(self.readBookNumber)"))
            }
        }
    }
}

Everything works fine. And my last page loading fine. But it does not solve the problem in my question.

4
  • "I want to load another text with 1700 pages" ... that's a LOT of text to load into a web view, and as you've seen it can take a long time to render. If you're trying to create something like a "book" reader, you may need to think about another approach. Commented Nov 1, 2020 at 13:00
  • @DonMag Time for rendering is not very important to me. It is important that the amount of time is accurate. I just can't be sure that 5 seconds will work for all devices. Maybe some device will process the text longer and I'm wrong. Therefore, I would like the device to automatically choose the right amount of time to render. For example, for iPhone 12 1.0 seconds are enough, and for iPad mini 2 - 4.0 seconds. I just want it to be automatically detected in the device. Instead of guessing the right amount of time myself. Because it is always inaccurate and can lead to unwanted behavior. Commented Nov 1, 2020 at 13:37
  • Do you have any script running in the html you're loading? How are you "paginating" the "1700" pages? It could be helpful if you can provide an example html file. Commented Nov 1, 2020 at 14:05
  • @DonMag test project - gofile.io/d/4wzUhA see style.css lines overflow: -webkit-paged-x !important; direction: ltr !important; Commented Nov 1, 2020 at 14:07

3 Answers 3

1
+50

Here is a modified version of your ViewController class:

import UIKit
import WebKit

class ViewController: UIViewController, UIScrollViewDelegate, WKNavigationDelegate {
    
    @IBOutlet weak var webView: WKWebView!
    @IBOutlet weak var pagesLabel: UILabel!
    
    var readBookNumber = 0
    let headerString = "<meta name=\"viewport\" content=\"initial-scale=1.0\" />"
    var textSize = 3

    var contentSize: CGSize = .zero

    override func viewDidLoad() {
        super.viewDidLoad()
        
        // Web View Delegate
        
        webView.scrollView.delegate = self
        webView.navigationDelegate = self
        
        webView.scrollView.isPagingEnabled = true
        webView.scrollView.alwaysBounceVertical = false
        webView.scrollView.showsHorizontalScrollIndicator = true
        webView.scrollView.showsVerticalScrollIndicator = false
        webView.scrollView.panGestureRecognizer.isEnabled = false
        webView.scrollView.pinchGestureRecognizer?.isEnabled = false
        webView.scrollView.bouncesZoom = false
        
        self.webView.isOpaque = false;
        self.webView.backgroundColor = .clear
        
        // Load File
        
        do {
            guard let filePath = Bundle.main.path(forResource: "0", ofType: "html")
                else {
                    print ("File reading error")
                    return
                }
            var content =  try String(contentsOfFile: filePath, encoding: .utf8)
            let baseUrl = URL(fileURLWithPath: filePath)
            
            content.changeHtmlStyle(font: "Iowan-Old-Style", fontSize: 4, fontColor: "black")
            webView.loadHTMLString(headerString+content, baseURL: baseUrl)
            
            // add content size Observer
            webView.scrollView.addObserver(self, forKeyPath: #keyPath(UIScrollView.contentSize), options: .new, context: nil)

        }
        catch {
            print ("File HTML error")
        }
    }
    
    override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
        if (keyPath == #keyPath(UIScrollView.contentSize)) {
            let contentSize = webView.scrollView.contentSize
            if contentSize != self.contentSize {
                self.contentSize = contentSize
                DispatchQueue.main.async {
                    self.webView.scrollView.contentOffset.x = CGFloat(UserDefaults.standard.integer(forKey: "pageToLoad"))
                }
            }
        }
    }

    // MARK: - webView Scroll View
    
    func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
        self.stoppedScrolling()
    }

    func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
        if !decelerate {
            self.stoppedScrolling()
        }
    }
    
    func scrollViewDidScroll(_ scrollView: UIScrollView) {
        var currentPage = Int((webView.scrollView.contentOffset.x / webView.scrollView.frame.size.width) + 1)
        let pageCount = Int(webView.scrollView.contentSize.width / webView.scrollView.frame.size.width)
        
        if currentPage == 0 {
            currentPage = 1
        } else {
            
        }
        
        if !webView.isHidden {
            pagesLabel.text = "\( currentPage ) из \( pageCount )"
        } else {
            pagesLabel.text = ""
        }
    }
    
    func scrollViewWillBeginZooming(_ scrollView: UIScrollView, with view: UIView?) {
        webView.scrollView.pinchGestureRecognizer?.isEnabled = false
    }

    func stoppedScrolling() {
        let pageToLoad = Int((webView.scrollView.contentOffset.x))
        UserDefaults.standard.set(pageToLoad, forKey: "pageToLoad")
    }
    
    // MARK: - loading webView

    func webView(_ webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!) {
    }
    
    func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
        
        // Маленькая задержка, которую мне хотелось бы использовать
        
        /*DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
            self.webView.scrollView.contentOffset.x = CGFloat(UserDefaults.standard.integer(forKey: "pageToLoad"))
        }*/
        
        // Большая задержка, которую мне приходится использовать

        // don't do this here... we'll do the "auto-scroll" inside the change contentSize Observer
        //DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) {
        //    self.webView.scrollView.contentOffset.x = CGFloat(UserDefaults.standard.integer(forKey: "pageToLoad"))
        //}
    }

    func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error)  {
    }


}

extension String {
  mutating func changeHtmlStyle(font: String, fontSize: Int, fontColor: String) {
    let style = "<font face='\(font)' size='\(fontSize)' color= '\(fontColor)'>%@"
    self = String(format: style, self)
  }
}

It uses an Observer to watch the contentSize change in the web view's scroll view.

Note that it is called multiple times - with different values - during the load and layout process, but it may do the job for you.

Also note, though, that you'll need to account for changes in the web view size - for example, if the user rotates the device. So... more to do, but this may get you going.

Sign up to request clarification or add additional context in comments.

Comments

1

You can add a property observer and watch the estimated progress of the page load:

override func viewDidLoad() {
    super.viewDidLoad()
    webView.addObserver(self, forKeyPath: #keyPath(WKWebView.estimatedProgress), options: .new, context: nil)

....
}

and observe when the page is being loaded:

override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
    if (keyPath == "estimatedProgress") {
        if webView.estimatedProgress == 1.0 {
            print ("page loaded")
        }
    }
}

You may be able to predict based on the page number how far into the loading process you need to be before you set your offset.

1 Comment

I have the same result as in my code. I have updated my question and added details on how I am using your code.
0

Instead of observing WKWebView.estimatedProgress you should observe UIScrollView.contentSize because you need to scroll to an available position e.g.:

var positionY: CGFloat = 1000
var contentSize = CGSize(width: 0, height: 0)

override func viewDidLoad() {
    super.viewDidLoad()
    ...
    webView?.scrollView.addObserver(self, forKeyPath: #keyPath(UIScrollView.contentSize), options: .new, context: nil)
}

override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
    if (keyPath == #keyPath(UIScrollView.contentSize)) {
        if let contentSize = webView?.scrollView.contentSize, contentSize != self.contentSize {
            self.contentSize = contentSize
            if contentSize.height > positionY {
                webView?.scrollView.setContentOffset(CGPoint(x: 0, y: positionY), animated: true)
            }
        }
    }
}

1 Comment

I have a horizontal ScrollView like ebook reader. But your code doesn't work. Because, as I said, first I see the page with the last reading, and the after several second page last reading page changes to first.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.