import UIKit import WebKit import SafariServices import Turbo final class SceneDelegate: UIResponder { private static var sharedProcessPool = WKProcessPool() var window: UIWindow? private let baseURL = <%= name %>.baseURL private let rootURL1 = <%= name %>.homeURL1 private let rootURL2 = <%= name %>.homeURL2 private var tabBarController: UITabBarController! private lazy var navigationController1 = { tabBarController.viewControllers![0] as! TurboNavigationController }() private lazy var navigationController2 = { tabBarController.viewControllers![1] as! TurboNavigationController }() // MARK: - Setup private func configureRootViewController() { guard let window = window else { fatalError() } window.tintColor = UIColor(named: "Tint") tabBarController = (window.rootViewController as! UITabBarController) navigationController1.session = session1 navigationController1.modalSession = modalSession navigationController2.session = session2 navigationController2.modalSession = modalSession } private func navigationController() -> TurboNavigationController { tabBarController.selectedViewController as! TurboNavigationController } private func session() -> Session { navigationController().session } // MARK: - Sessions private lazy var session1 = makeSession() private lazy var session2 = 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() navigationController1.route(url: rootURL1, options: VisitOptions(action: .replace), properties: [:]) navigationController2.route(url: rootURL2, 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 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 == baseURL.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) } }