From 5ac3b5356296508907ec220db4efcd6b6bbee593 Mon Sep 17 00:00:00 2001 From: Joe Masilotti Date: Sun, 15 Oct 2023 06:26:21 -0700 Subject: [PATCH 1/4] Expose current navigation controller Helpful when completely custom navigation is required, like presenting a modal from: ``` WKUIDelegate.webView(_:runJavaScriptConfirmPanelWithMessage:initiatedByFrame:completionHandler:) ``` --- Sources/TurboNavigator.swift | 9 ++++++--- Tests/TurboNavigatorTests.swift | 16 ++++++++++++++++ 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/Sources/TurboNavigator.swift b/Sources/TurboNavigator.swift index 4e90bcc..e53669f 100644 --- a/Sources/TurboNavigator.swift +++ b/Sources/TurboNavigator.swift @@ -51,9 +51,9 @@ public class TurboNavigator { self.delegate = delegate } - public var rootViewController: UIViewController { navigationController } - public let navigationController: UINavigationController - public let modalNavigationController: UINavigationController + public var currentNavigationController: UINavigationController { + navigationController.presentedViewController != nil ? modalNavigationController : navigationController + } public func route(_ url: URL) { let options = VisitOptions(action: .advance, response: nil) @@ -89,6 +89,9 @@ public class TurboNavigator { // MARK: Internal + let navigationController: UINavigationController + let modalNavigationController: UINavigationController + let session: Session let modalSession: Session diff --git a/Tests/TurboNavigatorTests.swift b/Tests/TurboNavigatorTests.swift index 06e0051..49b3de7 100644 --- a/Tests/TurboNavigatorTests.swift +++ b/Tests/TurboNavigatorTests.swift @@ -245,6 +245,22 @@ final class TurboNavigatorTests: XCTestCase { XCTAssertNotEqual(navigator.session.activeVisitable?.visitableURL, proposal.url) } + func test_currentNavigationController_startsAsRoot() { + XCTAssertIdentical(navigator.currentNavigationController, navigator.navigationController) + } + + func test_currentNavigationController_modalPresented_isModal() { + navigator.route(VisitProposal(path: "/one")) + navigator.route(VisitProposal(path: "/two", context: .modal)) + XCTAssertIdentical(navigator.currentNavigationController, navigator.modalNavigationController) + } + + func test_currentNavigationController_mainNavigation_isRoot() { + navigator.route(VisitProposal(path: "/one", context: .modal)) + navigator.route(VisitProposal(path: "/two")) + XCTAssertIdentical(navigator.currentNavigationController, navigator.navigationController) + } + // MARK: Private private enum Context { From f93f6a7ec15ab8e5d389577ab3c572088c256bcc Mon Sep 17 00:00:00 2001 From: Joe Masilotti Date: Sun, 15 Oct 2023 07:13:13 -0700 Subject: [PATCH 2/4] Handle alert() and confirm() by default with option to override. --- Demo/Demo/SceneDelegate.swift | 2 +- .../controllers/alerts_controller.js | 14 +++++++++++ .../app/javascript/controllers/index.js | 3 +++ .../app/views/layouts/application.html.erb | 2 +- .../app/views/navigations/_item.html.erb | 2 +- .../app/views/navigations/show.html.erb | 18 ++++++++++++- Demo/Server/app/views/resources/show.html.erb | 2 +- Sources/TurboNavigationDelegate.swift | 25 +++++++++++++++++++ Sources/TurboNavigator.swift | 19 +++++++++++++- 9 files changed, 81 insertions(+), 6 deletions(-) create mode 100644 Demo/Server/app/javascript/controllers/alerts_controller.js diff --git a/Demo/Demo/SceneDelegate.swift b/Demo/Demo/SceneDelegate.swift index bed164e..3144dd6 100644 --- a/Demo/Demo/SceneDelegate.swift +++ b/Demo/Demo/SceneDelegate.swift @@ -18,7 +18,7 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { self.window = UIWindow(windowScene: windowScene) self.window?.makeKeyAndVisible() - self.window?.rootViewController = self.turboNavigator.rootViewController + self.window?.rootViewController = self.turboNavigator.currentNavigationController self.turboNavigator.route(baseURL) } } diff --git a/Demo/Server/app/javascript/controllers/alerts_controller.js b/Demo/Server/app/javascript/controllers/alerts_controller.js new file mode 100644 index 0000000..6e5ddcf --- /dev/null +++ b/Demo/Server/app/javascript/controllers/alerts_controller.js @@ -0,0 +1,14 @@ +import { Controller } from "@hotwired/stimulus" + +export default class extends Controller { + showAlert(event) { + event.preventDefault() + alert(event.currentTarget.dataset["title"]) + } + + showConfirm(event) { + event.preventDefault() + const result = confirm(event.currentTarget.dataset["title"]) + alert(`You ${result ? "confirmed" : "cancelled"} the dialog.`) + } +} diff --git a/Demo/Server/app/javascript/controllers/index.js b/Demo/Server/app/javascript/controllers/index.js index 373c3ed..9a5ff8e 100644 --- a/Demo/Server/app/javascript/controllers/index.js +++ b/Demo/Server/app/javascript/controllers/index.js @@ -3,3 +3,6 @@ // ./bin/rails generate stimulus controllerName import { application } from "./application" + +import AlertsController from "./alerts_controller" +application.register("alerts", AlertsController) diff --git a/Demo/Server/app/views/layouts/application.html.erb b/Demo/Server/app/views/layouts/application.html.erb index 88d1b84..04eb732 100644 --- a/Demo/Server/app/views/layouts/application.html.erb +++ b/Demo/Server/app/views/layouts/application.html.erb @@ -12,7 +12,7 @@ <%= hotwire_livereload_tags if Rails.env.development? %> - "> + "> <%= render "shared/navbar" %>
<%= render "shared/flash" %> diff --git a/Demo/Server/app/views/navigations/_item.html.erb b/Demo/Server/app/views/navigations/_item.html.erb index 6dcf9df..cb78ca8 100644 --- a/Demo/Server/app/views/navigations/_item.html.erb +++ b/Demo/Server/app/views/navigations/_item.html.erb @@ -5,7 +5,7 @@

<%= name %>

-

<%= description %>

+

<%= local_assigns[:description] || yield %>

diff --git a/Demo/Server/app/views/navigations/show.html.erb b/Demo/Server/app/views/navigations/show.html.erb index cd02889..44d4f6f 100644 --- a/Demo/Server/app/views/navigations/show.html.erb +++ b/Demo/Server/app/views/navigations/show.html.erb @@ -3,7 +3,7 @@

This screen was pushed onto the navigation stack.

This is the default behavior, no custom options are required.

-
+
<%= render "navigations/item", path: second_navigation_path, icon: "bi-arrow-right", @@ -34,4 +34,20 @@ icon: "bi-bug", name: "Error handling", description: "Visit a page that does not exist (404)." %> + + <%= render "navigations/item", + path: "#", + icon: "bi-exclamation-circle", + name: "JavaScript alert dialog", + data: {action: "alerts#showAlert", title: "A JavaScript alert."} do %> + Present a JavaScript alert(). + <% end %> + + <%= render "navigations/item", + path: "#", + icon: "bi-question-circle", + name: "JavaScript confirm dialog", + data: {action: "alerts#showConfirm", title: "Are you sure?"} do %> + Present a JavaScript confirm(), like via data-turbo-confirm. + <% end %>
diff --git a/Demo/Server/app/views/resources/show.html.erb b/Demo/Server/app/views/resources/show.html.erb index 047438b..0cdfbf4 100644 --- a/Demo/Server/app/views/resources/show.html.erb +++ b/Demo/Server/app/views/resources/show.html.erb @@ -5,5 +5,5 @@
<%= link_to "Edit resource", edit_resource_path(@resource), class: "btn btn-outline-secondary" %> - <%= link_to "Delete resource", resource_path(@resource), data: {turbo_method: :delete}, class: "btn btn-outline-danger" %> + <%= link_to "Delete resource", resource_path(@resource), data: {turbo_method: :delete, turbo_confirm: "Delete this resource?"}, class: "btn btn-outline-danger" %>
diff --git a/Sources/TurboNavigationDelegate.swift b/Sources/TurboNavigationDelegate.swift index dd5c6a5..29f10ce 100644 --- a/Sources/TurboNavigationDelegate.swift +++ b/Sources/TurboNavigationDelegate.swift @@ -34,6 +34,12 @@ public protocol TurboNavigationDelegate: AnyObject { /// Optional. Useful for interacting with the web view after the page loads. func sessionDidFinishRequest(_ session: Session) + + /// Optional. Override to customize the behavior when a JavaScript `alert()` dialog is shown. + func controller(_ controller: UIViewController, runJavaScriptAlertPanelWithMessage message: String, completionHandler: @escaping () -> Void) + + /// Optional. Override to customize the behavior when a JavaScript `confirm()` dialog is shown. + func controller(_ controller: UIViewController, runJavaScriptConfirmPanelWithMessage message: String, completionHandler: @escaping (Bool) -> Void) } public extension TurboNavigationDelegate { @@ -63,4 +69,23 @@ public extension TurboNavigationDelegate { func sessionDidFinishRequest(_ session: Session) {} func sessionDidLoadWebView(_ session: Session) {} + + func controller(_ controller: UIViewController, runJavaScriptAlertPanelWithMessage message: String, completionHandler: @escaping () -> Void) { + let alert = UIAlertController(title: message, message: nil, preferredStyle: .alert) + alert.addAction(UIAlertAction(title: "Close", style: .default) { _ in + completionHandler() + }) + controller.present(alert, animated: true) + } + + func controller(_ controller: UIViewController, runJavaScriptConfirmPanelWithMessage message: String, completionHandler: @escaping (Bool) -> Void) { + let alert = UIAlertController(title: message, message: nil, preferredStyle: .alert) + alert.addAction(UIAlertAction(title: "OK", style: .destructive) { _ in + completionHandler(true) + }) + alert.addAction(UIAlertAction(title: "Cancel", style: .cancel) { _ in + completionHandler(false) + }) + controller.present(alert, animated: true) + } } diff --git a/Sources/TurboNavigator.swift b/Sources/TurboNavigator.swift index e53669f..422c9f5 100644 --- a/Sources/TurboNavigator.swift +++ b/Sources/TurboNavigator.swift @@ -5,7 +5,7 @@ import WebKit /// Handles navigation to new URLs using the following rules: /// https://github.com/joemasilotti/TurboNavigator#handled-flows -public class TurboNavigator { +public class TurboNavigator: NSObject { /// Default initializer. /// - Parameters: /// - delegate: handle custom controller routing @@ -22,11 +22,14 @@ public class TurboNavigator { self.delegate = delegate self.navigationController = navigationController self.modalNavigationController = modalNavigationController + super.init() session.delegate = self modalSession.delegate = self session.pathConfiguration = pathConfiguration modalSession.pathConfiguration = pathConfiguration + session.webView.uiDelegate = self + modalSession.webView.uiDelegate = self } /// Provide `Turbo.Session` instances with preconfigured path configurations and delegates. @@ -278,3 +281,17 @@ extension TurboNavigator: SessionDelegate { delegate.sessionDidLoadWebView(session) } } + +// MARK: WKUIDelegate + +extension TurboNavigator: WKUIDelegate { + public func webView(_ webView: WKWebView, runJavaScriptAlertPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping () -> Void) { + guard let controller = currentNavigationController.visibleViewController else { return } + delegate.controller(controller, runJavaScriptAlertPanelWithMessage: message, completionHandler: completionHandler) + } + + public func webView(_ webView: WKWebView, runJavaScriptConfirmPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping (Bool) -> Void) { + guard let controller = currentNavigationController.visibleViewController else { return } + delegate.controller(controller, runJavaScriptConfirmPanelWithMessage: message, completionHandler: completionHandler) + } +} From 0aacb0154af56ba5f6a779ebef0cecf1ee18a1d8 Mon Sep 17 00:00:00 2001 From: Joe Masilotti Date: Wed, 1 Nov 2023 16:20:59 -0700 Subject: [PATCH 3/4] Pass all SessionDelegate callbacks through --- Demo/Demo/SceneDelegate.swift | 2 +- Sources/TurboNavigationDelegate.swift | 72 ++++------------ Sources/TurboNavigator.swift | 116 ++++++++++++++------------ 3 files changed, 78 insertions(+), 112 deletions(-) diff --git a/Demo/Demo/SceneDelegate.swift b/Demo/Demo/SceneDelegate.swift index 3144dd6..bed164e 100644 --- a/Demo/Demo/SceneDelegate.swift +++ b/Demo/Demo/SceneDelegate.swift @@ -18,7 +18,7 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { self.window = UIWindow(windowScene: windowScene) self.window?.makeKeyAndVisible() - self.window?.rootViewController = self.turboNavigator.currentNavigationController + self.window?.rootViewController = self.turboNavigator.rootViewController self.turboNavigator.route(baseURL) } } diff --git a/Sources/TurboNavigationDelegate.swift b/Sources/TurboNavigationDelegate.swift index 29f10ce..85cf4bc 100644 --- a/Sources/TurboNavigationDelegate.swift +++ b/Sources/TurboNavigationDelegate.swift @@ -5,10 +5,8 @@ import WebKit /// Implement to be notified when certain navigations are performed /// or to render a native controller instead of a Turbo web visit. public protocol TurboNavigationDelegate: AnyObject { - typealias RetryBlock = () -> Void - - /// Respond to authentication challenge presented by web servers behing basic auth. - func didReceiveAuthenticationChallenge(_ challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) + /// Optional. Override to provide a custom implementation of `WKUIDelegate`, like handling JavaScript alerts. + var webViewDelegate: WKUIDelegate? { get } /// Optional. Accept or reject a visit proposal. /// If accepted, you may provide a view controller to be displayed, otherwise a new `VisitableViewController` is displayed. @@ -19,73 +17,31 @@ public protocol TurboNavigationDelegate: AnyObject { /// - Returns: how to react to the visit proposal func handle(proposal: VisitProposal) -> ProposalResult - /// Optional. An error occurred loading the request, present it to the user. - /// Retry the request by executing the closure. - /// If not implemented, will present the error's localized description and a Retry button. - func visitableDidFailRequest(_ visitable: Visitable, error: Error, retry: @escaping RetryBlock) - - /// Optional. Implement to customize handling of external URLs. - /// If not implemented, will present `SFSafariViewController` as a modal and load the URL. - func openExternalURL(_ url: URL, from controller: UIViewController) + // MARK: - SessionDelegate overrides + /// Optional. Implement these functions when via an extension. - /// Optional. Implement to become the web view's navigation delegate after the initial cold boot visit is completed. - /// https://github.com/hotwired/turbo-ios/blob/main/Docs/Overview.md#becoming-the-web-views-navigation-delegate + func session(_ session: Session, didReceiveAuthenticationChallenge challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) func sessionDidLoadWebView(_ session: Session) - /// Optional. Useful for interacting with the web view after the page loads. + func sessionDidStartRequest(_ session: Session) func sessionDidFinishRequest(_ session: Session) - - /// Optional. Override to customize the behavior when a JavaScript `alert()` dialog is shown. - func controller(_ controller: UIViewController, runJavaScriptAlertPanelWithMessage message: String, completionHandler: @escaping () -> Void) - - /// Optional. Override to customize the behavior when a JavaScript `confirm()` dialog is shown. - func controller(_ controller: UIViewController, runJavaScriptConfirmPanelWithMessage message: String, completionHandler: @escaping (Bool) -> Void) + func sessionDidStartFormSubmission(_ session: Session) } public extension TurboNavigationDelegate { - func handle(proposal: VisitProposal) -> ProposalResult { .accept } + var webViewDelegate: WKUIDelegate? { nil } - func visitableDidFailRequest(_ visitable: Visitable, error: Error, retry: @escaping RetryBlock) { - if let errorPresenter = visitable as? ErrorPresenter { - errorPresenter.presentError(error) { - retry() - } - } - } + func handle(proposal: VisitProposal) -> ProposalResult { .accept } - func openExternalURL(_ url: URL, from controller: UIViewController) { - let safariViewController = SFSafariViewController(url: url) - safariViewController.modalPresentationStyle = .pageSheet - if #available(iOS 15.0, *) { - safariViewController.preferredControlTintColor = .tintColor - } - controller.present(safariViewController, animated: true) - } + // MARK: - SessionDelegate - func didReceiveAuthenticationChallenge(_ challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) { + func session(_ session: Session, didReceiveAuthenticationChallenge challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) { completionHandler(.performDefaultHandling, nil) } - func sessionDidFinishRequest(_ session: Session) {} - func sessionDidLoadWebView(_ session: Session) {} - func controller(_ controller: UIViewController, runJavaScriptAlertPanelWithMessage message: String, completionHandler: @escaping () -> Void) { - let alert = UIAlertController(title: message, message: nil, preferredStyle: .alert) - alert.addAction(UIAlertAction(title: "Close", style: .default) { _ in - completionHandler() - }) - controller.present(alert, animated: true) - } - - func controller(_ controller: UIViewController, runJavaScriptConfirmPanelWithMessage message: String, completionHandler: @escaping (Bool) -> Void) { - let alert = UIAlertController(title: message, message: nil, preferredStyle: .alert) - alert.addAction(UIAlertAction(title: "OK", style: .destructive) { _ in - completionHandler(true) - }) - alert.addAction(UIAlertAction(title: "Cancel", style: .cancel) { _ in - completionHandler(false) - }) - controller.present(alert, animated: true) - } + func sessionDidStartRequest(_ session: Session) {} + func sessionDidFinishRequest(_ session: Session) {} + func sessionDidStartFormSubmission(_ session: Session) {} } diff --git a/Sources/TurboNavigator.swift b/Sources/TurboNavigator.swift index 422c9f5..aaed176 100644 --- a/Sources/TurboNavigator.swift +++ b/Sources/TurboNavigator.swift @@ -8,56 +8,32 @@ import WebKit public class TurboNavigator: NSObject { /// Default initializer. /// - Parameters: - /// - delegate: handle custom controller routing - /// - pathConfiguration: assigned to internal `Session` instances for custom configuration - /// - navigationController: optional: override the main navigation stack - /// - modalNavigationController: optional: override the modal navigation stack - public init(delegate: TurboNavigationDelegate, - pathConfiguration: PathConfiguration? = nil, - navigationController: UINavigationController = UINavigationController(), - modalNavigationController: UINavigationController = UINavigationController()) + /// - delegate: Handle custom controller routing. + /// - pathConfiguration: Optional. Remotely configure settings and path rules. + /// - navigationController: Optional. Override the main navigation stack. + /// - modalNavigationController: Optional. Override the modal navigation stack. + public init( + delegate: TurboNavigationDelegate, + pathConfiguration: PathConfiguration? = nil, + navigationController: UINavigationController = UINavigationController(), + modalNavigationController: UINavigationController = UINavigationController()) { - self.session = Session(webView: TurboConfig.shared.makeWebView()) - self.modalSession = Session(webView: TurboConfig.shared.makeWebView()) self.delegate = delegate + self.pathConfiguration = pathConfiguration self.navigationController = navigationController self.modalNavigationController = modalNavigationController super.init() - - session.delegate = self - modalSession.delegate = self - session.pathConfiguration = pathConfiguration - modalSession.pathConfiguration = pathConfiguration - session.webView.uiDelegate = self - modalSession.webView.uiDelegate = self } - /// Provide `Turbo.Session` instances with preconfigured path configurations and delegates. - /// Note that TurboNavigationDelegate.controller(_:forProposal:) will no longer be called. - /// - Parameters: - /// - preconfiguredMainSession: a session whose delegate is not `TurboNavigator` - /// - preconfiguredModalSession: a session whose delegate is not `TurboNavigator` - /// - delegate: handle non-routing behavior, like custom error handling - /// - navigationController: optional: override the main navigation stack - /// - modalNavigationController: optional: override the modal navigation stack - public init(preconfiguredMainSession: Turbo.Session, - preconfiguredModalSession: Turbo.Session, - delegate: TurboNavigationDelegate, - navigationController: UINavigationController = UINavigationController(), - modalNavigationController: UINavigationController = UINavigationController()) - { - self.session = preconfiguredMainSession - self.modalSession = preconfiguredModalSession - self.navigationController = navigationController - self.modalNavigationController = modalNavigationController - - self.delegate = delegate - } + /// Set this as the `rootViewController` of your application's `UIWindow`. + public var rootViewController: UIViewController { navigationController } + /// `navigationController` or `modalNavigationController`, whichever is being shown. public var currentNavigationController: UINavigationController { navigationController.presentedViewController != nil ? modalNavigationController : navigationController } + /// Follows rules from `pathConfiguration` to route a `URL` to the stack. public func route(_ url: URL) { let options = VisitOptions(action: .advance, response: nil) let properties = session.pathConfiguration?.properties(for: url) ?? PathProperties() @@ -65,6 +41,7 @@ public class TurboNavigator: NSObject { route(proposal) } + /// Follows rules from `pathConfiguration` to route a `VisitProposal` to the stack. public func route(_ proposal: VisitProposal) { guard let controller = controller(for: proposal) else { return } @@ -95,8 +72,8 @@ public class TurboNavigator: NSObject { let navigationController: UINavigationController let modalNavigationController: UINavigationController - let session: Session - let modalSession: Session + lazy var session = makeSession() + lazy var modalSession = makeSession() // MARK: Private @@ -106,6 +83,15 @@ public class TurboNavigator: NSObject { } private unowned let delegate: TurboNavigationDelegate + private let pathConfiguration: PathConfiguration? + + private func makeSession() -> Session { + let session = Session(webView: TurboConfig.shared.makeWebView()) + session.delegate = self + session.pathConfiguration = pathConfiguration + session.webView.uiDelegate = delegate.webViewDelegate ?? self + return session + } private func controller(for proposal: VisitProposal) -> UIViewController? { switch delegate.handle(proposal: proposal) { @@ -254,13 +240,19 @@ extension TurboNavigator: SessionDelegate { } public func session(_ session: Session, openExternalURL url: URL) { - let controller = session === modalSession ? modalNavigationController : navigationController - delegate.openExternalURL(url, from: controller) + let safariViewController = SFSafariViewController(url: url) + safariViewController.modalPresentationStyle = .pageSheet + if #available(iOS 15.0, *) { + safariViewController.preferredControlTintColor = .tintColor + } + currentNavigationController.visibleViewController?.present(safariViewController, animated: true) } public func session(_ session: Session, didFailRequestForVisitable visitable: Visitable, error: Error) { - delegate.visitableDidFailRequest(visitable, error: error) { - session.reload() + if let errorPresenter = visitable as? ErrorPresenter { + errorPresenter.presentError(error) { + session.reload() + } } } @@ -268,30 +260,48 @@ extension TurboNavigator: SessionDelegate { session.reload() } + // MARK: SessionDelegate → TurboNavigationDelegate + public func session(_ session: Session, didReceiveAuthenticationChallenge challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) { - delegate.didReceiveAuthenticationChallenge(challenge, completionHandler: completionHandler) + delegate.session(session, didReceiveAuthenticationChallenge: challenge, completionHandler: completionHandler) + } + + public func sessionDidLoadWebView(_ session: Session) { + delegate.sessionDidLoadWebView(session) + } + + public func sessionDidStartRequest(_ session: Session) { + delegate.sessionDidStartRequest(session) } public func sessionDidFinishRequest(_ session: Session) { delegate.sessionDidFinishRequest(session) } - public func sessionDidLoadWebView(_ session: Session) { - session.webView.navigationDelegate = session - delegate.sessionDidLoadWebView(session) + public func sessionDidStartFormSubmission(_ session: Session) { + delegate.sessionDidStartFormSubmission(session) } } -// MARK: WKUIDelegate +// MARK: - WKUIDelegate extension TurboNavigator: WKUIDelegate { public func webView(_ webView: WKWebView, runJavaScriptAlertPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping () -> Void) { - guard let controller = currentNavigationController.visibleViewController else { return } - delegate.controller(controller, runJavaScriptAlertPanelWithMessage: message, completionHandler: completionHandler) + let alert = UIAlertController(title: message, message: nil, preferredStyle: .alert) + alert.addAction(UIAlertAction(title: "Close", style: .default) { _ in + completionHandler() + }) + currentNavigationController.visibleViewController?.present(alert, animated: true) } public func webView(_ webView: WKWebView, runJavaScriptConfirmPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping (Bool) -> Void) { - guard let controller = currentNavigationController.visibleViewController else { return } - delegate.controller(controller, runJavaScriptConfirmPanelWithMessage: message, completionHandler: completionHandler) + let alert = UIAlertController(title: message, message: nil, preferredStyle: .alert) + alert.addAction(UIAlertAction(title: "OK", style: .destructive) { _ in + completionHandler(true) + }) + alert.addAction(UIAlertAction(title: "Cancel", style: .cancel) { _ in + completionHandler(false) + }) + currentNavigationController.visibleViewController?.present(alert, animated: true) } } From d91d944a8d3de87d6607028fad0ceccd3b1d71b0 Mon Sep 17 00:00:00 2001 From: Joe Masilotti Date: Wed, 1 Nov 2023 19:10:56 -0700 Subject: [PATCH 4/4] Fix tests --- Tests/TurboNavigationDelegateTests.swift | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/Tests/TurboNavigationDelegateTests.swift b/Tests/TurboNavigationDelegateTests.swift index 65aa5f9..b9e5f90 100644 --- a/Tests/TurboNavigationDelegateTests.swift +++ b/Tests/TurboNavigationDelegateTests.swift @@ -12,16 +12,6 @@ final class TurboNavigationDelegateTests: XCTestCase { XCTAssertEqual(result, .accept) } - func test_openExternalURL_presentsSafariViewController() throws { - let url = URL(string: "https://example.com")! - let controller = TestableNavigationController() - - delegate.openExternalURL(url, from: controller) - - XCTAssert(controller.presentedViewController is SFSafariViewController) - XCTAssertEqual(controller.modalPresentationStyle, .pageSheet) - } - // MARK: Private private let delegate = DefaultDelegate()