Last active
April 21, 2022 18:11
-
-
Save GGJJack/2f1cc2c8d21dddd010f7b10efee457d9 to your computer and use it in GitHub Desktop.
SwiftUI WebView Examples
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
| // TrubleShooting | |
| // Error 1 | |
| // 2021-08-06 15:27:49.147478+0900 WebView[9051:327490] [Process] 0x7fe29b046e20 - [pageProxyID=5, webPageID=6, PID=9053] WebPageProxy::didFailProvisionalLoadForFrame: frameID = 3, domain = NSURLErrorDomain, code = -1022 | |
| // Error localizedDescription | |
| // Error : The resource could not be loaded because the App Transport Security policy requires the use of a secure connection. | |
| //Info.plist Option 1 : disable all | |
| //<key>NSAppTransportSecurity</key> | |
| //<dict> | |
| // <key>NSAllowsLocalNetworking</key> | |
| // <true/> | |
| //</dict> | |
| // Info.plist Option 2 : disable specific domain | |
| //<key> NSAppTransportSecurity </key> | |
| //<dict> | |
| // <key> NSExceptionDomains </key> | |
| // <dict> | |
| // <key> www.xxx.com </key> | |
| // <dict> | |
| // <key> NSTemporaryExceptionAllowsInsecureHTTPLoads </key> | |
| // <true /> | |
| // </dict> | |
| // </dict> | |
| //</dict> | |
| // Info.plist | |
| //NSAppTransportSecurity (Dictionary) | |
| // NSExceptionDomains (Dictionary) | |
| // NSAllowsArbitraryLoads (Bool) | |
| // <domain-name-for-exception-as-string> (Dictionary) | |
| // NSExceptionMinimumTLSVersion (String) | |
| // NSExceptionRequiresForwardSecrecy (Bool) | |
| // NSExceptionAllowsInsecureHTTPLoads (Bool) | |
| // NSRequiresCertificateTransparency (Bool) | |
| // NSIncludesSubdomains (Bool) | |
| // NSThirdPartyExceptionMinimumTLSVersion (String) | |
| // NSThirdPartyExceptionRequiresForwardSecrecy (Bool) | |
| // NSThirdPartyExceptionAllowsInsecureHTTPLoads (Bool) |
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
| // | |
| // WebView.swift | |
| // | |
| import UIKit | |
| import SwiftUI | |
| import Combine | |
| import WebKit | |
| class WebViewController: NSObject, ObservableObject, WKNavigationDelegate { | |
| @Published var url: URL? = nil | |
| var onLoadStart = PassthroughSubject<URL, Never>() | |
| var onLoadComplete = PassthroughSubject<URL, Never>() | |
| var onError = PassthroughSubject<Error, Never>() | |
| var onFilter: ((WKNavigationAction) -> WKNavigationActionPolicy)? = nil | |
| init(_ url: String) { | |
| self.url = URL(string: url) | |
| } | |
| init(_ url: URL) { | |
| self.url = url | |
| } | |
| func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) { | |
| // Example : Host-based access control | |
| //if let host = navigationAction.request.url?.host { | |
| // if host != "github.com" { | |
| // return decisionHandler(.cancel) | |
| // } | |
| //} else { | |
| // print("Load : \(String(describing: navigationAction.request.url))") | |
| //} | |
| if let url = navigationAction.request.url { | |
| onLoadStart.send(url) | |
| } | |
| return decisionHandler(onFilter?(navigationAction) ?? .allow) | |
| } | |
| func webView(_ webView: WKWebView, decidePolicyFor navigationResponse: WKNavigationResponse, decisionHandler: @escaping (WKNavigationResponsePolicy) -> Void) { | |
| print("\(#function)") | |
| decisionHandler(.allow) | |
| } | |
| func webView(_ webview: WKWebView, didFinish: WKNavigation!) { | |
| if let url = self.url { | |
| onLoadComplete.send(url) | |
| } | |
| } | |
| func webView(_ webView: WKWebView, didFailProvisionalNavigation: WKNavigation!, withError: Error) { | |
| onError.send(withError) | |
| } | |
| func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) { | |
| onError.send(error) | |
| } | |
| } | |
| struct WebView: UIViewRepresentable { | |
| @ObservedObject var controller: WebViewController //= WebViewController() | |
| var onLoadStart: ((URL) -> Void)? = nil | |
| var onLoadComplete: ((URL) -> Void)? = nil | |
| var onError: ((Error) -> Void)? = nil | |
| func makeCoordinator() -> WebView.Coordinator { | |
| Coordinator() | |
| } | |
| func makeUIView(context: Context) -> WKWebView { | |
| let preferences = WKPreferences() | |
| preferences.javaScriptCanOpenWindowsAutomatically = false | |
| let configuration = WKWebViewConfiguration() | |
| configuration.preferences = preferences | |
| let webView = WKWebView(frame: CGRect.zero, configuration: configuration) | |
| webView.navigationDelegate = controller | |
| webView.allowsBackForwardNavigationGestures = true | |
| webView.scrollView.isScrollEnabled = true | |
| controller.onLoadStart.receive(on: RunLoop.main).sink { url in | |
| self.onLoadStart?(url) | |
| }.store(in: &context.coordinator.subscriptions) | |
| controller.onLoadComplete.receive(on: RunLoop.main).sink { url in | |
| self.onLoadComplete?(url) | |
| }.store(in: &context.coordinator.subscriptions) | |
| controller.onError.receive(on: RunLoop.main).sink { err in | |
| self.onError?(err) | |
| }.store(in: &context.coordinator.subscriptions) | |
| if let url = self.controller.url { | |
| webView.load(URLRequest(url: url)) | |
| } | |
| return webView | |
| } | |
| func updateUIView(_ uiView: WKWebView, context: Context) { | |
| // if let url = self.controller.url { | |
| // uiView.load(URLRequest(url: url)) | |
| // } | |
| } | |
| func onLoadStart(listener: @escaping (URL) -> Void) -> Self { | |
| var copy = self | |
| copy.onLoadStart = listener | |
| return copy | |
| } | |
| func onLoadComplete(listener: @escaping (URL) -> Void) -> Self { | |
| var copy = self | |
| copy.onLoadComplete = listener | |
| return copy | |
| } | |
| func onError(listener: @escaping (Error) -> Void) -> Self { | |
| var copy = self | |
| copy.onError = listener | |
| return copy | |
| } | |
| func onFilter(listener: @escaping (WKNavigationAction) -> WKNavigationActionPolicy) -> Self { | |
| self.controller.onFilter = listener | |
| return self | |
| } | |
| } | |
| extension WebView { | |
| class Coordinator: NSObject, ObservableObject { | |
| var subscriptions = Set<AnyCancellable>() | |
| } | |
| } |
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
| struct WebViewSample1: View { | |
| let controller = WebViewController("about:blank") | |
| var body: some View { | |
| VStack { | |
| HStack { | |
| Button("Github") { | |
| controller.url = "https://github.com" | |
| } | |
| Button("Example") { | |
| controller.url = "https://example.com" | |
| } | |
| } | |
| WebView(controller: controller) | |
| .onLoadStart { url in | |
| print("Load Start : \(url)") | |
| } | |
| .onLoadComplete { url in | |
| print("Load Complete : \(url)") | |
| } | |
| .onError { err in | |
| print("Error : \(err)") | |
| } | |
| .onFilter { action in | |
| if let host = action.request.url?.host, host == "example.com" { | |
| return .cancel | |
| } | |
| return .allow | |
| } | |
| } | |
| } | |
| } | |
| struct WebViewSample1_Previews: PreviewProvider { | |
| static var previews: some View { | |
| WebViewSample1() | |
| } | |
| } |
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
| struct WebViewSample2: View { | |
| let controller = WebViewController("about:blank") | |
| var body: some View { | |
| VStack { | |
| Button("Github") { | |
| controller.url = "https://github.com" | |
| } | |
| WebView(controller: controller) | |
| } | |
| .onReceive(controller.onLoadStart.receive(on: RunLoop.main)) { url in | |
| print("Load Start : \(url)") | |
| } | |
| .onReceive(controller.onLoadComplete.receive(on: RunLoop.main)) { url in | |
| print("Load Complete : \(url)") | |
| } | |
| .onReceive(controller.onError.receive(on: RunLoop.main)) { err in | |
| print("Error : \(err)") | |
| } | |
| } | |
| } | |
| struct WebViewSample2_Previews: PreviewProvider { | |
| static var previews: some View { | |
| WebViewSample2() | |
| } | |
| } |
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
| class ViewModel: ObservableObject { | |
| let controller = WebViewController("about:blank") | |
| var subscriptions = Set<AnyCancellable>() | |
| init() { | |
| controller.onLoadStart.sink { print("Load Start : \($0)") }.store(in: &subscriptions) | |
| controller.onLoadComplete.sink { print("Load Start : \($0)") }.store(in: &subscriptions) | |
| controller.onError.sink { print("Load Start : \($0)") }.store(in: &subscriptions) | |
| controller.onFilter = { action in | |
| if let host = action.request.url?.host, host == "example.com" { | |
| return .cancel | |
| } | |
| return .allow | |
| } | |
| } | |
| func loadGithub() { | |
| controller.url = "https://github.com" | |
| } | |
| func loadExample() { | |
| controller.url = "https://example.com" | |
| } | |
| } | |
| struct WebViewSampleViewModel: View { | |
| @ObservedObject var vm = ViewModel() | |
| var body: some View { | |
| VStack { | |
| HStack { | |
| Button("Github") { vm.loadGithub() } | |
| Button("Example") { vm.loadExample() } | |
| } | |
| WebView(controller: vm.controller) | |
| } | |
| } | |
| } | |
| struct WebViewSampleViewModel_Previews: PreviewProvider { | |
| static var previews: some View { | |
| WebViewSampleViewModel() | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment