Merge pull request #1 from Ranchero-Software/ios-release

Ios release
This commit is contained in:
Stuart Breckenridge 2020-05-13 07:30:28 +08:00 committed by GitHub
commit 1f7d40a537
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 123 additions and 48 deletions

View File

@ -202,12 +202,7 @@ class ArticleViewController: UIViewController {
} }
@objc func contentSizeCategoryDidChange(_ note: Notification) { @objc func contentSizeCategoryDidChange(_ note: Notification) {
coordinator.webViewProvider.flushQueue() resetWebViewController()
coordinator.webViewProvider.replenishQueueIfNeeded()
if let controller = currentWebViewController {
controller.fullReload()
self.pageViewController.setViewControllers([controller], direction: .forward, animated: false, completion: nil)
}
} }
@objc func willEnterForeground(_ note: Notification) { @objc func willEnterForeground(_ note: Notification) {
@ -215,6 +210,7 @@ class ArticleViewController: UIViewController {
if AppDefaults.articleFullscreenEnabled { if AppDefaults.articleFullscreenEnabled {
currentWebViewController?.hideBars() currentWebViewController?.hideBars()
} }
resetWebViewController()
} }
// MARK: Actions // MARK: Actions
@ -274,10 +270,6 @@ class ArticleViewController: UIViewController {
currentWebViewController?.scrollPageDown() currentWebViewController?.scrollPageDown()
} }
func fullReload() {
currentWebViewController?.fullReload()
}
func stopArticleExtractorIfProcessing() { func stopArticleExtractorIfProcessing() {
currentWebViewController?.stopArticleExtractorIfProcessing() currentWebViewController?.stopArticleExtractorIfProcessing()
} }
@ -366,4 +358,13 @@ private extension ArticleViewController {
return controller 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)
}
}
} }

View File

