尝试实现启动多个 wkwebview 实例以在浏览器上模拟选项卡的功能

问题描述

我正在尝试制作一个非常准系统的移动浏览器来练习 swiftui 和 wkwebview(WKwebview 包含在 UIViewRepresentable 中)。但是,当尝试为浏览器实现多个选项卡时,我遇到了一个奇怪的错误认的 webview 可以工作,但是当按下按钮添加新选项卡时,我注意到新的 webview 实际上从未运行过它的 makeuiview() 函数。新选项卡仅显示一个 webview 加载的最后一个页面,但保持静态且无法接收导航。我不确定是什么导致 uiviewrepresentable 无法运行它的 makeuiview 方法。还有更多代码,但我发布了我认为最重要的内容

内容视图 (添加标签的逻辑所在)参考 Tablist.newTab() 函数

import SwiftUI
import CoreData


class TabList: ObservableObject {
    @Published var tabs: [browserPage] = [browserPage()]
    
    func newTab(){
        tabs.append(browserPage())
    }
    
}
struct ContentView: View {
    @Environment(\.managedobjectContext) private var viewContext
    @StateObject var tablist: TabList = TabList()
    
    @State var selection = 0
    @State var showTabs: Bool = false

    var body: some View {
        vstack {
          
            tablist.tabs[selection]
            Button("next") {
                withAnimation {
                    selection = (selection + 1) % tablist.tabs.count
                }
            }
            Divider()
            Button("add") {
                withAnimation {
                    tablist.newTab()
                    selection = tablist.tabs.count - 1
                    
                }
            }
            Divider()
            Text("\(selection)")
            Text("\(tablist.tabs.count)")
            
        }
        .frame( maxWidth: .infinity,maxHeight: .infinity,alignment: .topLeading)
        .sheet(isPresented: $showTabs,content: {
            
        })
        
        
    }
    
    
}

Webviewmodel 类

import Foundation
import Combine

class Webviewmodel: ObservableObject{
    
    var webViewNavigationPublisher = PassthroughSubject<WebViewNavigation,Never>()
    var showWebTitle = PassthroughSubject<String,Never>()
    var showLoader = PassthroughSubject<Bool,Never>()
    var valuePublisher = PassthroughSubject<String,Never>()
    var url: String = "https://www.google.com"
    @Published var urltoEdit: String = "https://www.google.com"

}

enum WebViewNavigation {
    case backward,forward,reload,load
}

WebsiteView(WKWebview 的 UIViewRepresentable)

import Foundation
import UIKit
import SwiftUI
import Combine
import WebKit

protocol WebViewHandlerDelegate {
    func receivedJsonValueFromWebView(value: [String: Any?])
    func receivedStringValueFromWebView(value: String)
}

struct WebView: UIViewRepresentable,WebViewHandlerDelegate {
   
    func receivedJsonValueFromWebView(value: [String : Any?]) {
        print("JSON value received from web is: \(value)")
    }
    
    func receivedStringValueFromWebView(value: String) {
        print("String value received from web is: \(value)")
    }
    
    @Observedobject var viewmodel: Webviewmodel
    
    func makeCoordinator() -> Coordinator {
        Coordinator(self)
    }
    
    func makeUIView(context: Context) -> WKWebView {
        let preferences = WKPreferences()
        let configuration = WKWebViewConfiguration()
        configuration.userContentController.add(self.makeCoordinator(),name: "iOSNative")
        configuration.preferences = preferences
        
        let webView = WKWebView(frame: CGRect.zero,configuration: configuration)
        webView.navigationDelegate = context.coordinator
        
        //remove after debugging...
        webView.allowsBackForwardNavigationGestures = true
        webView.scrollView.isScrollEnabled = true
        webView.load(URLRequest(url: URL(string: viewmodel.url)!))
        print("UUUUUUUUUUUUUUUUUUUUU " + viewmodel.url)
        return webView
    }
    
    
    func updateUIView(_ webView: WKWebView,context: Context) {
    }
    
    class Coordinator : NSObject,WKNavigationDelegate {
        var parent: WebView
        var valueSubscriber: AnyCancellable? = nil
        var webViewNavigationSubscriber: AnyCancellable? = nil
        var delegate: WebViewHandlerDelegate?
        
