diff --git a/NetNewsWire.xcodeproj/project.pbxproj b/NetNewsWire.xcodeproj/project.pbxproj index 6070aa612..6cfd8fde3 100644 --- a/NetNewsWire.xcodeproj/project.pbxproj +++ b/NetNewsWire.xcodeproj/project.pbxproj @@ -78,6 +78,7 @@ 5170743A232AABFC00A461A3 /* FlattenedAccountFolderPickerData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51C452812265093600C03939 /* FlattenedAccountFolderPickerData.swift */; }; 517630042336215100E15FFF /* main.js in Resources */ = {isa = PBXBuildFile; fileRef = 517630032336215100E15FFF /* main.js */; }; 517630052336215100E15FFF /* main.js in Resources */ = {isa = PBXBuildFile; fileRef = 517630032336215100E15FFF /* main.js */; }; + 517630232336657E00E15FFF /* DetailViewControllerWebViewProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 517630222336657E00E15FFF /* DetailViewControllerWebViewProvider.swift */; }; 5183CCD0226E1E880010922C /* NonIntrinsicLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5183CCCF226E1E880010922C /* NonIntrinsicLabel.swift */; }; 5183CCDA226E31A50010922C /* NonIntrinsicImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5183CCD9226E31A50010922C /* NonIntrinsicImageView.swift */; }; 5183CCDD226F1F5C0010922C /* NavigationProgressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5183CCDC226F1F5C0010922C /* NavigationProgressView.swift */; }; @@ -813,6 +814,7 @@ 515D4FCE2325B3D000EE1167 /* NetNewsWire_iOSshareextension_target.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = NetNewsWire_iOSshareextension_target.xcconfig; sourceTree = ""; }; 51707438232AA97100A461A3 /* ShareFolderPickerController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareFolderPickerController.swift; sourceTree = ""; }; 517630032336215100E15FFF /* main.js */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.javascript; path = main.js; sourceTree = ""; }; + 517630222336657E00E15FFF /* DetailViewControllerWebViewProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetailViewControllerWebViewProvider.swift; sourceTree = ""; }; 5183CCCF226E1E880010922C /* NonIntrinsicLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NonIntrinsicLabel.swift; sourceTree = ""; }; 5183CCD9226E31A50010922C /* NonIntrinsicImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NonIntrinsicImageView.swift; sourceTree = ""; }; 5183CCDC226F1F5C0010922C /* NavigationProgressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationProgressView.swift; sourceTree = ""; }; @@ -1370,6 +1372,7 @@ isa = PBXGroup; children = ( 51C4527E2265092C00C03939 /* DetailViewController.swift */, + 517630222336657E00E15FFF /* DetailViewControllerWebViewProvider.swift */, 515ADE3F22E11FAE006B2460 /* SystemMessageViewController.swift */, ); path = Detail; @@ -2702,6 +2705,7 @@ 51C452A722650A3D00C03939 /* RSImage-Extensions.swift in Sources */, 51C45269226508F600C03939 /* MasterFeedTableViewCell.swift in Sources */, 51F85BFD2275DCA800C787DC /* SingleLineUILabelSizer.swift in Sources */, + 517630232336657E00E15FFF /* DetailViewControllerWebViewProvider.swift in Sources */, 51C4528F226509BD00C03939 /* UnreadFeed.swift in Sources */, 51AF460E232488C6001742EF /* Account-Extensions.swift in Sources */, 5183CCDD226F1F5C0010922C /* NavigationProgressView.swift in Sources */, diff --git a/iOS/Detail/DetailViewController.swift b/iOS/Detail/DetailViewController.swift index c6a8c1cc7..23d1bcd0f 100644 --- a/iOS/Detail/DetailViewController.swift +++ b/iOS/Detail/DetailViewController.swift @@ -40,27 +40,21 @@ class DetailViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() - 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) - - updateArticleSelection() - NotificationCenter.default.addObserver(self, selector: #selector(unreadCountDidChange(_:)), name: .UnreadCountDidChange, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(statusesDidChange(_:)), name: .StatusesDidChange, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(progressDidChange(_:)), name: .AccountRefreshProgressDidChange, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(contentSizeCategoryDidChange(_:)), name: UIContentSizeCategory.didChangeNotification, object: nil) + + DetailViewControllerWebViewProvider.shared.dequeueWebView() { webView in + + self.webView = webView + webView.navigationDelegate = self + + self.webViewContainer.addChildAndPin(webView) + self.updateArticleSelection() + + } + } override func viewDidAppear(_ animated: Bool) { @@ -261,58 +255,3 @@ private struct TemplateData: Codable { let style: String let body: String } - - -// 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) { - guard queue.count < maximumQueueDepth else { - return - } - - webView.uiDelegate = nil - webView.navigationDelegate = nil - - let pageURL = Bundle.main.url(forResource: "page", withExtension: "html")! - let page = try! String(contentsOf: pageURL) - let baseURL = pageURL.deletingLastPathComponent() - - webView.loadHTMLString(page, baseURL: baseURL) - - queue.insert(webView, at: 0) - } - - // MARK: Private - - private let minimumQueueDepth = 3 - private let maximumQueueDepth = 6 - private var queue: [WKWebView] = [] - - private init() { - replenishQueueIfNeeded() - } - - private func replenishQueueIfNeeded() { - while queue.count < minimumQueueDepth { - let webView = WKWebView(frame: .zero) - enqueueWebView(webView) - } - } -} diff --git a/iOS/Detail/DetailViewControllerWebViewProvider.swift b/iOS/Detail/DetailViewControllerWebViewProvider.swift new file mode 100644 index 000000000..1b3796bc1 --- /dev/null +++ b/iOS/Detail/DetailViewControllerWebViewProvider.swift @@ -0,0 +1,88 @@ +// +// DetailViewControllerWebViewProvider.swift +// NetNewsWire-iOS +// +// Created by Maurice Parker on 9/21/19. +// Copyright © 2019 Ranchero Software. All rights reserved. +// + +import Foundation +import WebKit + +/// 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: NSObject, WKNavigationDelegate { + + static let shared = DetailViewControllerWebViewProvider() + + private let minimumQueueDepth = 3 + private let maximumQueueDepth = 6 + private var queue: [WKWebView] = [] + + private var waitingForFirstLoad = true + private var waitingCompletionHandler: ((WKWebView) -> ())? + + func dequeueWebView(completion: @escaping (WKWebView) -> ()) { + if waitingForFirstLoad { + waitingCompletionHandler = completion + } else { + completeRequest(completion: completion) + } + } + + func enqueueWebView(_ webView: WKWebView) { + guard queue.count < maximumQueueDepth else { + return + } + + webView.navigationDelegate = self + queue.insert(webView, at: 0) + + let pageURL = Bundle.main.url(forResource: "page", withExtension: "html")! + let page = try! String(contentsOf: pageURL) + let baseURL = pageURL.deletingLastPathComponent() + + webView.loadHTMLString(page, baseURL: baseURL) + + } + + // MARK: WKNavigationDelegate + + func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) { + if waitingForFirstLoad { + waitingForFirstLoad = false + if let completion = waitingCompletionHandler { + completeRequest(completion: completion) + waitingCompletionHandler = nil + } + } + } + + // MARK: Private + + private override init() { + super.init() + replenishQueueIfNeeded() + } + + private func replenishQueueIfNeeded() { + while queue.count < minimumQueueDepth { + let webView = WKWebView(frame: .zero) + enqueueWebView(webView) + } + } + + private func completeRequest(completion: @escaping (WKWebView) -> ()) { + if let webView = queue.popLast() { + webView.navigationDelegate = nil + replenishQueueIfNeeded() + completion(webView) + return + } + + assertionFailure("Creating WKWebView in \(#function); queue has run dry.") + let webView = WKWebView(frame: .zero) + completion(webView) + } + +} diff --git a/submodules/RSCore b/submodules/RSCore index 4dbd31b09..fd55bace3 160000 --- a/submodules/RSCore +++ b/submodules/RSCore @@ -1 +1 @@ -Subproject commit 4dbd31b090ab15c3966e9810a65edbf4abdbdd33 +Subproject commit fd55bace3c2ef2c9e5af8110c0fd742e2135cad1