Unify DetailIconSchemeHandler and ArticleIconSchemeHandler into one shared ArticleIconSchemeHandler.

This commit is contained in:
Brent Simmons 2025-01-20 14:28:23 -08:00
parent 09397f0a74
commit 97757a567f
5 changed files with 123 additions and 115 deletions

View File

@ -1,46 +0,0 @@
//
// DetailIconSchemeHandler.swift
// NetNewsWire-iOS
//
// Created by Maurice Parker on 11/7/19.
// Copyright © 2019 Ranchero Software. All rights reserved.
//
import Foundation
import WebKit
import Articles
class DetailIconSchemeHandler: NSObject, WKURLSchemeHandler {
var currentArticle: Article?
func webView(_ webView: WKWebView, start urlSchemeTask: WKURLSchemeTask) {
guard let responseURL = urlSchemeTask.request.url, let iconImage = self.currentArticle?.iconImage() else {
urlSchemeTask.didFailWithError(URLError(.fileDoesNotExist))
return
}
let iconView = IconView(frame: CGRect(x: 0, y: 0, width: 48, height: 48))
iconView.iconImage = iconImage
let renderedImage = iconView.asImage()
guard let data = renderedImage.dataRepresentation() else {
urlSchemeTask.didFailWithError(URLError(.fileDoesNotExist))
return
}
let headerFields = ["Cache-Control": "no-cache"]
if let response = HTTPURLResponse(url: responseURL, statusCode: 200, httpVersion: nil, headerFields: headerFields) {
urlSchemeTask.didReceive(response)
urlSchemeTask.didReceive(data)
urlSchemeTask.didFinish()
}
}
func webView(_ webView: WKWebView, stop urlSchemeTask: WKURLSchemeTask) {
urlSchemeTask.didFailWithError(URLError(.unknown))
}
}

View File

@ -56,8 +56,8 @@ final class DetailWebViewController: NSViewController {
webView.configuration.preferences._developerExtrasEnabled = newValue
}
}
private let detailIconSchemeHandler = DetailIconSchemeHandler()
private lazy var articleIconSchemeHandler = ArticleIconSchemeHandler(delegate: self)
private var waitingForFirstReload = false
private let keyboardDelegate = DetailKeyboardDelegate()
private var windowScrollY: CGFloat?
@ -79,7 +79,7 @@ final class DetailWebViewController: NSViewController {
override func loadView() {
let configuration = WebViewConfiguration.configuration(with: detailIconSchemeHandler)
let configuration = WebViewConfiguration.configuration(with: articleIconSchemeHandler)
webView = DetailWebView(frame: NSRect.zero, configuration: configuration)
webView.uiDelegate = self
@ -171,6 +171,25 @@ final class DetailWebViewController: NSViewController {
}
// MARK: - ArticleIconSchemeHandlerDelegate
extension DetailWebViewController: ArticleIconSchemeHandlerDelegate {
func articleIconSchemeHandler(_: ArticleIconSchemeHandler, imageForArticleID articleID: String) -> IconImage? {
guard let article else {
assertionFailure("Did not expect request for article image when there is no current article.")
return nil
}
guard articleID == article.articleID else {
assertionFailure("Expected articleID to match current articleID.")
return nil
}
return article.iconImage() // May be nil  not a programming error
}
}
// MARK: - WKScriptMessageHandler
extension DetailWebViewController: WKScriptMessageHandler {
@ -282,10 +301,8 @@ private extension DetailWebViewController {
case .loading:
rendering = ArticleRenderer.loadingHTML(theme: theme)
case .article(let article, _):
detailIconSchemeHandler.currentArticle = article
rendering = ArticleRenderer.articleHTML(article: article, theme: theme)
case .extracted(let article, let extractedArticle, _):
detailIconSchemeHandler.currentArticle = article
rendering = ArticleRenderer.articleHTML(article: article, extractedArticle: extractedArticle, theme: theme)
}

View File

@ -0,0 +1,79 @@
//
// ArticleIconSchemeHandler.swift
// NetNewsWire
//
// Created by Brent Simmons on 1/20/25.
// Copyright © 2025 Ranchero Software. All rights reserved.
//
import Foundation
import WebKit
protocol ArticleIconSchemeHandlerDelegate: AnyObject {
func articleIconSchemeHandler(_: ArticleIconSchemeHandler, imageForArticleID: String) -> IconImage?
}
final class ArticleIconSchemeHandler: NSObject, WKURLSchemeHandler {
private weak var delegate: ArticleIconSchemeHandlerDelegate?
private static let headerFields = ["Cache-Control": "no-cache"]
init(delegate: ArticleIconSchemeHandlerDelegate) {
self.delegate = delegate
}
// WKURLSchemeHandler is @MainActor, so this is @MainActor.
func webView(_ webView: WKWebView, start urlSchemeTask: WKURLSchemeTask) {
guard let url = urlSchemeTask.request.url,
let components = URLComponents(url: url, resolvingAgainstBaseURL: false) else {
assertionFailure("Expected URL and components in ArticleIconSchemeHandler.")
urlSchemeTask.didFailWithError(URLError(.fileDoesNotExist))
return
}
let articleID = components.path
guard !articleID.isEmpty else {
assertionFailure("Expected non-empty articleID in ArticleIconSchemeHandler.")
urlSchemeTask.didFailWithError(URLError(.fileDoesNotExist))
return
}
guard let iconImage = delegate?.articleIconSchemeHandler(self, imageForArticleID: articleID) else {
// There may not be an image this is not a programming error.
urlSchemeTask.didFailWithError(URLError(.fileDoesNotExist))
return
}
let iconView = IconView(frame: CGRect(x: 0, y: 0, width: 48, height: 48))
iconView.iconImage = iconImage
let renderedImage = iconView.asImage()
guard let data = renderedImage.dataRepresentation() else {
assertionFailure("Expected non-empty image data ArticleIconSchemeHandler.")
urlSchemeTask.didFailWithError(URLError(.fileDoesNotExist))
return
}
guard let response = HTTPURLResponse(url: url, statusCode: 200, httpVersion: nil, headerFields: Self.headerFields) else {
assertionFailure("Expected to create HTTPURLResponse but failed.")
urlSchemeTask.didFailWithError(URLError(.unknown))
return
}
urlSchemeTask.didReceive(response)
urlSchemeTask.didReceive(data)
urlSchemeTask.didFinish()
}
func webView(_ webView: WKWebView, stop urlSchemeTask: WKURLSchemeTask) {
urlSchemeTask.didFailWithError(URLError(.unknown))
}
}
// TODO: The above code is re-rendering images multiple times, which is not good
// for performance. Fixing this will probably require some refactoring
// of the entire system for images, so that the code gets some kind of identifier
// probably a URL  so that it has a key for a cache, so it doesnt have to
// re-render images.

View File

@ -1,60 +0,0 @@
//
// ArticleIconSchemeHandler.swift
// NetNewsWire-iOS
//
// Created by Maurice Parker on 1/27/20.
// Copyright © 2020 Ranchero Software. All rights reserved.
//
import Foundation
import WebKit
import Articles
class ArticleIconSchemeHandler: NSObject, WKURLSchemeHandler {
weak var coordinator: SceneCoordinator?
init(coordinator: SceneCoordinator) {
self.coordinator = coordinator
}
func webView(_ webView: WKWebView, start urlSchemeTask: WKURLSchemeTask) {
guard let url = urlSchemeTask.request.url, let coordinator = coordinator else {
urlSchemeTask.didFailWithError(URLError(.fileDoesNotExist))
return
}
guard let components = URLComponents(url: url, resolvingAgainstBaseURL: false) else {
return
}
let articleID = components.path
guard let iconImage = coordinator.articleFor(articleID)?.iconImage() else {
urlSchemeTask.didFailWithError(URLError(.fileDoesNotExist))
return
}
let iconView = IconView(frame: CGRect(x: 0, y: 0, width: 48, height: 48))
iconView.iconImage = iconImage
let renderedImage = iconView.asImage()
guard let data = renderedImage.dataRepresentation() else {
urlSchemeTask.didFailWithError(URLError(.fileDoesNotExist))
return
}
let headerFields = ["Cache-Control": "no-cache"]
if let response = HTTPURLResponse(url: url, statusCode: 200, httpVersion: nil, headerFields: headerFields) {
urlSchemeTask.didReceive(response)
urlSchemeTask.didReceive(data)
urlSchemeTask.didFinish()
}
}
func webView(_ webView: WKWebView, stop urlSchemeTask: WKURLSchemeTask) {
urlSchemeTask.didFailWithError(URLError(.unknown))
}
}

View File

@ -18,8 +18,8 @@ protocol WebViewControllerDelegate: AnyObject {
func webViewController(_: WebViewController, articleExtractorButtonStateDidUpdate: ArticleExtractorButtonState)
}
class WebViewController: UIViewController {
final class WebViewController: UIViewController {
private struct MessageName {
static let imageWasClicked = "imageWasClicked"
static let imageWasShown = "imageWasShown"
@ -39,7 +39,7 @@ class WebViewController: UIViewController {
private var isFullScreenAvailable: Bool {
return AppDefaults.shared.articleFullscreenAvailable && traitCollection.userInterfaceIdiom == .phone && coordinator.isRootSplitCollapsed
}
private lazy var articleIconSchemeHandler = ArticleIconSchemeHandler(coordinator: coordinator);
private lazy var articleIconSchemeHandler = ArticleIconSchemeHandler(delegate: self)
private lazy var transition = ImageTransition(controller: self)
private var clickedImageCompletion: (() -> Void)?
@ -279,6 +279,25 @@ class WebViewController: UIViewController {
}
}
// MARK: - ArticleIconSchemeHandlerDelegate
extension WebViewController: ArticleIconSchemeHandlerDelegate {
func articleIconSchemeHandler(_: ArticleIconSchemeHandler, imageForArticleID articleID: String) -> IconImage? {
guard let article else {
assertionFailure("Did not expect request for article image when there is no current article.")
return nil
}
guard articleID == article.articleID else {
assertionFailure("Expected articleID to match current articleID.")
return nil
}
return article.iconImage() // May be nil  not a programming error
}
}
// MARK: ArticleExtractorDelegate
extension WebViewController: ArticleExtractorDelegate {
@ -300,7 +319,6 @@ extension WebViewController: ArticleExtractorDelegate {
articleExtractorButtonState = .on
}
}
}
// MARK: UIContextMenuInteractionDelegate