From e5782ca464f1b2d2dc9387e4f0d89ee2a6558589 Mon Sep 17 00:00:00 2001 From: Jim Correia Date: Sat, 31 Aug 2019 11:27:59 -0700 Subject: [PATCH 1/3] Removed the hide the webview workaround to expose the flash to white problem. --- iOS/Detail/DetailViewController.swift | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/iOS/Detail/DetailViewController.swift b/iOS/Detail/DetailViewController.swift index 2eb994207..8755137d2 100644 --- a/iOS/Detail/DetailViewController.swift +++ b/iOS/Detail/DetailViewController.swift @@ -28,7 +28,6 @@ class DetailViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() - webView.isHidden = true webView.navigationDelegate = self markAsRead() @@ -213,16 +212,6 @@ extension DetailViewController: WKNavigationDelegate { } } - - func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) { - // We initially hide the webview and only show it after it has loaded to avoid the - // white flashing that WKWebView does when it loads. This is especially noticable - // in dark mode. - DispatchQueue.main.asyncAfter(deadline: .now() + 0.05) { - webView.isHidden = false - } - } - } private extension DetailViewController { From a5b2c759ecb1552d526c89c50f39a18059a7c992 Mon Sep 17 00:00:00 2001 From: Jim Correia Date: Sat, 31 Aug 2019 12:30:01 -0700 Subject: [PATCH 2/3] Create the WKWebView instance at runtime. Specify a container view in the storyboard and create+install the web view at viewDidLoad time. This lets us experiment with solutions for providing a web view that avoids the flash-to-white problem. --- iOS/Base.lproj/Main.storyboard | 23 ++++++------------ iOS/Detail/DetailViewController.swift | 34 +++++++++++++++++++-------- 2 files changed, 31 insertions(+), 26 deletions(-) diff --git a/iOS/Base.lproj/Main.storyboard b/iOS/Base.lproj/Main.storyboard index 4c40e5500..63ea87669 100644 --- a/iOS/Base.lproj/Main.storyboard +++ b/iOS/Base.lproj/Main.storyboard @@ -1,8 +1,8 @@ - + - + @@ -15,22 +15,13 @@ - + - - - - - - + + + - - - - - - @@ -112,7 +103,7 @@ - + diff --git a/iOS/Detail/DetailViewController.swift b/iOS/Detail/DetailViewController.swift index 8755137d2..632a5134f 100644 --- a/iOS/Detail/DetailViewController.swift +++ b/iOS/Detail/DetailViewController.swift @@ -14,22 +14,36 @@ import SafariServices class DetailViewController: UIViewController { - @IBOutlet weak var nextUnreadBarButtonItem: UIBarButtonItem! - @IBOutlet weak var prevArticleBarButtonItem: UIBarButtonItem! - @IBOutlet weak var nextArticleBarButtonItem: UIBarButtonItem! - @IBOutlet weak var readBarButtonItem: UIBarButtonItem! - @IBOutlet weak var starBarButtonItem: UIBarButtonItem! - @IBOutlet weak var actionBarButtonItem: UIBarButtonItem! - @IBOutlet weak var browserBarButtonItem: UIBarButtonItem! - @IBOutlet weak var webView: WKWebView! - + @IBOutlet private weak var nextUnreadBarButtonItem: UIBarButtonItem! + @IBOutlet private weak var prevArticleBarButtonItem: UIBarButtonItem! + @IBOutlet private weak var nextArticleBarButtonItem: UIBarButtonItem! + @IBOutlet private weak var readBarButtonItem: UIBarButtonItem! + @IBOutlet private weak var starBarButtonItem: UIBarButtonItem! + @IBOutlet private weak var actionBarButtonItem: UIBarButtonItem! + @IBOutlet private weak var browserBarButtonItem: UIBarButtonItem! + @IBOutlet private weak var webViewContainer: UIView! + private var webView: WKWebView! + weak var coordinator: AppCoordinator! override func viewDidLoad() { - super.viewDidLoad() + + webView = WKWebView(frame: webViewContainer.bounds) + webView.translatesAutoresizingMaskIntoConstraints = false webView.navigationDelegate = self + webViewContainer.addSubview(webView) + + let constraints: [NSLayoutConstraint] = [ + webView.leadingAnchor.constraint(equalTo: webViewContainer.safeAreaLayoutGuide.leadingAnchor), + webView.trailingAnchor.constraint(equalTo: webViewContainer.safeAreaLayoutGuide.trailingAnchor), + webView.topAnchor.constraint(equalTo: webViewContainer.safeAreaLayoutGuide.topAnchor), + webView.bottomAnchor.constraint(equalTo: webViewContainer.safeAreaLayoutGuide.bottomAnchor), + ] + + NSLayoutConstraint.activate(constraints) + markAsRead() updateUI() reloadHTML() From f7b53283d6602d1a34f1e3b6a0ca7a12bf863242 Mon Sep 17 00:00:00 2001 From: Jim Correia Date: Sat, 31 Aug 2019 12:53:03 -0700 Subject: [PATCH 3/3] Maintain a queue of "prepared" web views for use in DetailWebViewController. WKWebView has an awful behavior of a flash to white on first load when in dark mode. Keep a queue of WebViews where we've already done a trivial load so that by the time we need them in the UI, they're past the flash-to-shite part of their lifecycle. --- iOS/AppCoordinator.swift | 3 ++ iOS/Detail/DetailViewController.swift | 53 ++++++++++++++++++++++++++- 2 files changed, 55 insertions(+), 1 deletion(-) diff --git a/iOS/AppCoordinator.swift b/iOS/AppCoordinator.swift index 1eaf7f4e5..415048466 100644 --- a/iOS/AppCoordinator.swift +++ b/iOS/AppCoordinator.swift @@ -237,6 +237,9 @@ class AppCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider { NotificationCenter.default.addObserver(self, selector: #selector(userDefaultsDidChange(_:)), name: UserDefaults.didChangeNotification, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(accountDidDownloadArticles(_:)), name: .AccountDidDownloadArticles, object: nil) + + // Force lazy initialization of the web view provider so that it can warm up the queue of prepared web views + let _ = DetailViewControllerWebViewProvider.shared } func start() -> UIViewController { diff --git a/iOS/Detail/DetailViewController.swift b/iOS/Detail/DetailViewController.swift index 632a5134f..214e3dc8e 100644 --- a/iOS/Detail/DetailViewController.swift +++ b/iOS/Detail/DetailViewController.swift @@ -26,10 +26,16 @@ class DetailViewController: UIViewController { weak var coordinator: AppCoordinator! + deinit { + webView.removeFromSuperview() + DetailViewControllerWebViewProvider.shared.enqueueWebView(webView) + webView = nil + } + override func viewDidLoad() { super.viewDidLoad() - webView = WKWebView(frame: webViewContainer.bounds) + webView = DetailViewControllerWebViewProvider.shared.dequeueWebView() webView.translatesAutoresizingMaskIntoConstraints = false webView.navigationDelegate = self @@ -237,3 +243,48 @@ private extension DetailViewController { } } + +// MARK: - + +/// WKWebView has an awful behavior of a flash to white on first load when in dark mode. +/// Keep a queue of WebViews where we've already done a trivial load so that by the time we need them in the UI, they're past the flash-to-shite part of their lifecycle. +class DetailViewControllerWebViewProvider { + static var shared = DetailViewControllerWebViewProvider() + + func dequeueWebView() -> WKWebView { + if let webView = queue.popLast() { + replenishQueueIfNeeded() + return webView + } + + assertionFailure("Creating WKWebView in \(#function); queue has run dry.") + let webView = WKWebView(frame: .zero) + return webView + } + + func enqueueWebView(_ webView: WKWebView) { + webView.uiDelegate = nil + webView.navigationDelegate = nil + + let html = ArticleRenderer.noSelectionHTML(style: .defaultStyle) + webView.loadHTMLString(html, baseURL: nil) + + queue.insert(webView, at: 0) + } + + // MARK: Private + + private let minimumQueueDepth = 3 + private var queue: [WKWebView] = [] + + private init() { + replenishQueueIfNeeded() + } + + private func replenishQueueIfNeeded() { + while queue.count < minimumQueueDepth { + let webView = WKWebView(frame: .zero) + enqueueWebView(webView) + } + } +}