import UIKit import WebKit import SafariServices import Turbo final class SceneDelegate: UIResponder { private static var sharedProcessPool = WKProcessPool() var window: UIWindow? private let rootURL = <%= name %>.homeURL private var navigationController: TurboNavigationController! // MARK: - Setup private func configureRootViewController() { guard let window = window else { fatalError() } navigationController = window.rootViewController as? TurboNavigationController navigationController.session = session navigationController.modalSession = modalSession } // MARK: - Authentication private func promptForAuthentication() { let authURL = <%= name %>.signInURL let properties = pathConfiguration.properties(for: authURL) navigationController.route(url: authURL, options: VisitOptions(), properties: properties) } // MARK: - Sessions private lazy var session = makeSession() private lazy var modalSession = makeSession() private func makeSession() -> Session { let configuration = WKWebViewConfiguration() configuration.applicationNameForUserAgent = "Turbo Native iOS" configuration.processPool = Self.sharedProcessPool let webView = WKWebView(frame: .zero, configuration: configuration) webView.uiDelegate = self let session = Session(webView: webView) session.delegate = self session.pathConfiguration = pathConfiguration return session } // MARK: - Path Configuration private lazy var pathConfiguration = PathConfiguration(sources: [ .file(Bundle.main.url(forResource: "path-configuration", withExtension: "json")!), ]) } extension SceneDelegate: UIWindowSceneDelegate { func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { guard let _ = scene as? UIWindowScene else { return } configureRootViewController() navigationController.route(url: rootURL, options: VisitOptions(action: .replace), properties: [:]) } } extension SceneDelegate: SessionDelegate { func session(_ session: Session, didProposeVisit proposal: VisitProposal) { navigationController.route(url: proposal.url, options: proposal.options, properties: proposal.properties) } func session(_ session: Session, didFailRequestForVisitable visitable: Visitable, error: Error) { if let turboError = error as? TurboError, case let .http(statusCode) = turboError, statusCode == 401 { promptForAuthentication() } else if let errorPresenter = visitable as? ErrorPresenter { errorPresenter.presentError(error) { [weak self] in self?.session.reload() } } else { assertionFailure("Visit failed!") } } // When a form submission completes in the modal session, we need to // manually clear the snapshot cache in the default session, since we // don't want potentially stale cached snapshots to be used func sessionDidFinishFormSubmission(_ session: Session) { if (session == modalSession) { self.session.clearSnapshotCache() } } func sessionDidLoadWebView(_ session: Session) { session.webView.navigationDelegate = self } func sessionWebViewProcessDidTerminate(_ session: Session) { session.reload() } } extension SceneDelegate: WKNavigationDelegate { func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) { if navigationAction.navigationType == .linkActivated { // Any link that's not on the same domain as the Turbo root url will go through here // Other links on the domain, but that have an extension that is non-html will also go here // You can decide how to handle those, by default if you're not the navigationDelegate // the Session will open them in the default browser let url = navigationAction.request.url! // For this demo, we'll load files from our domain in a SafariViewController so you // don't need to leave the app. You might expand this in your app // to open all audio/video/images in a native media viewer if url.host == rootURL.host, !url.pathExtension.isEmpty { let safariViewController = SFSafariViewController(url: url) navigationController.present(safariViewController, animated: true) } else { UIApplication.shared.open(url) } decisionHandler(.cancel) } else { decisionHandler(.allow) } } } extension SceneDelegate: WKUIDelegate { func webView(_ webView: WKWebView, runJavaScriptConfirmPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping (Bool) -> Void) { let confirm = UIAlertController(title: nil, message: message, preferredStyle: .alert) confirm.addAction(UIAlertAction(title: "Cancel", style: .cancel) { _ in completionHandler(false) }) confirm.addAction(UIAlertAction(title: "OK", style: .default) { _ in completionHandler(true) }) // JavaScript alerts in Turbo Native navigationController.present(confirm, animated: true) } }