Always swap out the web view to prevent white flashing between articles and make loading faster.

This commit is contained in:
Maurice Parker 2020-01-31 21:07:08 -07:00
parent 3a69425752
commit 8cb28b63b8
1 changed files with 72 additions and 57 deletions

View File

@ -29,7 +29,10 @@ class WebViewController: UIViewController {
private var topShowBarsViewConstraint: NSLayoutConstraint! private var topShowBarsViewConstraint: NSLayoutConstraint!
private var bottomShowBarsViewConstraint: NSLayoutConstraint! private var bottomShowBarsViewConstraint: NSLayoutConstraint!
private var webView: WKWebView! private var webView: WKWebView? {
return view.subviews[0] as? WKWebView
}
private lazy var contextMenuInteraction = UIContextMenuInteraction(delegate: self) private lazy var contextMenuInteraction = UIContextMenuInteraction(delegate: self)
private var isFullScreenAvailable: Bool { private var isFullScreenAvailable: Bool {
return traitCollection.userInterfaceIdiom == .phone && coordinator.isRootSplitCollapsed return traitCollection.userInterfaceIdiom == .phone && coordinator.isRootSplitCollapsed
@ -46,7 +49,7 @@ class WebViewController: UIViewController {
var isShowingExtractedArticle = false { var isShowingExtractedArticle = false {
didSet { didSet {
if isShowingExtractedArticle != oldValue { if isShowingExtractedArticle != oldValue {
reloadHTML() loadWebView()
} }
} }
} }
@ -68,7 +71,7 @@ class WebViewController: UIViewController {
} }
if article != oldValue { if article != oldValue {
windowScrollY = 0 windowScrollY = 0
reloadHTML() loadWebView()
} }
} }
} }
@ -77,14 +80,7 @@ class WebViewController: UIViewController {
var windowScrollY = 0 var windowScrollY = 0
deinit { deinit {
if webView != nil { recycleWebView(webView)
webView?.evaluateJavaScript("cancelImageLoad();")
webView.configuration.userContentController.removeScriptMessageHandler(forName: MessageName.imageWasClicked)
webView.configuration.userContentController.removeScriptMessageHandler(forName: MessageName.imageWasShown)
webView.removeFromSuperview()
coordinator.webViewProvider.enqueueWebView(webView)
webView = nil
}
} }
override func viewDidLoad() { override func viewDidLoad() {
@ -95,43 +91,16 @@ class WebViewController: UIViewController {
NotificationCenter.default.addObserver(self, selector: #selector(faviconDidBecomeAvailable(_:)), name: .FaviconDidBecomeAvailable, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(faviconDidBecomeAvailable(_:)), name: .FaviconDidBecomeAvailable, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(contentSizeCategoryDidChange(_:)), name: UIContentSizeCategory.didChangeNotification, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(contentSizeCategoryDidChange(_:)), name: UIContentSizeCategory.didChangeNotification, object: nil)
coordinator.webViewProvider.dequeueWebView() { webView in
// Add the webview
self.webView = webView
webView.translatesAutoresizingMaskIntoConstraints = false
self.view.addSubview(webView)
NSLayoutConstraint.activate([
self.view.leadingAnchor.constraint(equalTo: webView.leadingAnchor),
self.view.trailingAnchor.constraint(equalTo: webView.trailingAnchor),
self.view.topAnchor.constraint(equalTo: webView.topAnchor),
self.view.bottomAnchor.constraint(equalTo: webView.bottomAnchor)
])
// Configure the tap zones // Configure the tap zones
self.configureTopShowBarsView() configureTopShowBarsView()
self.configureBottomShowBarsView() configureBottomShowBarsView()
// Configure the webview loadWebView()
webView.navigationDelegate = self
webView.uiDelegate = self
webView.scrollView.delegate = self
self.configureContextMenuInteraction()
webView.configuration.userContentController.add(WrapperScriptMessageHandler(self), name: MessageName.imageWasClicked)
webView.configuration.userContentController.add(WrapperScriptMessageHandler(self), name: MessageName.imageWasShown)
self.reloadHTML()
self.view.setNeedsLayout()
self.view.layoutIfNeeded()
}
} }
override func viewWillDisappear(_ animated: Bool) { override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated) super.viewWillDisappear(animated)
stopMediaPlayback() stopMediaPlayback()
} }
@ -162,14 +131,17 @@ class WebViewController: UIViewController {
// MARK: API // MARK: API
func focus() { func focus() {
webView.becomeFirstResponder() webView?.becomeFirstResponder()
} }
func canScrollDown() -> Bool { func canScrollDown() -> Bool {
guard let webView = webView else { return false }
return webView.scrollView.contentOffset.y < finalScrollPosition() return webView.scrollView.contentOffset.y < finalScrollPosition()
} }
func scrollPageDown() { func scrollPageDown() {
guard let webView = webView else { return }
let scrollToY: CGFloat = { let scrollToY: CGFloat = {
let fullScroll = webView.scrollView.contentOffset.y + webView.scrollView.layoutMarginsGuide.layoutFrame.height let fullScroll = webView.scrollView.contentOffset.y + webView.scrollView.layoutMarginsGuide.layoutFrame.height
let final = finalScrollPosition() let final = finalScrollPosition()
@ -191,7 +163,7 @@ class WebViewController: UIViewController {
} }
func fullReload() { func fullReload() {
self.reloadHTML() self.loadWebView()
} }
func showBars() { func showBars() {
@ -269,7 +241,7 @@ extension WebViewController: ArticleExtractorDelegate {
func articleExtractionDidFail(with: Error) { func articleExtractionDidFail(with: Error) {
stopArticleExtractor() stopArticleExtractor()
articleExtractorButtonState = .error articleExtractorButtonState = .error
reloadHTML() loadWebView()
} }
func articleExtractionDidComplete(extractedArticle: ExtractedArticle) { func articleExtractionDidComplete(extractedArticle: ExtractedArticle) {
@ -352,10 +324,6 @@ extension WebViewController: WKNavigationDelegate {
} }
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
renderPage()
}
} }
// MARK: WKUIDelegate // MARK: WKUIDelegate
@ -454,8 +422,53 @@ private struct ImageClickMessage: Codable {
private extension WebViewController { private extension WebViewController {
func reloadHTML() { func loadWebView() {
webView?.loadFileURL(ArticleRenderer.page.url, allowingReadAccessTo: ArticleRenderer.page.baseURL) guard isViewLoaded else { return }
coordinator.webViewProvider.dequeueWebView() { webView in
let webViewToRecycle = self.webView
// Add the webview
webView.translatesAutoresizingMaskIntoConstraints = false
self.view.insertSubview(webView, at: 0)
NSLayoutConstraint.activate([
self.view.leadingAnchor.constraint(equalTo: webView.leadingAnchor),
self.view.trailingAnchor.constraint(equalTo: webView.trailingAnchor),
self.view.topAnchor.constraint(equalTo: webView.topAnchor),
self.view.bottomAnchor.constraint(equalTo: webView.bottomAnchor)
])
self.view.setNeedsLayout()
self.view.layoutIfNeeded()
// Configure the webview
webView.navigationDelegate = self
webView.uiDelegate = self
webView.scrollView.delegate = self
self.configureContextMenuInteraction()
webView.configuration.userContentController.add(WrapperScriptMessageHandler(self), name: MessageName.imageWasClicked)
webView.configuration.userContentController.add(WrapperScriptMessageHandler(self), name: MessageName.imageWasShown)
self.renderPage()
self.recycleWebView(webViewToRecycle)
}
}
func recycleWebView(_ webView: WKWebView?) {
guard let webView = webView else { return }
webView.removeFromSuperview()
webView.evaluateJavaScript("cancelImageLoad();")
webView.configuration.userContentController.removeScriptMessageHandler(forName: MessageName.imageWasClicked)
webView.configuration.userContentController.removeScriptMessageHandler(forName: MessageName.imageWasShown)
webView.interactions.removeAll()
coordinator.webViewProvider.enqueueWebView(webView)
} }
func renderPage() { func renderPage() {
@ -497,6 +510,7 @@ private extension WebViewController {
} }
func finalScrollPosition() -> CGFloat { func finalScrollPosition() -> CGFloat {
guard let webView = webView else { return 0 }
return webView.scrollView.contentSize.height - webView.scrollView.bounds.height + webView.scrollView.safeAreaInsets.bottom return webView.scrollView.contentSize.height - webView.scrollView.bounds.height + webView.scrollView.safeAreaInsets.bottom
} }
@ -522,7 +536,8 @@ private extension WebViewController {
} }
func imageWasClicked(body: String?) { func imageWasClicked(body: String?) {
guard let body = body, guard let webView = webView,
let body = body,
let data = body.data(using: .utf8), let data = body.data(using: .utf8),
let clickMessage = try? JSONDecoder().decode(ImageClickMessage.self, from: data), let clickMessage = try? JSONDecoder().decode(ImageClickMessage.self, from: data),
let range = clickMessage.imageURL.range(of: ";base64,") let range = clickMessage.imageURL.range(of: ";base64,")