diff --git a/iOS/Article/ArticleViewController.swift b/iOS/Article/ArticleViewController.swift index 8b47ffa05..bb839e372 100644 --- a/iOS/Article/ArticleViewController.swift +++ b/iOS/Article/ArticleViewController.swift @@ -202,12 +202,7 @@ class ArticleViewController: UIViewController { } @objc func contentSizeCategoryDidChange(_ note: Notification) { - coordinator.webViewProvider.flushQueue() - coordinator.webViewProvider.replenishQueueIfNeeded() - if let controller = currentWebViewController { - controller.fullReload() - self.pageViewController.setViewControllers([controller], direction: .forward, animated: false, completion: nil) - } + resetWebViewController() } @objc func willEnterForeground(_ note: Notification) { @@ -215,6 +210,7 @@ class ArticleViewController: UIViewController { if AppDefaults.articleFullscreenEnabled { currentWebViewController?.hideBars() } + resetWebViewController() } // MARK: Actions @@ -274,10 +270,6 @@ class ArticleViewController: UIViewController { currentWebViewController?.scrollPageDown() } - func fullReload() { - currentWebViewController?.fullReload() - } - func stopArticleExtractorIfProcessing() { currentWebViewController?.stopArticleExtractorIfProcessing() } @@ -366,4 +358,13 @@ private extension ArticleViewController { return controller } + func resetWebViewController() { + coordinator.webViewProvider.flushQueue() + coordinator.webViewProvider.replenishQueueIfNeeded() + if let controller = currentWebViewController { + controller.fullReload() + self.pageViewController.setViewControllers([controller], direction: .forward, animated: false, completion: nil) + } + } + } diff --git a/iOS/Article/WebViewController.swift b/iOS/Article/WebViewController.swift index d8e59ff05..3483c2079 100644 --- a/iOS/Article/WebViewController.swift +++ b/iOS/Article/WebViewController.swift @@ -148,7 +148,7 @@ class WebViewController: UIViewController { } func fullReload() { - self.loadWebView() + loadWebView(replaceExistingWebView: true) } func showBars() { @@ -288,10 +288,16 @@ extension WebViewController: UIContextMenuInteractionDelegate { // MARK: WKNavigationDelegate extension WebViewController: WKNavigationDelegate { + + func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) { + if view.subviews.count > 1 { + view.subviews.last?.removeFromSuperview() + } + } + func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) { if navigationAction.navigationType == .linkActivated { - guard let url = navigationAction.request.url else { decisionHandler(.allow) return @@ -313,13 +319,13 @@ extension WebViewController: WKNavigationDelegate { } else { decisionHandler(.allow) } - } else { - decisionHandler(.allow) - } - + } + + func webViewWebContentProcessDidTerminate(_ webView: WKWebView) { + fullReload() } } @@ -402,10 +408,10 @@ private struct ImageClickMessage: Codable { private extension WebViewController { - func loadWebView() { + func loadWebView(replaceExistingWebView: Bool = false) { guard isViewLoaded else { return } - if let webView = webView { + if !replaceExistingWebView, let webView = webView { self.renderPage(webView) return } diff --git a/iOS/Article/WebViewProvider.swift b/iOS/Article/WebViewProvider.swift index 9d05473a9..41c0e550c 100644 --- a/iOS/Article/WebViewProvider.swift +++ b/iOS/Article/WebViewProvider.swift @@ -7,42 +7,116 @@ // import Foundation +import RSCore 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 WebViewProvider: NSObject { - let articleIconSchemeHandler: ArticleIconSchemeHandler - - private let minimumQueueDepth = 3 - private let maximumQueueDepth = 6 + private let articleIconSchemeHandler: ArticleIconSchemeHandler + private let operationQueue = MainThreadOperationQueue() private var queue = UIView() init(coordinator: SceneCoordinator, viewController: UIViewController) { articleIconSchemeHandler = ArticleIconSchemeHandler(coordinator: coordinator) super.init() viewController.view.insertSubview(queue, at: 0) - replenishQueueIfNeeded() } func flushQueue() { - queue.subviews.forEach { $0.removeFromSuperview() } + operationQueue.add(WebViewProviderFlushQueueOperation(queue: queue)) } func replenishQueueIfNeeded() { + operationQueue.add(WebViewProviderReplenishQueueOperation(queue: queue, articleIconSchemeHandler: articleIconSchemeHandler)) + } + + func dequeueWebView(completion: @escaping (PreloadedWebView) -> ()) { + operationQueue.add(WebViewProviderDequeueOperation(queue: queue, articleIconSchemeHandler: articleIconSchemeHandler, completion: completion)) + operationQueue.add(WebViewProviderReplenishQueueOperation(queue: queue, articleIconSchemeHandler: articleIconSchemeHandler)) + } + +} + +class WebViewProviderFlushQueueOperation: MainThreadOperation { + + // MainThreadOperation + public var isCanceled = false + public var id: Int? + public weak var operationDelegate: MainThreadOperationDelegate? + public var name: String? = "WebViewProviderFlushQueueOperation" + public var completionBlock: MainThreadOperation.MainThreadOperationCompletionBlock? + + private var queue: UIView + + init(queue: UIView) { + self.queue = queue + } + + func run() { + queue.subviews.forEach { $0.removeFromSuperview() } + self.operationDelegate?.operationDidComplete(self) + } + +} + +class WebViewProviderReplenishQueueOperation: MainThreadOperation { + + // MainThreadOperation + public var isCanceled = false + public var id: Int? + public weak var operationDelegate: MainThreadOperationDelegate? + public var name: String? = "WebViewProviderReplenishQueueOperation" + public var completionBlock: MainThreadOperation.MainThreadOperationCompletionBlock? + + private let minimumQueueDepth = 3 + + private var queue: UIView + private var articleIconSchemeHandler: ArticleIconSchemeHandler + + init(queue: UIView, articleIconSchemeHandler: ArticleIconSchemeHandler) { + self.queue = queue + self.articleIconSchemeHandler = articleIconSchemeHandler + } + + func run() { while queue.subviews.count < minimumQueueDepth { - enqueueWebView(PreloadedWebView(articleIconSchemeHandler: articleIconSchemeHandler)) + let webView = PreloadedWebView(articleIconSchemeHandler: articleIconSchemeHandler) + queue.insertSubview(webView, at: 0) + webView.preload() } + self.operationDelegate?.operationDidComplete(self) } - func dequeueWebView(completion: @escaping (PreloadedWebView) -> ()) { +} + +class WebViewProviderDequeueOperation: MainThreadOperation { + + // MainThreadOperation + public var isCanceled = false + public var id: Int? + public weak var operationDelegate: MainThreadOperationDelegate? + public var name: String? = "WebViewProviderFlushQueueOperation" + public var completionBlock: MainThreadOperation.MainThreadOperationCompletionBlock? + + private var queue: UIView + private var articleIconSchemeHandler: ArticleIconSchemeHandler + private var completion: (PreloadedWebView) -> () + + init(queue: UIView, articleIconSchemeHandler: ArticleIconSchemeHandler, completion: @escaping (PreloadedWebView) -> ()) { + self.queue = queue + self.articleIconSchemeHandler = articleIconSchemeHandler + self.completion = completion + } + + func run() { if let webView = queue.subviews.last as? PreloadedWebView { webView.ready { preloadedWebView in preloadedWebView.removeFromSuperview() - self.replenishQueueIfNeeded() - completion(preloadedWebView) + self.completion(preloadedWebView) + self.operationDelegate?.operationDidComplete(self) } return } @@ -50,18 +124,11 @@ class WebViewProvider: NSObject { assertionFailure("Creating PreloadedWebView in \(#function); queue has run dry.") let webView = PreloadedWebView(articleIconSchemeHandler: articleIconSchemeHandler) + webView.preload() webView.ready { preloadedWebView in - self.replenishQueueIfNeeded() - completion(preloadedWebView) + self.completion(preloadedWebView) + self.operationDelegate?.operationDidComplete(self) } } - func enqueueWebView(_ webView: PreloadedWebView) { - guard queue.subviews.count < maximumQueueDepth else { - return - } - queue.insertSubview(webView, at: 0) - webView.preload() - } - } diff --git a/iOS/MasterFeed/Cell/MasterFeedTableViewSectionHeader.swift b/iOS/MasterFeed/Cell/MasterFeedTableViewSectionHeader.swift index d06a33399..3fcf7506f 100644 --- a/iOS/MasterFeed/Cell/MasterFeedTableViewSectionHeader.swift +++ b/iOS/MasterFeed/Cell/MasterFeedTableViewSectionHeader.swift @@ -73,16 +73,13 @@ class MasterFeedTableViewSectionHeader: UITableViewHeaderFooterView { private let unreadCountView = MasterFeedUnreadCountView(frame: CGRect.zero) - @available(iOS 13.4, *) - private(set) lazy var disclosurePointerInteraction = UIPointerInteraction() - private lazy var disclosureButton: UIButton = { let button = NonIntrinsicButton() button.tintColor = UIColor.tertiaryLabel button.setImage(AppAssets.disclosureImage, for: .normal) button.contentMode = .center if #available(iOS 13.4, *) { - button.addInteraction(disclosurePointerInteraction) + button.addInteraction(UIPointerInteraction()) } button.addTarget(self, action: #selector(toggleDisclosure), for: .touchUpInside) return button diff --git a/iOS/Resources/main_ios.js b/iOS/Resources/main_ios.js index f3cbd712f..885529894 100644 --- a/iOS/Resources/main_ios.js +++ b/iOS/Resources/main_ios.js @@ -35,10 +35,14 @@ class ImageViewer { this.hideLoadingIndicator(); var canvas = document.createElement("canvas"); - canvas.width = this.img.naturalWidth * window.devicePixelRatio; - canvas.height = this.img.naturalHeight * window.devicePixelRatio; + var pixelRatio = window.devicePixelRatio; + do { + canvas.width = this.img.naturalWidth * pixelRatio; + canvas.height = this.img.naturalHeight * pixelRatio; + pixelRatio--; + } while (pixelRatio > 0 && canvas.width * canvas.height > 16777216) canvas.getContext("2d").drawImage(this.img, 0, 0, canvas.width, canvas.height); - + const rect = this.img.getBoundingClientRect(); const message = { x: rect.x, diff --git a/xcconfig/common/NetNewsWire_ios_target_common.xcconfig b/xcconfig/common/NetNewsWire_ios_target_common.xcconfig index e34f4fec5..eb3906f45 100644 --- a/xcconfig/common/NetNewsWire_ios_target_common.xcconfig +++ b/xcconfig/common/NetNewsWire_ios_target_common.xcconfig @@ -1,7 +1,7 @@ // High Level Settings common to both the iOS application and any extensions we bundle with it MARKETING_VERSION = 5.0.1 -CURRENT_PROJECT_VERSION = 44 +CURRENT_PROJECT_VERSION = 46 ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon