SwiftUI WKWebView 检测到 url 变化
SwiftUI WKWebView detect url changing
我是 swift 学习者。我使用 SwiftUI,它是一个结构,我必须实现一个 WKWebView,其中 url 正在动态变化。我必须抓住这些变化 urls,但我尝试过的解决方案不起作用。
例如:
我试过这个代码块,但它不工作,它给了我一些编译器错误:
import SwiftUI
import WebKit
struct ContentView: UIViewRepresentable, WKNavigationDelegate {
let request = URLRequest(url: URL(string: "https://apple.com")!)
func makeUIView(context: Context) -> WKWebView {
let preferences = WKPreferences()
preferences.javaScriptEnabled = true
preferences.javaScriptCanOpenWindowsAutomatically = true
let configuration = WKWebViewConfiguration()
configuration.preferences = preferences
let webView = WKWebView(frame: .zero, configuration: configuration)
webView.allowsBackForwardNavigationGestures = true
return webView
}
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
// 'override' can only be specified on class membe
if keyPath == #keyPath(WKWebView.url) {
print("### URL:", self.webView.url!)
}
if keyPath == #keyPath(WKWebView.estimatedProgress) {
// When page load finishes. Should work on each page reload.
if (self.webView.estimatedProgress == 1) {
print("### EP:", self.webView.estimatedProgress)
}
}
}
func updateUIView(_ uiView: WKWebView, context: Context) {
uiView.load(request)
}
func webViewDidFinishLoad(webView : WKWebView) {
print("Loaded: \(String(describing: webView.url))")
}
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
print("Loaded: \(String(describing: webView.url))")
//progressView.isHidden = true
}
func webView(_ webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!) {
//progressView.isHidden = false
print("Loaded: \(String(describing: webView.url))")
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
我在 struct ContentView...
行有一个非 class 类型 'ContentView' cannot conform to class protocol 'NSObjectProtocol' 错误
您将此用于 WKNavigationProtocol 的委托来执行(例如允许或取消 URL 加载)您的操作
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
if let host = navigationAction.request.url?.host {
if host.contains("facebook.com") {
decisionHandler(.cancel)
return
}
}
decisionHandler(.allow)
}
我找到了一个很好的解决我的问题的方法。我会 post 在这里。也许有人想看,也许对他们有用。
observe.observation = uiView.observe(\WKWebView.url, options: .new) { view, change in
if let url = view.url {
// do something with your url
}
}
您可以使用 key/value 观察来检测 WKWebView 的 url 属性 的变化。
这是一个将 WKWebView 包装在 UIViewRepresentable 中的简单示例。
请注意,因为我们正在修改 属性,UIViewRepresentable 是一个 final class
而不是结构。
import Combine
import SwiftUI
import WebKit
final class WebView: UIViewRepresentable {
@Published var url: URL? = nil {
didSet {
if url != nil {
willChange.send(url)
}
}
}
private let view = WKWebView()
private var urlChangedObservation: NSKeyValueObservation?
private let willChange = PassthroughSubject<URL?, Never>()
func makeUIView(context: Context) -> WKWebView {
return makeWebView()
}
func updateUIView(_ uiView: WKWebView, context: Context) {
}
func display(_ html: String) {
self.view.loadHTMLString(html, baseURL: nil)
}
public func load(_ url: String) -> WebView {
let link = URL(string: url)!
let request = URLRequest(url: link)
self.view.load(request)
return self
}
func makeWebView() -> WKWebView {
self.urlChangedObservation = self.view.observe(\WKWebView.url, options: .new) { view, change in
if let url = view.url {
self.url = url
}
}
return self.view
}
}
然后您可以在持有 WebView 的容器的 onReceive() 中收听 url 修改后的通知:
.onReceive(self.webview.$url) { url in
if let url = url {
}
}
您可以简单地创建一个名为“WebViewModel”的 webview 的 ObservableObject 模型class,例如
class WebViewModel: ObservableObject {
@Published var link: String
@Published var didFinishLoading: Bool = false
init (link: String) {
self.link = link
}
}
并导入
import WebKit
import Combine
然后复制这段代码
struct SwiftUIWebView: UIViewRepresentable {
@ObservedObject var viewModel: WebViewModel
let webView = WKWebView()
func makeUIView(context: UIViewRepresentableContext<SwiftUIWebView>) -> WKWebView {
self.webView.navigationDelegate = context.coordinator
if let url = URL(string: viewModel.link) {
self.webView.load(URLRequest(url: url))
}
return self.webView
}
func updateUIView(_ uiView: WKWebView, context: UIViewRepresentableContext<SwiftUIWebView>) {
return
}
class Coordinator: NSObject, WKNavigationDelegate {
private var viewModel: WebViewModel
init(_ viewModel: WebViewModel) {
self.viewModel = viewModel
}
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
//print("WebView: navigation finished")
self.viewModel.didFinishLoading = true
}
}
func makeCoordinator() -> SwiftUIWebView.Coordinator {
Coordinator(viewModel)
}
}
struct SwiftUIWebView_Previews: PreviewProvider {
static var previews: some View {
SwiftUIWebView(viewModel: WebViewModel(link: "https://google.com"))
//WebView(request: URLRequest(url: URL(string: "https://www.apple.com")!))
}
}
在你看来
struct AnyView: View {
@ObservedObject var model = WebViewModel(link: "https://www.wikipedia.org/")
var body: some View {
NavigationView {
SwiftUIWebView(viewModel: model)
if model.didFinishLoading {
//do your stuff
}
}
}}
所以通过这种方式你可以得到其他代表的回应。
使用WKWebView
的以下委托函数:
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
// Suppose you don't want your user to go a restricted site
if let host = navigationAction.request.url?.host {
if host == "restricted.com" {
decisionHandler(.cancel)
return
}
}
decisionHandler(.allow)
}
您可以阅读来自 Medium
的 this 文章,其中显示了拦截每个 network
调用或 Url
更改和获取即将到来的 Url
相关的更好方法data
。它还展示了如何在 SwiftUI
中实现 WebView
、与 JavaScript
函数交互以及从 iOS 项目
加载本地 .html
文件
我来这里是为了尝试一种快速的方法来在 SwiftUI 中获取工作示例,以从 Web 身份验证服务获取 HTML 响应。 (具体来说,新的 DropBox awful 使用 URI 的身份验证模式......我们没有看到这个细节,但回调和代码应该足够解释。(JSOn来自我在 URI 中指定的 Web 服务器) )
在我们的 Swift UI 部分:
struct ContentView: View {
@State private var showingSheet = false
private var webCallBack: WebCallBack = nil
let webView = WKWebView(frame: .zero)
@State private var auth_code = ""
var body: some View {
VStack{
Text("\(auth_code)")
.font(.system(size: 50))
Button("Show Auth web form") {
self.showingSheet = true
}
.sheet(isPresented: $showingSheet) {
WebView( webView: webView, webCallBack: { (d: Dict?) in
print("\n", d)
auth_code = (d?["auth_code"] as? String) ?? "!!"
showingSheet = false
} )
}
}
}
}
我们的实施:
typealias WebCallBack = ( (Dict?)->() )?
class MyWKDelegate: NSObject, WKNavigationDelegate{
private var webCallBack : WebCallBack = nil
init(webCallBack: WebCallBack) {
self.webCallBack = webCallBack
}
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
print("End loading")
webView.evaluateJavaScript("document.body.innerHTML", completionHandler: { result, error in
if let html = result as? String {
//print(html)
// we are here also at first call, i.e. web view with user / password. Custiomize as needed.
if let d = dictFromJSONWith(string: html){
//print(d)
self.webCallBack?(d)
}
}
})
}
}
struct WebView: UIViewRepresentable {
let webView: WKWebView
let delegate: MyWKDelegate
internal init(webView: WKWebView, webCallBack: WebCallBack) {
self.webView = webView
self.delegate = MyWKDelegate(webCallBack: webCallBack)
webView.navigationDelegate = delegate
let urlStr = DB_URL.replacingOccurrences(of: "APP_KEY", with: APP_KEY).replacingOccurrences(of: "REDIRECT_URI", with: REDIRECT_URI)
print(urlStr)
if let url = URL(string: urlStr){
webView.load(URLRequest(url: url))
}
}
func makeUIView(context: Context) -> WKWebView {
return webView
}
func updateUIView(_ uiView: WKWebView, context: Context) { }
}
一些辅助代码,让生活更轻松:
typealias Dict = [String : Any]
typealias Dicts = [Dict]
func dictFromJSONWith(data: Data?)->Dict? {
guard let data = data else {
return nil
}
if let dict = try? JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions() ){
return dict as? Dict
}
return nil
}
func dictFromJSONWith(string: String?)->Dict?{
guard let data = string?.data(using: .utf8) else{
return nil
}
return dictFromJSONWith(data: data)
}
简单,就用这个委托方法
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
print(webView.url?.absoluteString)
}
我是 swift 学习者。我使用 SwiftUI,它是一个结构,我必须实现一个 WKWebView,其中 url 正在动态变化。我必须抓住这些变化 urls,但我尝试过的解决方案不起作用。
例如:
import SwiftUI
import WebKit
struct ContentView: UIViewRepresentable, WKNavigationDelegate {
let request = URLRequest(url: URL(string: "https://apple.com")!)
func makeUIView(context: Context) -> WKWebView {
let preferences = WKPreferences()
preferences.javaScriptEnabled = true
preferences.javaScriptCanOpenWindowsAutomatically = true
let configuration = WKWebViewConfiguration()
configuration.preferences = preferences
let webView = WKWebView(frame: .zero, configuration: configuration)
webView.allowsBackForwardNavigationGestures = true
return webView
}
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
// 'override' can only be specified on class membe
if keyPath == #keyPath(WKWebView.url) {
print("### URL:", self.webView.url!)
}
if keyPath == #keyPath(WKWebView.estimatedProgress) {
// When page load finishes. Should work on each page reload.
if (self.webView.estimatedProgress == 1) {
print("### EP:", self.webView.estimatedProgress)
}
}
}
func updateUIView(_ uiView: WKWebView, context: Context) {
uiView.load(request)
}
func webViewDidFinishLoad(webView : WKWebView) {
print("Loaded: \(String(describing: webView.url))")
}
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
print("Loaded: \(String(describing: webView.url))")
//progressView.isHidden = true
}
func webView(_ webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!) {
//progressView.isHidden = false
print("Loaded: \(String(describing: webView.url))")
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
我在 struct ContentView...
行有一个非 class 类型 'ContentView' cannot conform to class protocol 'NSObjectProtocol' 错误您将此用于 WKNavigationProtocol 的委托来执行(例如允许或取消 URL 加载)您的操作
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
if let host = navigationAction.request.url?.host {
if host.contains("facebook.com") {
decisionHandler(.cancel)
return
}
}
decisionHandler(.allow)
}
我找到了一个很好的解决我的问题的方法。我会 post 在这里。也许有人想看,也许对他们有用。
observe.observation = uiView.observe(\WKWebView.url, options: .new) { view, change in
if let url = view.url {
// do something with your url
}
}
您可以使用 key/value 观察来检测 WKWebView 的 url 属性 的变化。
这是一个将 WKWebView 包装在 UIViewRepresentable 中的简单示例。
请注意,因为我们正在修改 属性,UIViewRepresentable 是一个 final class
而不是结构。
import Combine
import SwiftUI
import WebKit
final class WebView: UIViewRepresentable {
@Published var url: URL? = nil {
didSet {
if url != nil {
willChange.send(url)
}
}
}
private let view = WKWebView()
private var urlChangedObservation: NSKeyValueObservation?
private let willChange = PassthroughSubject<URL?, Never>()
func makeUIView(context: Context) -> WKWebView {
return makeWebView()
}
func updateUIView(_ uiView: WKWebView, context: Context) {
}
func display(_ html: String) {
self.view.loadHTMLString(html, baseURL: nil)
}
public func load(_ url: String) -> WebView {
let link = URL(string: url)!
let request = URLRequest(url: link)
self.view.load(request)
return self
}
func makeWebView() -> WKWebView {
self.urlChangedObservation = self.view.observe(\WKWebView.url, options: .new) { view, change in
if let url = view.url {
self.url = url
}
}
return self.view
}
}
然后您可以在持有 WebView 的容器的 onReceive() 中收听 url 修改后的通知:
.onReceive(self.webview.$url) { url in
if let url = url {
}
}
您可以简单地创建一个名为“WebViewModel”的 webview 的 ObservableObject 模型class,例如
class WebViewModel: ObservableObject {
@Published var link: String
@Published var didFinishLoading: Bool = false
init (link: String) {
self.link = link
}
}
并导入
import WebKit
import Combine
然后复制这段代码
struct SwiftUIWebView: UIViewRepresentable {
@ObservedObject var viewModel: WebViewModel
let webView = WKWebView()
func makeUIView(context: UIViewRepresentableContext<SwiftUIWebView>) -> WKWebView {
self.webView.navigationDelegate = context.coordinator
if let url = URL(string: viewModel.link) {
self.webView.load(URLRequest(url: url))
}
return self.webView
}
func updateUIView(_ uiView: WKWebView, context: UIViewRepresentableContext<SwiftUIWebView>) {
return
}
class Coordinator: NSObject, WKNavigationDelegate {
private var viewModel: WebViewModel
init(_ viewModel: WebViewModel) {
self.viewModel = viewModel
}
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
//print("WebView: navigation finished")
self.viewModel.didFinishLoading = true
}
}
func makeCoordinator() -> SwiftUIWebView.Coordinator {
Coordinator(viewModel)
}
}
struct SwiftUIWebView_Previews: PreviewProvider {
static var previews: some View {
SwiftUIWebView(viewModel: WebViewModel(link: "https://google.com"))
//WebView(request: URLRequest(url: URL(string: "https://www.apple.com")!))
}
}
在你看来
struct AnyView: View {
@ObservedObject var model = WebViewModel(link: "https://www.wikipedia.org/")
var body: some View {
NavigationView {
SwiftUIWebView(viewModel: model)
if model.didFinishLoading {
//do your stuff
}
}
}}
所以通过这种方式你可以得到其他代表的回应。
使用WKWebView
的以下委托函数:
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
// Suppose you don't want your user to go a restricted site
if let host = navigationAction.request.url?.host {
if host == "restricted.com" {
decisionHandler(.cancel)
return
}
}
decisionHandler(.allow)
}
您可以阅读来自 Medium
的 this 文章,其中显示了拦截每个 network
调用或 Url
更改和获取即将到来的 Url
相关的更好方法data
。它还展示了如何在 SwiftUI
中实现 WebView
、与 JavaScript
函数交互以及从 iOS 项目
.html
文件
我来这里是为了尝试一种快速的方法来在 SwiftUI 中获取工作示例,以从 Web 身份验证服务获取 HTML 响应。 (具体来说,新的 DropBox awful 使用 URI 的身份验证模式......我们没有看到这个细节,但回调和代码应该足够解释。(JSOn来自我在 URI 中指定的 Web 服务器) )
在我们的 Swift UI 部分:
struct ContentView: View {
@State private var showingSheet = false
private var webCallBack: WebCallBack = nil
let webView = WKWebView(frame: .zero)
@State private var auth_code = ""
var body: some View {
VStack{
Text("\(auth_code)")
.font(.system(size: 50))
Button("Show Auth web form") {
self.showingSheet = true
}
.sheet(isPresented: $showingSheet) {
WebView( webView: webView, webCallBack: { (d: Dict?) in
print("\n", d)
auth_code = (d?["auth_code"] as? String) ?? "!!"
showingSheet = false
} )
}
}
}
}
我们的实施:
typealias WebCallBack = ( (Dict?)->() )?
class MyWKDelegate: NSObject, WKNavigationDelegate{
private var webCallBack : WebCallBack = nil
init(webCallBack: WebCallBack) {
self.webCallBack = webCallBack
}
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
print("End loading")
webView.evaluateJavaScript("document.body.innerHTML", completionHandler: { result, error in
if let html = result as? String {
//print(html)
// we are here also at first call, i.e. web view with user / password. Custiomize as needed.
if let d = dictFromJSONWith(string: html){
//print(d)
self.webCallBack?(d)
}
}
})
}
}
struct WebView: UIViewRepresentable {
let webView: WKWebView
let delegate: MyWKDelegate
internal init(webView: WKWebView, webCallBack: WebCallBack) {
self.webView = webView
self.delegate = MyWKDelegate(webCallBack: webCallBack)
webView.navigationDelegate = delegate
let urlStr = DB_URL.replacingOccurrences(of: "APP_KEY", with: APP_KEY).replacingOccurrences(of: "REDIRECT_URI", with: REDIRECT_URI)
print(urlStr)
if let url = URL(string: urlStr){
webView.load(URLRequest(url: url))
}
}
func makeUIView(context: Context) -> WKWebView {
return webView
}
func updateUIView(_ uiView: WKWebView, context: Context) { }
}
一些辅助代码,让生活更轻松:
typealias Dict = [String : Any]
typealias Dicts = [Dict]
func dictFromJSONWith(data: Data?)->Dict? {
guard let data = data else {
return nil
}
if let dict = try? JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions() ){
return dict as? Dict
}
return nil
}
func dictFromJSONWith(string: String?)->Dict?{
guard let data = string?.data(using: .utf8) else{
return nil
}
return dictFromJSONWith(data: data)
}
简单,就用这个委托方法
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
print(webView.url?.absoluteString)
}