spb/prisme Public MIT
Navigateur iOS intelligent — chaque page comprise localement avant d'être affichée. SwiftUI · WebKit · Foundation Models, 100% on-device.
Swift 96.7%
JavaScript 3.3%
1//2// BrowserWebView.swift3// Prisme4//5// Author: Simon-Pierre Boucher <contact@spboucher.ai>6//78import SwiftUI9import WebKit1011/// The single `UIViewRepresentable` wrapping `WKWebView` (CLAUDE.md §9).12/// One instance renders the active tab; use `.id(tab.id)` so switching tabs13/// tears down and rebuilds the attachment, returning the view to the pool.14struct BrowserWebView: UIViewRepresentable {15 let tab: Tab16 let pool: WebViewPool1718 func makeCoordinator() -> Coordinator {19 Coordinator(tab: tab, pool: pool)20 }2122 func makeUIView(context: Context) -> WKWebView {23 let webView = pool.checkout(for: tab.containerID)24 context.coordinator.attach(to: webView)2526 if let state = tab.interactionState {27 // Restores history, scroll position and form state.28 webView.interactionState = state29 } else if let url = tab.initialURL {30 webView.load(URLRequest(url: url))31 }32 return webView33 }3435 func updateUIView(_ webView: WKWebView, context: Context) {}3637 static func dismantleUIView(_ webView: WKWebView, coordinator: Coordinator) {38 coordinator.detach(from: webView)39 }4041 // MARK: - Coordinator4243 @MainActor44 final class Coordinator: NSObject, WKNavigationDelegate, WKUIDelegate {45 private let tab: Tab46 private let pool: WebViewPool47 private var observations: [NSKeyValueObservation] = []4849 init(tab: Tab, pool: WebViewPool) {50 self.tab = tab51 self.pool = pool52 }5354 func attach(to webView: WKWebView) {55 webView.navigationDelegate = self56 webView.uiDelegate = self57 tab.page.webView = webView58 tab.page.sync(from: webView)59 installRefreshControl(on: webView)60 observe(webView)61 if let prismeWebView = webView as? PrismeWebView {62 let page = tab.page63 prismeWebView.onSaveExcerpt = { page.onExcerptRequested?() }64 }65 }6667 func detach(from webView: WKWebView) {68 observations.removeAll()69 (webView as? PrismeWebView)?.onSaveExcerpt = nil70 tab.interactionState = webView.interactionState71 tab.page.webView = nil72 tab.page.resetTransientState()73 webView.navigationDelegate = nil74 webView.uiDelegate = nil75 webView.scrollView.refreshControl = nil76 pool.checkin(webView, containerID: tab.containerID)77 }7879 // MARK: State observation8081 private func observe(_ webView: WKWebView) {82 // WKWebView KVO fires on the main thread; assumeIsolated is safe.83 let page = tab.page84 observations = [85 webView.observe(\.title) { wv, _ in86 MainActor.assumeIsolated { page.sync(from: wv) }87 },88 webView.observe(\.url) { wv, _ in89 MainActor.assumeIsolated { page.sync(from: wv) }90 },91 webView.observe(\.estimatedProgress) { wv, _ in92 MainActor.assumeIsolated { page.sync(from: wv) }93 },94 webView.observe(\.isLoading) { wv, _ in95 MainActor.assumeIsolated { page.sync(from: wv) }96 },97 webView.observe(\.canGoBack) { wv, _ in98 MainActor.assumeIsolated { page.sync(from: wv) }99 },100 webView.observe(\.canGoForward) { wv, _ in101 MainActor.assumeIsolated { page.sync(from: wv) }102 },103 ]104 }105106 // MARK: Pull to refresh107108 private func installRefreshControl(on webView: WKWebView) {109 let control = UIRefreshControl()110 control.addTarget(self, action: #selector(handleRefresh), for: .valueChanged)111 webView.scrollView.refreshControl = control112 }113114 @objc private func handleRefresh() {115 tab.page.reload()116 }117118 // MARK: WKNavigationDelegate119120 func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {121 webView.scrollView.refreshControl?.endRefreshing()122 tab.page.sync(from: webView)123 if let url = webView.url {124 tab.page.onDidFinishLoad?(url)125 }126 }127128 func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) {129 webView.scrollView.refreshControl?.endRefreshing()130 }131132 func webView(133 _ webView: WKWebView,134 didFailProvisionalNavigation navigation: WKNavigation!,135 withError error: Error136 ) {137 webView.scrollView.refreshControl?.endRefreshing()138 }139140 // MARK: WKUIDelegate141142 func webView(143 _ webView: WKWebView,144 createWebViewWith configuration: WKWebViewConfiguration,145 for navigationAction: WKNavigationAction,146 windowFeatures: WKWindowFeatures147 ) -> WKWebView? {148 // target=_blank: load in place for now; real popup-to-tab149 // routing comes with tab groups.150 if navigationAction.targetFrame == nil {151 webView.load(navigationAction.request)152 }153 return nil154 }155 }156}157