Created
June 24, 2022 09:39
-
-
Save viere1234/79ea9c3412efb61f5bda5c64ee31848b to your computer and use it in GitHub Desktop.
This file contains 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 | |
// OPass | |
// | |
// Created by secminhr on 2022/6/23. | |
// 2022 OPass. | |
// | |
import SwiftUI | |
import Combine | |
import WebKit | |
struct WebView: View { | |
@StateObject var webViewStore = WebViewStore() | |
let url: URL | |
let title: String | |
init(_ url: URL, title: String? = nil) { | |
self.url = url | |
self.title = title ?? "" | |
} | |
var body: some View { | |
VStack(spacing: 0) { | |
Divider() | |
WebViewWrapper(webView: webViewStore.webView) | |
} | |
//.edgesIgnoringSafeArea(.bottom) | |
.background(Color("SectionBackgroundColor").edgesIgnoringSafeArea(.all)) | |
.navigationTitle(title) | |
.navigationBarTitleDisplayMode(.inline) | |
.onAppear { self.webViewStore.webView.load(.init(url: url)) } | |
.toolbar { | |
ToolbarItem(placement: .navigationBarTrailing) { | |
SFButton(systemName: webViewStore.estimatedProgress != 1.0 ? "xmark" : "arrow.clockwise") { | |
self.webViewStore.webView.load(.init(url: url)) | |
} | |
} | |
ToolbarItem(placement: .bottomBar) { | |
SFButton(systemName: "chevron.left") { | |
self.webViewStore.webView.goBack() | |
} | |
} | |
ToolbarItem(placement: .bottomBar) { | |
SFButton(systemName: "chevron.right") { | |
self.webViewStore.webView.goForward() | |
} | |
} | |
} | |
.overlay { | |
if webViewStore.estimatedProgress != 1.0 { | |
VStack { | |
ProgressView(value: webViewStore.estimatedProgress) | |
Spacer() | |
} | |
} | |
} | |
} | |
} | |
@dynamicMemberLookup | |
class WebViewStore: ObservableObject { | |
@Published var webView: WKWebView { | |
didSet { | |
setupObservers() | |
} | |
} | |
init(webView: WKWebView = WKWebView()) { | |
self.webView = webView | |
setupObservers() | |
} | |
private func setupObservers() { | |
func subscriber<Value>(for keyPath: KeyPath<WKWebView, Value>) -> NSKeyValueObservation { | |
return webView.observe(keyPath, options: [.prior]) { _, change in | |
if change.isPrior { | |
self.objectWillChange.send() | |
} | |
} | |
} | |
// Setup observers for all KVO compliant properties | |
observers = [ | |
subscriber(for: \.title), | |
subscriber(for: \.url), | |
subscriber(for: \.isLoading), | |
subscriber(for: \.estimatedProgress), | |
subscriber(for: \.hasOnlySecureContent), | |
subscriber(for: \.serverTrust), | |
subscriber(for: \.canGoBack), | |
subscriber(for: \.canGoForward) | |
] | |
observers += [ | |
subscriber(for: \.themeColor), | |
subscriber(for: \.underPageBackgroundColor), | |
subscriber(for: \.microphoneCaptureState), | |
subscriber(for: \.cameraCaptureState) | |
] | |
#if swift(>=5.7) | |
if #available(iOS 16.0, macOS 13.0, *) { | |
observers.append(subscriber(for: \.fullscreenState)) | |
} | |
#else | |
if #available(iOS 15.0, macOS 12.0, *) { | |
observers.append(subscriber(for: \.fullscreenState)) | |
} | |
#endif | |
} | |
private var observers: [NSKeyValueObservation] = [] | |
subscript<T>(dynamicMember keyPath: KeyPath<WKWebView, T>) -> T { | |
webView[keyPath: keyPath] | |
} | |
} | |
/// A container for using a WKWebView in SwiftUI | |
struct WebViewWrapper: View, UIViewRepresentable { | |
/// The WKWebView to display | |
let webView: WKWebView | |
init(webView: WKWebView) { | |
self.webView = webView | |
} | |
func makeUIView(context: UIViewRepresentableContext<WebViewWrapper>) -> WKWebView { | |
webView | |
} | |
func updateUIView(_ uiView: WKWebView, context: UIViewRepresentableContext<WebViewWrapper>) { | |
} | |
} | |
/* | |
import SwiftUI | |
import WebKit | |
struct WebView: View { | |
let url: URL? | |
let title: String? | |
@State private var progress: Double = 0.0 | |
@State private var outdated: Bool = false | |
var body: some View { | |
VStack(spacing: 0) { | |
Divider() | |
WebViewWrapper(url: url, outdated: $outdated, progress: $progress) | |
} | |
.background(Color("SectionBackgroundColor").edgesIgnoringSafeArea(.all)) | |
.navigationBarTitleDisplayMode(.inline) | |
.navigationTitle(title ?? "") | |
.refreshable { outdated = true } | |
.toolbar { | |
ToolbarItem(placement: .navigationBarTrailing) { | |
SFButton(systemName: progress != 1.0 ? "xmark" : "arrow.clockwise") { | |
outdated = progress == 1.0 | |
} | |
} | |
} | |
.overlay { | |
if progress != 1.0 { | |
VStack { | |
ProgressView(value: progress) | |
Spacer() | |
}.frame(width: UIScreen.main.bounds.width + 3) | |
} | |
} | |
} | |
} | |
struct WebViewWrapper: UIViewRepresentable { | |
let url: URL? | |
@Binding var outdated: Bool | |
@Binding var progress: Double | |
func makeCoordinator() -> Coordinator { | |
return Coordinator(outdated: _outdated, progress: _progress) | |
} | |
func makeUIView(context: Context) -> WKWebView { | |
let view = WKWebView() | |
context.coordinator.observer = view.observe(\.estimatedProgress, options: [.new]) { _, change in | |
DispatchQueue.main.async { | |
progress = change.newValue ?? 0.0 | |
} | |
} | |
view.navigationDelegate = context.coordinator | |
return view | |
} | |
func updateUIView(_ uiView: WKWebView, context: Context) { | |
//check context.coordinator.started to prevent unnecessary load, | |
//the check is necessary since updateUIView will be called multiple times due to progress change | |
if let url = url, !context.coordinator.started { | |
DispatchQueue.main.async { | |
outdated = false | |
} | |
uiView.load(URLRequest(url: url)) | |
} | |
if outdated { | |
DispatchQueue.main.async { | |
outdated = false | |
} | |
uiView.reload() | |
} | |
} | |
class Coordinator: NSObject, WKNavigationDelegate { | |
@Binding var outdated: Bool | |
@Binding var progress: Double | |
var observer: NSKeyValueObservation? = nil | |
private(set) var started = false | |
init(outdated: Binding<Bool>, progress: Binding<Double>) { | |
_outdated = outdated | |
_progress = progress | |
} | |
func webView(_ webView: WKWebView, didCommit navigation: WKNavigation!) { | |
started = true | |
} | |
deinit { | |
observer = nil | |
} | |
} | |
} | |
*/ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment