Skip to content

Instantly share code, notes, and snippets.

@GGJJack
Last active April 21, 2022 18:11
Show Gist options
  • Select an option

  • Save GGJJack/2f1cc2c8d21dddd010f7b10efee457d9 to your computer and use it in GitHub Desktop.

Select an option

Save GGJJack/2f1cc2c8d21dddd010f7b10efee457d9 to your computer and use it in GitHub Desktop.
SwiftUI WebView Examples
// 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)
//
// 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>()
}
}
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()
}
}
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()
}
}
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