在 WKWebView 中加载 html 文本

Posted

技术标签:

【中文标题】在 WKWebView 中加载 html 文本【英文标题】:Load html text in WKWebView 【发布时间】:2020-10-27 19:46:03 【问题描述】:

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

一切正常。我的最后一页加载正常。但这并不能解决我的问题。

【问题讨论】:

“我想加载另一个 1700 页的文本” ...这是要加载到 Web 视图中的大量文本,正如您所见,它可能需要很长时间来渲染。如果您正在尝试创建类似“书籍”阅读器的东西,您可能需要考虑另一种方法。 @DonMag 渲染时间对我来说不是很重要。重要的是时间量是准确的。我只是不能确定 5 秒是否适用于所有设备。也许某些设备会更长时间地处理文本,我错了。因此,我希望设备能够自动选择正确的渲染时间。例如,iPhone 12 1.0 秒就足够了,iPad mini 2 - 4.0 秒。我只想在设备中自动检测到它。而不是自己猜测正确的时间。因为它总是不准确,并可能导致不良行为。 您正在加载的 html 中是否有任何脚本正在运行?您如何“分页”“1700”页?如果您能提供一个示例 html 文件,将会很有帮助。 @DonMag 测试项目 - gofile.io/d/4wzUhA 见 style.cssoverflow: -webkit-paged-x !important; direction: ltr !important; 【参考方案1】:

这是您的 ViewController 类的修改版本:

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 来观察 Web 视图的滚动视图中 contentSize 的变化。

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

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

【讨论】:

【参考方案2】:

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

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

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

【讨论】:

我的结果与我的代码相同。我更新了我的问题并添加了有关我如何使用您的代码的详细信息。【参考方案3】:

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

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)
            
        
    


【讨论】:

我有一个像电子书阅读器一样的水平滚动视图。但是你的代码不起作用。因为,正如我所说,首先我看到最后阅读的页面,然后几秒钟后最后阅读页面变为第一页。

以上是关于在 WKWebView 中加载 html 文本的主要内容,如果未能解决你的问题,请参考以下文章

在 WKwebView (SWIFT) 中加载本地资源以及 HTML 字符串?

在ios中加载html时如何在wkwebview中隐藏带有src的图像?

来自html字符串的iOS Swift 4图像未在WKWebView中加载

下载 WKWebView 中加载的嵌入式 PDF

如何更改 WKWebView 文本颜色?

在 WKWebView 中填充输入文本字段