在 WKWebView 中加载 html 文本

Load html text in WKWebView

我使用此代码加载我的 html 文件 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")
}

此代码用于加载用户上次停止阅读的页面:

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

我使用此方法加载最后一页的代码:

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"))
    }
}

一开始我用的是deadline: .now() + 0.1,但是没用。因为上次阅读的页面最初是加载的,几秒钟后我在第一页上看到了我的文字。我将其更改为 deadline: .now() + 0.5 并且文本从阅读的最后一页开始正常加载。它有 700 页。但是现在我想加载另一个 1700 页的文本。我和第一次一样有同样的问题。我可以更改 deadline: .now() + 1.0,我的文本将正常加载。但我认为这不是最好的解决方案。我 运行 它在我的 iPhone X 上。但也许如果我 运行 它在 iPad mini 2 我应该改变 deadline: .now() + 10.0 因为 iPad mini 2不是很强大。如何解决问题?

根据@DPrice代码更新:

如果我使用这个代码:

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)"))
        }
    }
}

我的代码中出现了同样的错误结果。

但是如果我使用这个代码:

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)"))
            }
        }
    }
}

一切正常。我的最后一页加载正常。但是并没有解决我的问题

您可以添加一个 属性 观察者并观察页面加载的估计进度:

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 {
            print ("page loaded")
        }
    }
}

您或许能够根据页码预测在设置偏移量之前您需要进入加载过程多远。

而不是观察 WKWebView.estimatedProgress 你应该观察 UIScrollView.contentSize 因为你需要滚动到一个可用的位置例如:

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)
            }
        }
    }
}

这是您的 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)
  }
}

它使用 Observer 来观察 contentSize 网络视图滚动视图的变化。

请注意,它在加载和布局过程中被多次调用 - 具有不同的值,但它可能会为您完成这项工作。

另请注意,您需要考虑网络视图大小的变化 - 例如,如果用户旋转设备。所以...还有更多工作要做,但这可能会让您继续前进。