@ -148,7 +148,7 @@ class WebViewController: UIViewController {
} }
func fullReload() { func fullReload() {
self.loadWebView() loadWebView(replaceExistingWebView: true)
} }
func showBars() { func showBars() {
@ -288,10 +288,16 @@ extension WebViewController: UIContextMenuInteractionDelegate {
// MARK: WKNavigationDelegate // MARK: WKNavigationDelegate
extension WebViewController: 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) { func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
if navigationAction.navigationType == .linkActivated { if navigationAction.navigationType == .linkActivated {
guard let url = navigationAction.request.url else { guard let url = navigationAction.request.url else {
decisionHandler(.allow) decisionHandler(.allow)
return return
@ -313,13 +319,13 @@ extension WebViewController: WKNavigationDelegate {
} else { } else {
decisionHandler(.allow) decisionHandler(.allow)
} }
} else { } else {
decisionHandler(.allow) decisionHandler(.allow)
} }
}
func webViewWebContentProcessDidTerminate(_ webView: WKWebView) {
fullReload()
} }
} }
@ -402,10 +408,10 @@ private struct ImageClickMessage: Codable {
private extension WebViewController { private extension WebViewController {
func loadWebView() { func loadWebView(replaceExistingWebView: Bool = false) {
guard isViewLoaded else { return } guard isViewLoaded else { return }
if let webView = webView { if !replaceExistingWebView, let webView = webView {
self.renderPage(webView) self.renderPage(webView)
return return
} }

View File

@ -7,42 +7,116 @@
// //
import Foundation import Foundation
import RSCore
import WebKit import WebKit
/// WKWebView has an awful behavior of a flash to white on first load when in dark mode. /// 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. /// 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 { class WebViewProvider: NSObject {
let articleIconSchemeHandler: ArticleIconSchemeHandler private let articleIconSchemeHandler: ArticleIconSchemeHandler
private let operationQueue = MainThreadOperationQueue()
private let minimumQueueDepth = 3
private let maximumQueueDepth = 6
private var queue = UIView() private var queue = UIView()
init(coordinator: SceneCoordinator, viewController: UIViewController) { init(coordinator: SceneCoordinator, viewController: UIViewController) {
articleIconSchemeHandler = ArticleIconSchemeHandler(coordinator: coordinator) articleIconSchemeHandler = ArticleIconSchemeHandler(coordinator: coordinator)
super.init() super.init()
viewController.view.insertSubview(queue, at: 0) viewController.view.insertSubview(queue, at: 0)
replenishQueueIfNeeded() replenishQueueIfNeeded()
} }
func flushQueue() { func flushQueue() {
queue.subviews.forEach { $0.removeFromSuperview() } operationQueue.add(WebViewProviderFlushQueueOperation(queue: queue))
} }
func replenishQueueIfNeeded() { func replenishQueueIfNeeded() {
while queue.subviews.count < minimumQueueDepth { operationQueue.add(WebViewProviderReplenishQueueOperation(queue: queue, articleIconSchemeHandler: articleIconSchemeHandler))
enqueueWebView(PreloadedWebView(articleIconSchemeHandler: articleIconSchemeHandler))
}
} }
func dequeueWebView(completion: @escaping (PreloadedWebView) -> ()) { 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 {
let webView = PreloadedWebView(articleIconSchemeHandler: articleIconSchemeHandler)
queue.insertSubview(webView, at: 0)
webView.preload()
}
self.operationDelegate?.operationDidComplete(self)
}
}
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 { if let webView = queue.subviews.last as? PreloadedWebView {
webView.ready { preloadedWebView in webView.ready { preloadedWebView in
preloadedWebView.removeFromSuperview() preloadedWebView.removeFromSuperview()
self.replenishQueueIfNeeded() self.completion(preloadedWebView)
completion(preloadedWebView) self.operationDelegate?.operationDidComplete(self)
} }
return return
} }
@ -50,18 +124,11 @@ class WebViewProvider: NSObject {
assertionFailure("Creating PreloadedWebView in \(#function); queue has run dry.") assertionFailure("Creating PreloadedWebView in \(#function); queue has run dry.")
let webView = PreloadedWebView(articleIconSchemeHandler: articleIconSchemeHandler) let webView = PreloadedWebView(articleIconSchemeHandler: articleIconSchemeHandler)
webView.ready { preloadedWebView in
self.replenishQueueIfNeeded()
completion(preloadedWebView)
}
}
func enqueueWebView(_ webView: PreloadedWebView) {
guard queue.subviews.count < maximumQueueDepth else {
return
}
queue.insertSubview(webView, at: 0)
webView.preload() webView.preload()
webView.ready { preloadedWebView in
self.completion(preloadedWebView)
self.operationDelegate?.operationDidComplete(self)
}
} }
} }

View File

@ -73,16 +73,13 @@ class MasterFeedTableViewSectionHeader: UITableViewHeaderFooterView {
private let unreadCountView = MasterFeedUnreadCountView(frame: CGRect.zero) private let unreadCountView = MasterFeedUnreadCountView(frame: CGRect.zero)
@available(iOS 13.4, *)
private(set) lazy var disclosurePointerInteraction = UIPointerInteraction()
private lazy var disclosureButton: UIButton = { private lazy var disclosureButton: UIButton = {
let button = NonIntrinsicButton() let button = NonIntrinsicButton()
button.tintColor = UIColor.tertiaryLabel button.tintColor = UIColor.tertiaryLabel
button.setImage(AppAssets.disclosureImage, for: .normal) button.setImage(AppAssets.disclosureImage, for: .normal)
button.contentMode = .center button.contentMode = .center
if #available(iOS 13.4, *) { if #available(iOS 13.4, *) {
button.addInteraction(disclosurePointerInteraction) button.addInteraction(UIPointerInteraction())
} }
button.addTarget(self, action: #selector(toggleDisclosure), for: .touchUpInside) button.addTarget(self, action: #selector(toggleDisclosure), for: .touchUpInside)
return button return button

View File

@ -35,8 +35,12 @@ class ImageViewer {
this.hideLoadingIndicator(); this.hideLoadingIndicator();
var canvas = document.createElement("canvas"); var canvas = document.createElement("canvas");
canvas.width = this.img.naturalWidth * window.devicePixelRatio; var pixelRatio = window.devicePixelRatio;
canvas.height = this.img.naturalHeight * 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); canvas.getContext("2d").drawImage(this.img, 0, 0, canvas.width, canvas.height);
const rect = this.img.getBoundingClientRect(); const rect = this.img.getBoundingClientRect();

View File

@ -1,7 +1,7 @@
// High Level Settings common to both the iOS application and any extensions we bundle with it // High Level Settings common to both the iOS application and any extensions we bundle with it
MARKETING_VERSION = 5.0.1 MARKETING_VERSION = 5.0.1
CURRENT_PROJECT_VERSION = 44 CURRENT_PROJECT_VERSION = 46
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon