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