Skip to content

Instantly share code, notes, and snippets.

@viere1234
Created June 24, 2022 09:39
Show Gist options
  • Save viere1234/79ea9c3412efb61f5bda5c64ee31848b to your computer and use it in GitHub Desktop.
Save viere1234/79ea9c3412efb61f5bda5c64ee31848b to your computer and use it in GitHub Desktop.
//
// 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