htmliosswiftwkwebviewwkwebviewconfiguration

Load html text in WKWebView


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.


Solution

  • 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.