Merge pull request #924 from correia/correia/white-flash-on-load

Fix flash on white when loading detail view controller in dark mode
This commit is contained in:
Maurice Parker 2019-08-31 15:02:20 -05:00 committed by GitHub
commit 8335a5cb48
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 85 additions and 37 deletions

View File

@ -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 {

View File

@ -1,8 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="14854.2" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="14865.1" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
<device id="retina6_1" orientation="portrait" appearance="light"/>
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14806.4"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14819.2"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
@ -15,22 +15,13 @@
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<wkWebView opaque="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="t8d-md-Yhc">
<view contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="DNb-lt-KzC">
<rect key="frame" x="0.0" y="44" width="414" height="769"/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<wkWebViewConfiguration key="configuration">
<audiovisualMediaTypes key="mediaTypesRequiringUserActionForPlayback" none="YES"/>
<wkPreferences key="preferences"/>
</wkWebViewConfiguration>
</wkWebView>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
</view>
</subviews>
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
<constraints>
<constraint firstItem="t8d-md-Yhc" firstAttribute="top" secondItem="VUw-jc-0yf" secondAttribute="top" id="0aK-ew-1HG"/>
<constraint firstItem="VUw-jc-0yf" firstAttribute="trailing" secondItem="t8d-md-Yhc" secondAttribute="trailing" id="31v-r8-kzh"/>
<constraint firstItem="VUw-jc-0yf" firstAttribute="bottom" secondItem="t8d-md-Yhc" secondAttribute="bottom" id="kK6-eC-XwD"/>
<constraint firstItem="t8d-md-Yhc" firstAttribute="leading" secondItem="VUw-jc-0yf" secondAttribute="leading" id="nDG-aV-vqc"/>
</constraints>
<viewLayoutGuide key="safeArea" id="VUw-jc-0yf"/>
</view>
<toolbarItems>
@ -112,7 +103,7 @@
<outlet property="prevArticleBarButtonItem" destination="v4j-fq-23N" id="Gny-Oh-cQa"/>
<outlet property="readBarButtonItem" destination="hy0-LS-MzE" id="BzM-x9-tuj"/>
<outlet property="starBarButtonItem" destination="wU4-eH-wC9" id="Z8Q-Lt-dKk"/>
<outlet property="webView" destination="t8d-md-Yhc" id="Iqg-bg-wds"/>
<outlet property="webViewContainer" destination="DNb-lt-KzC" id="Fc1-Ae-pWK"/>
</connections>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="FJe-Yq-33r" sceneMemberID="firstResponder"/>

View File

@ -14,23 +14,42 @@ 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!
deinit {
webView.removeFromSuperview()
DetailViewControllerWebViewProvider.shared.enqueueWebView(webView)
webView = nil
}
override func viewDidLoad() {
super.viewDidLoad()
webView.isHidden = true
webView = DetailViewControllerWebViewProvider.shared.dequeueWebView()
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()
@ -213,16 +232,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 {
@ -234,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)
}
}
}