        init(_ uiWebView: WebView) {
            self.parent = uiWebView
        }
        
        deinit {
            valueSubscriber?.cancel()
            webViewNavigationSubscriber?.cancel()
        }
        
        func webView(_ webView: WKWebView,didFinish navigation: WKNavigation!) {
            // Get the title of loaded webcontent
            webView.evaluateJavaScript("document.title") { (response,error) in
                if let error = error {
                    print("Error getting title")
                    print(error.localizedDescription)
                }
                
                guard let title = response as? String else {
                    return
                }
                
                self.parent.viewmodel.showWebTitle.send(title)
                
            }
            
            valueSubscriber = parent.viewmodel.valuePublisher.receive(on: RunLoop.main).sink(receiveValue: { value in
                let javascriptFunction = "valueGotFromIOS(\(value));"
                webView.evaluateJavaScript(javascriptFunction) { (response,error) in
                    if let error = error {
                        print("Error calling javascript:valueGotFromIOS()")
                        print(error.localizedDescription)
                    } else {
                        print("Called javascript:valueGotFromIOS()")
                    }
                }
            })
            
            // Page loaded so no need to show loader anymore
            self.parent.viewmodel.showLoader.send(false)
        }
        
        /* Here I implemented most of the WKWebView's delegate functions so that you can kNow them and
         can use them in different necessary purposes */
        
        func webViewWebContentProcessDidTerminate(_ webView: WKWebView) {
            // Hides loader
            parent.viewmodel.showLoader.send(false)
        }
        
        func webView(_ webView: WKWebView,didFail navigation: WKNavigation!,withError error: Error) {
            // Hides loader
            parent.viewmodel.showLoader.send(false)
        }
        
        func webView(_ webView: WKWebView,didCommit navigation: WKNavigation!) {
            self.parent.viewmodel.urltoEdit = webView.url!.absoluteString
            // Shows loader
            parent.viewmodel.showLoader.send(true)
        }
        
        func webView(_ webView: WKWebView,didStartProvisionalNavigation navigation: WKNavigation!) {
            // Shows loader
            parent.viewmodel.showLoader.send(true)
            self.webViewNavigationSubscriber = self.parent.viewmodel.webViewNavigationPublisher.receive(on: RunLoop.main).sink(receiveValue: { navigation in
                switch navigation {
                case .backward:
                    if webView.canGoBack {
                        webView.goBack()
                    }
                case .forward:
                    if webView.canGoForward {
                        webView.goForward()
                    }
                case .reload:
                    webView.reload()
                case .load:
                    webView.load(URLRequest(url: URL(string: self.parent.viewmodel.url)!))
                    print("IIIIIIIIIIIIIIIIIIIII  " +  self.parent.viewmodel.url)
                }
                
            })
        }
        
        // This function is essential for intercepting every navigation in the webview
        func webView(_ webView: WKWebView,decidePolicyFor navigationAction: WKNavigationAction,decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
            // Suppose you don't want your user to go a restricted site
            // Here you can get many information about new url from 'navigationAction.request.description'
            decisionHandler(.allow)
        }
    }
}

// MARK: - Extensions
extension WebView.Coordinator: WKScriptMessageHandler {
    func userContentController(_ userContentController: WKUserContentController,didReceive message: WKScriptMessage) {
        // Make sure that your passed delegate is called
        if message.name == "iOSNative" {
            if let body = message.body as? [String: Any?] {
                delegate?.receivedJsonValueFromWebView(value: body)
            } else if let body = message.body as? String {
                delegate?.receivedStringValueFromWebView(value: body)
            }
        }
    }
}

浏览器页面视图

import SwiftUI

struct browserPage: View,Identifiable{
  
    let id: UUID = UUID()
    
    @Observedobject var viewmodel = Webviewmodel()
    
    var body: some View {
        vstack{
            URLBarView(viewmodel: viewmodel)
            
            HStack{
                WebView(viewmodel: viewmodel)
            }
            
            BottomNavBarView(viewmodel: viewmodel)
                .padding(.bottom,25)
           
        }
        
        
    }
}

解决方法

暂无找到可以解决该程序问题的有效方法,小编努力寻找整理中!

如果你已经找到好的解决方法,欢迎将解决方案带上本链接一起发送给小编。

小编邮箱:dio#foxmail.com (将#修改为@)