使用 swiftUI 将更新发送到子视图(WKWebView)?
Send update to child view (WKWebView) using swiftUI?
我想使用 SwiftUI 在视图中制作一个非常简单的浏览器。
期待什么:
在我的ContentView中,有一个WKWebView,在Navigation Bar的左边有一个“Go Back”按钮。如果按下“返回”按钮,webView 将返回到上一页。
ContentView.swift:
struct ContentView: View {
let defaultUrl = "https://www.apple.com"
@State var needsGoBack = false
var body: some View {
WebView(urlString: defaultUrl, needsGoBack: $needsGoBack)
.navigationBarItems(leading:
Button(action: {
print("button pressed...set needsGoBack = true")
needsGoBack = true
}) {
Text("Go Back")
})
}
}
WebView.swift
struct WebView: UIViewRepresentable {
let urlString: String
let navigationHelper = WebViewHelper()
@State var myWebView = WKWebView()
@Binding var needsGoBack: Bool
func makeUIView(context: Context) -> WKWebView {
if let url = URL(string: urlString) {
let request = URLRequest(url: url)
myWebView.load(request)
}
myWebView.navigationDelegate = navigationHelper
myWebView.uiDelegate = navigationHelper
return myWebView
}
func updateUIView(_ uiView: WKWebView, context: Context) {
print("webview updateUIView")
print("needsGoBack", needsGoBack)
if needsGoBack {
myWebView.goBack()
needsGoBack = false // this line has problem
}
}
typealias UIViewType = WKWebView
}
// https://gist.github.com/joshbetz/2ff5922203240d4685d5bdb5ada79105
class WebViewHelper: NSObject, WKNavigationDelegate, WKUIDelegate {
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
print("webview didFinishNavigation")
}
func webView(_ webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!) {
print("didStartProvisionalNavigation")
}
func webView(_ webView: WKWebView, didCommit navigation: WKNavigation!) {
print("webviewDidCommit")
}
func webView(_ webView: WKWebView, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
print("didReceiveAuthenticationChallenge")
completionHandler(.performDefaultHandling, nil)
}
}
实际发生了什么:
在 WebView.swift 的 updateUIView 函数中,有一条警告消息“在视图更新期间修改状态,这将导致未定义的行为。”对于这行代码:“needsGoBack = false”。并且“needsGoBack”变量不会设置为“false”。 “返回”按钮第一次起作用。但是,因为“needsGoBack”之后一直是“true”,子视图(WebView)将不会被第二次通知(调用updateUIView方法)。
通过将您的 WKWebView
存储在您的 ContentView
都可以访问的单独对象(在本例中为 NavigationState
)中,您可以访问 goBack()
方法直接。这样,您就避免了尝试使用 Bool
来表示一次性事件的棘手问题,这不仅在实践中不起作用(如您所见),而且在语义上也有点有趣想想。
class NavigationState : NSObject, ObservableObject {
@Published var currentURL : URL?
@Published var webView : WKWebView
override init() {
let wv = WKWebView()
self.webView = wv
super.init()
wv.navigationDelegate = self
}
func loadRequest(_ urlRequest: URLRequest) {
webView.load(urlRequest)
}
}
extension NavigationState : WKNavigationDelegate {
func webView(_ webView: WKWebView, didCommit navigation: WKNavigation!) {
self.currentURL = webView.url
}
}
struct WebView : UIViewRepresentable {
@ObservedObject var navigationState : NavigationState
func makeUIView(context: Context) -> WKWebView {
return navigationState.webView
}
func updateUIView(_ uiView: WKWebView, context: Context) {
}
}
struct ContentView: View {
@StateObject var navigationState = NavigationState()
var body: some View {
VStack {
Text(navigationState.currentURL?.absoluteString ?? "(none)")
WebView(navigationState: navigationState)
.clipped()
HStack {
Button("Back") {
navigationState.webView.goBack()
}
Button("Forward") {
navigationState.webView.goForward()
}
}
}.onAppear {
navigationState.loadRequest(URLRequest(url: URL(string: "https://www.google.com")!))
}
}
}
我想使用 SwiftUI 在视图中制作一个非常简单的浏览器。
期待什么:
在我的ContentView中,有一个WKWebView,在Navigation Bar的左边有一个“Go Back”按钮。如果按下“返回”按钮,webView 将返回到上一页。
ContentView.swift:
struct ContentView: View {
let defaultUrl = "https://www.apple.com"
@State var needsGoBack = false
var body: some View {
WebView(urlString: defaultUrl, needsGoBack: $needsGoBack)
.navigationBarItems(leading:
Button(action: {
print("button pressed...set needsGoBack = true")
needsGoBack = true
}) {
Text("Go Back")
})
}
}
WebView.swift
struct WebView: UIViewRepresentable {
let urlString: String
let navigationHelper = WebViewHelper()
@State var myWebView = WKWebView()
@Binding var needsGoBack: Bool
func makeUIView(context: Context) -> WKWebView {
if let url = URL(string: urlString) {
let request = URLRequest(url: url)
myWebView.load(request)
}
myWebView.navigationDelegate = navigationHelper
myWebView.uiDelegate = navigationHelper
return myWebView
}
func updateUIView(_ uiView: WKWebView, context: Context) {
print("webview updateUIView")
print("needsGoBack", needsGoBack)
if needsGoBack {
myWebView.goBack()
needsGoBack = false // this line has problem
}
}
typealias UIViewType = WKWebView
}
// https://gist.github.com/joshbetz/2ff5922203240d4685d5bdb5ada79105
class WebViewHelper: NSObject, WKNavigationDelegate, WKUIDelegate {
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
print("webview didFinishNavigation")
}
func webView(_ webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!) {
print("didStartProvisionalNavigation")
}
func webView(_ webView: WKWebView, didCommit navigation: WKNavigation!) {
print("webviewDidCommit")
}
func webView(_ webView: WKWebView, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
print("didReceiveAuthenticationChallenge")
completionHandler(.performDefaultHandling, nil)
}
}
实际发生了什么:
在 WebView.swift 的 updateUIView 函数中,有一条警告消息“在视图更新期间修改状态,这将导致未定义的行为。”对于这行代码:“needsGoBack = false”。并且“needsGoBack”变量不会设置为“false”。 “返回”按钮第一次起作用。但是,因为“needsGoBack”之后一直是“true”,子视图(WebView)将不会被第二次通知(调用updateUIView方法)。
通过将您的 WKWebView
存储在您的 ContentView
都可以访问的单独对象(在本例中为 NavigationState
)中,您可以访问 goBack()
方法直接。这样,您就避免了尝试使用 Bool
来表示一次性事件的棘手问题,这不仅在实践中不起作用(如您所见),而且在语义上也有点有趣想想。
class NavigationState : NSObject, ObservableObject {
@Published var currentURL : URL?
@Published var webView : WKWebView
override init() {
let wv = WKWebView()
self.webView = wv
super.init()
wv.navigationDelegate = self
}
func loadRequest(_ urlRequest: URLRequest) {
webView.load(urlRequest)
}
}
extension NavigationState : WKNavigationDelegate {
func webView(_ webView: WKWebView, didCommit navigation: WKNavigation!) {
self.currentURL = webView.url
}
}
struct WebView : UIViewRepresentable {
@ObservedObject var navigationState : NavigationState
func makeUIView(context: Context) -> WKWebView {
return navigationState.webView
}
func updateUIView(_ uiView: WKWebView, context: Context) {
}
}
struct ContentView: View {
@StateObject var navigationState = NavigationState()
var body: some View {
VStack {
Text(navigationState.currentURL?.absoluteString ?? "(none)")
WebView(navigationState: navigationState)
.clipped()
HStack {
Button("Back") {
navigationState.webView.goBack()
}
Button("Forward") {
navigationState.webView.goForward()
}
}
}.onAppear {
navigationState.loadRequest(URLRequest(url: URL(string: "https://www.google.com")!))
}
}
}