Created
September 30, 2025 03:42
-
-
Save leok7v/76eb2c6a5c5068aed42fb9df85df36f1 to your computer and use it in GitHub Desktop.
Swift.WebView polyfill
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import SwiftUI | |
| import WebKit | |
| import Foundation | |
| import SwiftUI | |
| //import Foundation | |
| #if os(iOS) | |
| import UIKit | |
| typealias OSWindow = UIWindow | |
| typealias OSView = UIView | |
| typealias OSHostingController = UIHostingController | |
| typealias OSViewRepresentable = UIViewRepresentable | |
| #elseif os(macOS) | |
| import AppKit | |
| typealias OSWindow = NSWindow | |
| typealias OSView = NSView | |
| typealias OSHostingController = NSHostingController | |
| typealias OSViewRepresentable = NSViewRepresentable | |
| #endif | |
| public enum UIWebViewAction: Equatable { | |
| case idle, | |
| load(URLRequest), | |
| loadHTML(String), | |
| reload, | |
| goBack, | |
| goForward, | |
| evaluateJS(String, (Result<Any?, Error>) -> Void) | |
| public static func == (lhs: UIWebViewAction, rhs: UIWebViewAction) -> Bool { | |
| if case .idle = lhs, case .idle = rhs { return true } | |
| if case let .load(l) = lhs, case let .load(r) = rhs { return l == r } | |
| if case let .loadHTML(l) = lhs, case let .loadHTML(r) = rhs { return l == r } | |
| if case .reload = lhs, case .reload = rhs { return true } | |
| if case .goBack = lhs, case .goBack = rhs { return true } | |
| if case .goForward = lhs, case .goForward = rhs { return true } | |
| if case let .evaluateJS(l, _) = lhs, case let .evaluateJS(r, _) = rhs { return l == r } | |
| return false | |
| } | |
| } | |
| public struct UIWebViewState: Equatable { | |
| public internal(set) var isLoading: Bool | |
| public internal(set) var pageURL: String? | |
| public internal(set) var pageTitle: String? | |
| public internal(set) var pageHTML: String? | |
| public internal(set) var error: Error? | |
| public internal(set) var canGoBack: Bool | |
| public internal(set) var canGoForward: Bool | |
| public static let empty = UIWebViewState(isLoading: false, pageURL: nil, | |
| pageTitle: nil, pageHTML: nil, error: nil, canGoBack: false, | |
| canGoForward: false) | |
| public static func == (lhs: UIWebViewState, rhs: UIWebViewState) -> Bool { | |
| lhs.isLoading == rhs.isLoading && | |
| lhs.pageURL == rhs.pageURL && | |
| lhs.pageTitle == rhs.pageTitle && | |
| lhs.pageHTML == rhs.pageHTML && | |
| lhs.error?.localizedDescription == rhs.error?.localizedDescription && | |
| lhs.canGoBack == rhs.canGoBack && | |
| lhs.canGoForward == rhs.canGoForward | |
| } | |
| } | |
| public class UIWebViewCoordinator: NSObject { | |
| private let webView: UIWebView | |
| var actionInProgress = false | |
| init(webView: UIWebView) { | |
| self.webView = webView | |
| } | |
| func setLoading(_ isLoading: Bool, canGoBack: Bool? = nil, canGoForward: Bool? = nil, error: Error? = nil) { | |
| var newState = webView.state | |
| newState.isLoading = isLoading | |
| if let canGoBack { newState.canGoBack = canGoBack } | |
| if let canGoForward { newState.canGoForward = canGoForward } | |
| if let error { newState.error = error } | |
| webView.state = newState | |
| webView.action = .idle | |
| actionInProgress = false | |
| } | |
| } | |
| extension UIWebViewCoordinator: WKNavigationDelegate { | |
| public func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) { | |
| setLoading(false, canGoBack: webView.canGoBack, canGoForward: webView.canGoForward) | |
| webView.evaluateJavaScript("document.title") { response, _ in | |
| if let title = response as? String { | |
| var newState = self.webView.state | |
| newState.pageTitle = title | |
| self.webView.state = newState | |
| } | |
| } | |
| webView.evaluateJavaScript("document.URL.toString()") { response, _ in | |
| if let url = response as? String { | |
| var newState = self.webView.state | |
| newState.pageURL = url | |
| self.webView.state = newState | |
| } | |
| } | |
| if self.webView.htmlInState { | |
| webView.evaluateJavaScript("document.documentElement.outerHTML.toString()") { response, _ in | |
| if let html = response as? String { | |
| var newState = self.webView.state | |
| newState.pageHTML = html | |
| self.webView.state = newState | |
| } | |
| } | |
| } | |
| if !self.webView.config.allowsTextSelection { | |
| webView.evaluateJavaScript("document.body.style.webkitUserSelect='none';") { _, _ in } | |
| } | |
| } | |
| public func webViewWebContentProcessDidTerminate(_ webView: WKWebView) { | |
| setLoading(false) | |
| } | |
| public func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) { | |
| setLoading(false, error: error) | |
| } | |
| public func webView(_ webView: WKWebView, didCommit navigation: WKNavigation!) { | |
| setLoading(true) | |
| } | |
| public func webView(_ webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!) { | |
| setLoading(true, canGoBack: webView.canGoBack, canGoForward: webView.canGoForward) | |
| } | |
| public func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) { | |
| if let host = navigationAction.request.url?.host, self.webView.restrictedPages?.contains(where: { host.contains($0) }) == true { | |
| decisionHandler(.cancel) | |
| setLoading(false) | |
| return | |
| } | |
| if let url = navigationAction.request.url, let scheme = url.scheme, let handler = self.webView.schemeHandlers[scheme] { | |
| handler(url) | |
| decisionHandler(.cancel) | |
| return | |
| } | |
| decisionHandler(.allow) | |
| } | |
| } | |
| extension UIWebViewCoordinator: WKUIDelegate { | |
| public func webView(_ webView: WKWebView, createWebViewWith configuration: WKWebViewConfiguration, for navigationAction: WKNavigationAction, windowFeatures: WKWindowFeatures) -> WKWebView? { | |
| if navigationAction.targetFrame == nil { | |
| webView.load(navigationAction.request) | |
| } | |
| return nil | |
| } | |
| } | |
| public struct UIWebViewConfig { | |
| public static let `default` = UIWebViewConfig() | |
| public let javaScriptEnabled: Bool | |
| public let allowsBackForwardNavigationGestures: Bool | |
| public let allowsInlineMediaPlayback: Bool | |
| public let mediaTypesRequiringUserActionForPlayback: WKAudiovisualMediaTypes | |
| public let isScrollEnabled: Bool | |
| public let isOpaque: Bool | |
| public let backgroundColor: Color | |
| public let scrollBounceBehavior: ScrollBounceBehavior | |
| public let scrollBounceAxes: Axis.Set | |
| public let allowsMagnification: Bool | |
| public let allowsTextSelection: Bool | |
| public let allowsLinkPreviews: Bool | |
| public let allowsElementFullscreen: Bool | |
| public init(javaScriptEnabled: Bool = true, | |
| allowsBackForwardNavigationGestures: Bool = true, | |
| allowsInlineMediaPlayback: Bool = true, | |
| mediaTypesRequiringUserActionForPlayback: WKAudiovisualMediaTypes = [], | |
| isScrollEnabled: Bool = true, | |
| isOpaque: Bool = true, | |
| backgroundColor: Color = .clear, | |
| scrollBounceBehavior: ScrollBounceBehavior = .automatic, | |
| scrollBounceAxes: Axis.Set = [.vertical], | |
| allowsMagnification: Bool = true, | |
| allowsTextSelection: Bool = true, | |
| allowsLinkPreviews: Bool = true, | |
| allowsElementFullscreen: Bool = true) { | |
| self.javaScriptEnabled = javaScriptEnabled | |
| self.allowsBackForwardNavigationGestures = allowsBackForwardNavigationGestures | |
| self.allowsInlineMediaPlayback = allowsInlineMediaPlayback | |
| self.mediaTypesRequiringUserActionForPlayback = mediaTypesRequiringUserActionForPlayback | |
| self.isScrollEnabled = isScrollEnabled | |
| self.isOpaque = isOpaque | |
| self.backgroundColor = backgroundColor | |
| self.scrollBounceBehavior = scrollBounceBehavior | |
| self.scrollBounceAxes = scrollBounceAxes | |
| self.allowsMagnification = allowsMagnification | |
| self.allowsTextSelection = allowsTextSelection | |
| self.allowsLinkPreviews = allowsLinkPreviews | |
| self.allowsElementFullscreen = allowsElementFullscreen | |
| } | |
| } | |
| #if os(macOS) | |
| public typealias ViewRepresentableContext = NSViewRepresentableContext<UIWebView> | |
| #endif | |
| public struct UIWebView: OSViewRepresentable { | |
| public let config: UIWebViewConfig | |
| @Binding public var action: UIWebViewAction | |
| @Binding public var state: UIWebViewState | |
| public let restrictedPages: [String]? | |
| public let htmlInState: Bool | |
| public let schemeHandlers: [String: (URL) -> Void] | |
| public init(config: UIWebViewConfig = .default, | |
| action: Binding<UIWebViewAction>, | |
| state: Binding<UIWebViewState>, | |
| restrictedPages: [String]? = nil, | |
| htmlInState: Bool = false, | |
| schemeHandlers: [String: (URL) -> Void] = [:]) { | |
| self.config = config | |
| _action = action | |
| _state = state | |
| self.restrictedPages = restrictedPages | |
| self.htmlInState = htmlInState | |
| self.schemeHandlers = schemeHandlers | |
| } | |
| #if os(iOS) | |
| public typealias UIViewType = WKWebView | |
| #else | |
| public typealias NSViewType = WKWebView | |
| #endif | |
| public typealias Coordinator = UIWebViewCoordinator | |
| public func makeCoordinator() -> Coordinator { | |
| UIWebViewCoordinator(webView: self) | |
| } | |
| #if os(iOS) | |
| public func makeUIView(context: UIViewRepresentableContext<UIWebView>) -> UIViewType { | |
| let preferences = WKPreferences() | |
| // preferences.javaScriptEnabled = config.javaScriptEnabled | |
| let configuration = WKWebViewConfiguration() | |
| configuration.preferences = preferences | |
| configuration.allowsInlineMediaPlayback = config.allowsInlineMediaPlayback | |
| configuration.mediaTypesRequiringUserActionForPlayback = config.mediaTypesRequiringUserActionForPlayback | |
| let webView = WKWebView(frame: .zero, configuration: configuration) | |
| webView.navigationDelegate = context.coordinator | |
| webView.uiDelegate = context.coordinator | |
| webView.allowsBackForwardNavigationGestures = config.allowsBackForwardNavigationGestures | |
| webView.scrollView.isScrollEnabled = config.isScrollEnabled | |
| webView.isOpaque = config.isOpaque | |
| webView.backgroundColor = UIColor(config.backgroundColor) | |
| /* TODO: | |
| webView.scrollView.bounces = config.scrollBounceBehavior == ScrollBounceBehavior.never | |
| webView.scrollView.alwaysBounceVertical = config.scrollBounceBehavior == ScrollBounceBehavior.always && config.scrollBounceAxes.contains(.vertical) | |
| webView.scrollView.alwaysBounceHorizontal = config.scrollBounceBehavior == ScrollBounceBehavior.always && config.scrollBounceAxes.contains(.horizontal) | |
| */ | |
| return webView | |
| } | |
| public func updateUIView(_ uiView: UIViewType, context: UIViewRepresentableContext<UIWebView>) { | |
| if action == .idle || context.coordinator.actionInProgress { return } | |
| context.coordinator.actionInProgress = true | |
| switch action { | |
| case .load(let request): | |
| uiView.load(request) | |
| case .loadHTML(let html): | |
| uiView.loadHTMLString(html, baseURL: nil) | |
| case .reload: | |
| uiView.reload() | |
| case .goBack: | |
| uiView.goBack() | |
| case .goForward: | |
| uiView.goForward() | |
| case let .evaluateJS(command, callback): | |
| uiView.evaluateJavaScript(command) { result, error in | |
| if let error { | |
| callback(.failure(error)) | |
| } else { | |
| callback(.success(result)) | |
| } | |
| self.action = .idle | |
| context.coordinator.actionInProgress = false | |
| } | |
| return | |
| case .idle: | |
| break | |
| } | |
| } | |
| #else | |
| public func makeNSView(context: ViewRepresentableContext) -> NSViewType { | |
| let preferences = WKPreferences() | |
| // preferences.javaScriptEnabled = config.javaScriptEnabled | |
| let configuration = WKWebViewConfiguration() | |
| configuration.preferences = preferences | |
| let webView = WKWebView(frame: .zero, configuration: configuration) | |
| webView.navigationDelegate = context.coordinator | |
| webView.uiDelegate = context.coordinator | |
| webView.allowsBackForwardNavigationGestures = config.allowsBackForwardNavigationGestures | |
| webView.allowsMagnification = config.allowsMagnification | |
| webView.setValue(config.isOpaque, forKey: "drawsBackground") | |
| // webView.isOpaque = config.isOpaque | |
| // webView.backgroundColor = NSColor(config.backgroundColor) | |
| return webView | |
| } | |
| public func updateNSView(_ nsView: NSViewType, context: ViewRepresentableContext) { | |
| if action == .idle || context.coordinator.actionInProgress { return } | |
| context.coordinator.actionInProgress = true | |
| switch action { | |
| case .load(let request): | |
| nsView.load(request) | |
| case .loadHTML(let html): | |
| nsView.loadHTMLString(html, baseURL: nil) | |
| case .reload: | |
| nsView.reload() | |
| case .goBack: | |
| nsView.goBack() | |
| case .goForward: | |
| nsView.goForward() | |
| case let .evaluateJS(command, callback): | |
| nsView.evaluateJavaScript(command) { result, error in | |
| if let error { | |
| callback(.failure(error)) | |
| } else { | |
| callback(.success(result)) | |
| } | |
| self.action = .idle | |
| context.coordinator.actionInProgress = false | |
| } | |
| return | |
| case .idle: | |
| break | |
| } | |
| DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { | |
| self.action = .idle | |
| context.coordinator.actionInProgress = false | |
| } | |
| } | |
| #endif | |
| } | |
| extension UIWebView { | |
| public func scrollBounceBehavior(_ behavior: ScrollBounceBehavior, axes: Axis.Set = [.vertical]) -> Self { | |
| UIWebView(config: UIWebViewConfig(javaScriptEnabled: config.javaScriptEnabled, | |
| allowsBackForwardNavigationGestures: config.allowsBackForwardNavigationGestures, | |
| allowsInlineMediaPlayback: config.allowsInlineMediaPlayback, | |
| mediaTypesRequiringUserActionForPlayback: config.mediaTypesRequiringUserActionForPlayback, | |
| isScrollEnabled: config.isScrollEnabled, | |
| isOpaque: config.isOpaque, | |
| backgroundColor: config.backgroundColor, | |
| scrollBounceBehavior: behavior, | |
| scrollBounceAxes: axes, | |
| allowsMagnification: config.allowsMagnification, | |
| allowsTextSelection: config.allowsTextSelection, | |
| allowsLinkPreviews: config.allowsLinkPreviews, | |
| allowsElementFullscreen: config.allowsElementFullscreen), | |
| action: $action, | |
| state: $state, | |
| restrictedPages: restrictedPages, | |
| htmlInState: htmlInState, | |
| schemeHandlers: schemeHandlers) | |
| } | |
| public func webViewBackForwardNavigationGestures(_ enabled: Bool) -> Self { | |
| UIWebView(config: UIWebViewConfig(javaScriptEnabled: config.javaScriptEnabled, | |
| allowsBackForwardNavigationGestures: enabled, | |
| allowsInlineMediaPlayback: config.allowsInlineMediaPlayback, | |
| mediaTypesRequiringUserActionForPlayback: config.mediaTypesRequiringUserActionForPlayback, | |
| isScrollEnabled: config.isScrollEnabled, | |
| isOpaque: config.isOpaque, | |
| backgroundColor: config.backgroundColor, | |
| scrollBounceBehavior: config.scrollBounceBehavior, | |
| scrollBounceAxes: config.scrollBounceAxes, | |
| allowsMagnification: config.allowsMagnification, | |
| allowsTextSelection: config.allowsTextSelection, | |
| allowsLinkPreviews: config.allowsLinkPreviews, | |
| allowsElementFullscreen: config.allowsElementFullscreen), | |
| action: $action, | |
| state: $state, | |
| restrictedPages: restrictedPages, | |
| htmlInState: htmlInState, | |
| schemeHandlers: schemeHandlers) | |
| } | |
| public func webViewMagnificationGestures(_ enabled: Bool) -> Self { | |
| UIWebView(config: UIWebViewConfig(javaScriptEnabled: config.javaScriptEnabled, | |
| allowsBackForwardNavigationGestures: config.allowsBackForwardNavigationGestures, | |
| allowsInlineMediaPlayback: config.allowsInlineMediaPlayback, | |
| mediaTypesRequiringUserActionForPlayback: config.mediaTypesRequiringUserActionForPlayback, | |
| isScrollEnabled: config.isScrollEnabled, | |
| isOpaque: config.isOpaque, | |
| backgroundColor: config.backgroundColor, | |
| scrollBounceBehavior: config.scrollBounceBehavior, | |
| scrollBounceAxes: config.scrollBounceAxes, | |
| allowsMagnification: enabled, | |
| allowsTextSelection: config.allowsTextSelection, | |
| allowsLinkPreviews: config.allowsLinkPreviews, | |
| allowsElementFullscreen: config.allowsElementFullscreen), | |
| action: $action, | |
| state: $state, | |
| restrictedPages: restrictedPages, | |
| htmlInState: htmlInState, | |
| schemeHandlers: schemeHandlers) | |
| } | |
| public func webViewTextSelection(_ enabled: Bool) -> Self { | |
| UIWebView(config: UIWebViewConfig(javaScriptEnabled: config.javaScriptEnabled, | |
| allowsBackForwardNavigationGestures: config.allowsBackForwardNavigationGestures, | |
| allowsInlineMediaPlayback: config.allowsInlineMediaPlayback, | |
| mediaTypesRequiringUserActionForPlayback: config.mediaTypesRequiringUserActionForPlayback, | |
| isScrollEnabled: config.isScrollEnabled, | |
| isOpaque: config.isOpaque, | |
| backgroundColor: config.backgroundColor, | |
| scrollBounceBehavior: config.scrollBounceBehavior, | |
| scrollBounceAxes: config.scrollBounceAxes, | |
| allowsMagnification: config.allowsMagnification, | |
| allowsTextSelection: enabled, | |
| allowsLinkPreviews: config.allowsLinkPreviews, | |
| allowsElementFullscreen: config.allowsElementFullscreen), | |
| action: $action, | |
| state: $state, | |
| restrictedPages: restrictedPages, | |
| htmlInState: htmlInState, | |
| schemeHandlers: schemeHandlers) | |
| } | |
| public func webViewLinkPreviews(_ enabled: Bool) -> Self { | |
| UIWebView(config: UIWebViewConfig(javaScriptEnabled: config.javaScriptEnabled, | |
| allowsBackForwardNavigationGestures: config.allowsBackForwardNavigationGestures, | |
| allowsInlineMediaPlayback: config.allowsInlineMediaPlayback, | |
| mediaTypesRequiringUserActionForPlayback: config.mediaTypesRequiringUserActionForPlayback, | |
| isScrollEnabled: config.isScrollEnabled, | |
| isOpaque: config.isOpaque, | |
| backgroundColor: config.backgroundColor, | |
| scrollBounceBehavior: config.scrollBounceBehavior, | |
| scrollBounceAxes: config.scrollBounceAxes, | |
| allowsMagnification: config.allowsMagnification, | |
| allowsTextSelection: config.allowsTextSelection, | |
| allowsLinkPreviews: enabled, | |
| allowsElementFullscreen: config.allowsElementFullscreen), | |
| action: $action, | |
| state: $state, | |
| restrictedPages: restrictedPages, | |
| htmlInState: htmlInState, | |
| schemeHandlers: schemeHandlers) | |
| } | |
| public func webViewElementFullscreenBehavior(_ enabled: Bool) -> Self { | |
| UIWebView(config: UIWebViewConfig(javaScriptEnabled: config.javaScriptEnabled, | |
| allowsBackForwardNavigationGestures: config.allowsBackForwardNavigationGestures, | |
| allowsInlineMediaPlayback: config.allowsInlineMediaPlayback, | |
| mediaTypesRequiringUserActionForPlayback: config.mediaTypesRequiringUserActionForPlayback, | |
| isScrollEnabled: config.isScrollEnabled, | |
| isOpaque: config.isOpaque, | |
| backgroundColor: config.backgroundColor, | |
| scrollBounceBehavior: config.scrollBounceBehavior, | |
| scrollBounceAxes: config.scrollBounceAxes, | |
| allowsMagnification: config.allowsMagnification, | |
| allowsTextSelection: config.allowsTextSelection, | |
| allowsLinkPreviews: config.allowsLinkPreviews, | |
| allowsElementFullscreen: enabled), | |
| action: $action, | |
| state: $state, | |
| restrictedPages: restrictedPages, | |
| htmlInState: htmlInState, | |
| schemeHandlers: schemeHandlers) | |
| } | |
| public func webViewContentBackground(_ color: Color) -> Self { | |
| UIWebView(config: UIWebViewConfig(javaScriptEnabled: config.javaScriptEnabled, | |
| allowsBackForwardNavigationGestures: config.allowsBackForwardNavigationGestures, | |
| allowsInlineMediaPlayback: config.allowsInlineMediaPlayback, | |
| mediaTypesRequiringUserActionForPlayback: config.mediaTypesRequiringUserActionForPlayback, | |
| isScrollEnabled: config.isScrollEnabled, | |
| isOpaque: config.isOpaque, | |
| backgroundColor: color, | |
| scrollBounceBehavior: config.scrollBounceBehavior, | |
| scrollBounceAxes: config.scrollBounceAxes, | |
| allowsMagnification: config.allowsMagnification, | |
| allowsTextSelection: config.allowsTextSelection, | |
| allowsLinkPreviews: config.allowsLinkPreviews, | |
| allowsElementFullscreen: config.allowsElementFullscreen), | |
| action: $action, | |
| state: $state, | |
| restrictedPages: restrictedPages, | |
| htmlInState: htmlInState, | |
| schemeHandlers: schemeHandlers) | |
| } | |
| } | |
| public struct UIWebPage { | |
| public let url: URL? | |
| public let html: String? | |
| public init(url: URL) { | |
| self.url = url | |
| self.html = nil | |
| } | |
| public init(html: String) { | |
| self.url = nil | |
| self.html = html | |
| } | |
| } | |
| extension UIWebView { | |
| public init(page: UIWebPage, config: UIWebViewConfig = .default, | |
| state: Binding<UIWebViewState>, | |
| restrictedPages: [String]? = nil, htmlInState: Bool = false, | |
| schemeHandlers: [String: (URL) -> Void] = [:]) { | |
| self.init(config: config, action: .constant(.idle), state: state, | |
| restrictedPages: restrictedPages, htmlInState: htmlInState, | |
| schemeHandlers: schemeHandlers) | |
| if let url = page.url { | |
| action = .load(URLRequest(url: url)) | |
| } else if let html = page.html { | |
| action = .loadHTML(html) | |
| } | |
| } | |
| } | |
| struct WebViewTest: View { | |
| @State private var action = UIWebViewAction.idle | |
| @State private var state = UIWebViewState.empty | |
| @State private var address = "https://www.google.com" | |
| var body: some View { | |
| VStack { | |
| titleView | |
| navigationToolbar | |
| errorView | |
| Divider() | |
| UIWebView(action: $action, | |
| state: $state, | |
| restrictedPages: ["apple.com"], | |
| htmlInState: true) | |
| // Text(state.pageHTML ?? "").lineLimit(nil) | |
| Spacer() | |
| } | |
| } | |
| private var titleView: some View { | |
| Text(String(format: "%@ - %@", state.pageTitle ?? "Load a page", state.pageURL ?? "No URL")) | |
| .font(.system(size: 24)) | |
| } | |
| private var navigationToolbar: some View { | |
| HStack(spacing: 10) { | |
| Button("Test HTML") { | |
| action = .loadHTML(""" | |
| <html><body> | |
| <b>Hello World!</b><br /> | |
| <a href="https://www.google.com" target="_blank">Go to google</a> | |
| </body></html> | |
| """) | |
| state.error = nil | |
| } | |
| TextField("Address", text: $address) | |
| if state.isLoading { | |
| if #available(iOS 14, macOS 11, *) { | |
| ProgressView() | |
| .progressViewStyle(CircularProgressViewStyle()) | |
| } else { | |
| Text("Loading") | |
| } | |
| } | |
| Spacer() | |
| Button("Go") { | |
| if let url = URL(string: address) { | |
| action = .load(URLRequest(url: url)) | |
| } | |
| } | |
| Button(action: { | |
| action = .reload | |
| }) { | |
| if #available(iOS 14, macOS 11, *) { | |
| Image(systemName: "arrow.counterclockwise") | |
| .imageScale(.large) | |
| } else { | |
| Text("Reload") | |
| } | |
| } | |
| if state.canGoBack { | |
| Button(action: { | |
| action = .goBack | |
| }) { | |
| if #available(iOS 14, macOS 11, *) { | |
| Image(systemName: "chevron.left") | |
| .imageScale(.large) | |
| } else { | |
| Text("<") | |
| } | |
| } | |
| } | |
| if state.canGoForward { | |
| Button(action: { | |
| action = .goForward | |
| }) { | |
| if #available(iOS 14, macOS 11, *) { | |
| Image(systemName: "chevron.right") | |
| .imageScale(.large) | |
| } else { | |
| Text(">") | |
| } | |
| } | |
| } | |
| }.padding() | |
| } | |
| private var errorView: some View { | |
| Group { | |
| if let error = state.error { | |
| Text(error.localizedDescription) | |
| .foregroundColor(.red) | |
| } | |
| } | |
| } | |
| } | |
| struct WebView_Previews: PreviewProvider { | |
| static var previews: some View { | |
| WebViewTest() | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import SwiftUI | |
| import WebKit | |
| import Foundation | |
| public enum UIWebViewAction : Equatable { | |
| case idle, load(URLRequest), loadHTML(String), reload, goBack, goForward, evaluateJS(String, (Result<Any?, Error>) -> Void) | |
| /// Returns a Boolean value indicating whether two values are equal. | |
| /// | |
| /// Equality is the inverse of inequality. For any values `a` and `b`, | |
| /// `a == b` implies that `a != b` is `false`. | |
| /// | |
| /// - Parameters: | |
| /// - lhs: A value to compare. | |
| /// - rhs: Another value to compare. | |
| public static func == (lhs: UIWebViewAction, rhs: UIWebViewAction) -> Bool | |
| } | |
| public struct UIWebViewState : Equatable { | |
| public internal(set) var isLoading: Bool { get } | |
| public internal(set) var pageURL: String? { get } | |
| public internal(set) var pageTitle: String? { get } | |
| public internal(set) var pageHTML: String? { get } | |
| public internal(set) var error: Error? { get } | |
| public internal(set) var canGoBack: Bool { get } | |
| public internal(set) var canGoForward: Bool { get } | |
| public static let empty: UIWebView.UIWebViewState | |
| /// Returns a Boolean value indicating whether two values are equal. | |
| /// | |
| /// Equality is the inverse of inequality. For any values `a` and `b`, | |
| /// `a == b` implies that `a != b` is `false`. | |
| /// | |
| /// - Parameters: | |
| /// - lhs: A value to compare. | |
| /// - rhs: Another value to compare. | |
| public static func == (lhs: UIWebViewState, rhs: UIWebViewState) -> Bool | |
| } | |
| public class UIWebViewCoordinator : NSObject { | |
| internal var actionInProgress: Bool | |
| internal init(webView: UIWebView) | |
| internal func setLoading(_ isLoading: Bool, canGoBack: Bool? = nil, canGoForward: Bool? = nil, error: Error? = nil) | |
| } | |
| extension UIWebViewCoordinator : WKNavigationDelegate { | |
| @MainActor public func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) | |
| @MainActor public func webViewWebContentProcessDidTerminate(_ webView: WKWebView) | |
| @MainActor public func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) | |
| @MainActor public func webView(_ webView: WKWebView, didCommit navigation: WKNavigation!) | |
| @MainActor public func webView(_ webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!) | |
| @MainActor public func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) | |
| } | |
| extension UIWebViewCoordinator : WKUIDelegate { | |
| @MainActor public func webView(_ webView: WKWebView, createWebViewWith configuration: WKWebViewConfiguration, for navigationAction: WKNavigationAction, windowFeatures: WKWindowFeatures) -> WKWebView? | |
| } | |
| public struct UIWebViewConfig { | |
| public static let `default`: UIWebView.UIWebViewConfig | |
| public let javaScriptEnabled: Bool | |
| public let allowsBackForwardNavigationGestures: Bool | |
| public let allowsInlineMediaPlayback: Bool | |
| public let mediaTypesRequiringUserActionForPlayback: WKAudiovisualMediaTypes | |
| public let isScrollEnabled: Bool | |
| public let isOpaque: Bool | |
| public let backgroundColor: Color | |
| public let scrollBounceBehavior: ScrollBounceBehavior | |
| public let scrollBounceAxes: Axis.Set | |
| public let allowsMagnification: Bool | |
| public let allowsTextSelection: Bool | |
| public let allowsLinkPreviews: Bool | |
| public let allowsElementFullscreen: Bool | |
| public init(javaScriptEnabled: Bool = true, allowsBackForwardNavigationGestures: Bool = true, allowsInlineMediaPlayback: Bool = true, mediaTypesRequiringUserActionForPlayback: WKAudiovisualMediaTypes = [], isScrollEnabled: Bool = true, isOpaque: Bool = true, backgroundColor: Color = .clear, scrollBounceBehavior: ScrollBounceBehavior = .automatic, scrollBounceAxes: Axis.Set = [.vertical], allowsMagnification: Bool = true, allowsTextSelection: Bool = true, allowsLinkPreviews: Bool = true, allowsElementFullscreen: Bool = true) | |
| } | |
| public typealias ViewRepresentableContext = NSViewRepresentableContext<UIWebView> | |
| public struct UIWebView : OSViewRepresentable { | |
| public let config: UIWebViewConfig | |
| @Binding public var action: UIWebViewAction { get nonmutating set } | |
| @Binding public var state: UIWebViewState { get nonmutating set } | |
| public let restrictedPages: [String]? | |
| public let htmlInState: Bool | |
| public let schemeHandlers: [String : (URL) -> Void] | |
| public init(config: UIWebViewConfig = .default, action: Binding<UIWebViewAction>, state: Binding<UIWebViewState>, restrictedPages: [String]? = nil, htmlInState: Bool = false, schemeHandlers: [String : (URL) -> Void] = [:]) | |
| public typealias NSViewType = WKWebView | |
| public typealias Coordinator = UIWebViewCoordinator | |
| public func makeCoordinator() -> Coordinator | |
| public func makeNSView(context: ViewRepresentableContext) -> NSViewType | |
| public func updateNSView(_ nsView: NSViewType, context: ViewRepresentableContext) | |
| } | |
| extension UIWebView { | |
| public func scrollBounceBehavior(_ behavior: ScrollBounceBehavior, axes: Axis.Set = [.vertical]) -> UIWebView.UIWebView | |
| public func webViewBackForwardNavigationGestures(_ enabled: Bool) -> UIWebView.UIWebView | |
| public func webViewMagnificationGestures(_ enabled: Bool) -> UIWebView.UIWebView | |
| public func webViewTextSelection(_ enabled: Bool) -> UIWebView.UIWebView | |
| public func webViewLinkPreviews(_ enabled: Bool) -> UIWebView.UIWebView | |
| public func webViewElementFullscreenBehavior(_ enabled: Bool) -> UIWebView.UIWebView | |
| public func webViewContentBackground(_ color: Color) -> UIWebView.UIWebView | |
| } | |
| public struct UIWebPage { | |
| public let url: URL? | |
| public let html: String? | |
| public init(url: URL) | |
| public init(html: String) | |
| } | |
| extension UIWebView { | |
| public init(page: UIWebPage, config: UIWebViewConfig = .default, state: Binding<UIWebViewState>, restrictedPages: [String]? = nil, htmlInState: Bool = false, schemeHandlers: [String : (URL) -> Void] = [:]) | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment