如何在SwiftUI中为WKWebView创建浏览器选项卡

如何在SwiftUI中为WKWebView创建浏览器选项卡,swiftui,Swiftui,我试图建立一个新的标签功能,但我不太确定如何才能做到这一点。设置新的或以前的WKWebView时遇到问题。如果url无效,如何显示errorView 这就是我目前所拥有的 编辑:我不太确定如何初始化或如何创建invalidurl视图。这有点像我脑子里在想什么 class NavigationState : NSObject, ObservableObject { @Published var url : URL? let webView = WKWebView() } exte

我试图建立一个新的标签功能,但我不太确定如何才能做到这一点。设置新的或以前的WKWebView时遇到问题。如果url无效,如何显示errorView

这就是我目前所拥有的

编辑:我不太确定如何初始化或如何创建invalidurl视图。这有点像我脑子里在想什么

class NavigationState : NSObject, ObservableObject {
    @Published var url : URL?
    let webView = WKWebView()
}

extension NavigationState : WKNavigationDelegate {
    func webView(_ webView: WKWebView, didCommit navigation: WKNavigation!) {
        self.url = webView.url
    }
}

struct WebView : UIViewRepresentable {
    
    let request: URLRequest
    var navigationState : NavigationState
    
    func makeUIView(context: Context) -> WKWebView  {
        let webView = navigationState.webView
        webView.navigationDelegate = navigationState
        webView.load(request)
        return webView
    }
    
    func updateUIView(_ uiView: WKWebView, context: Context) { }
}

struct ContentView: View {
    @StateObject var navigationState = NavigationState()
    @State var tablist = [NavigationState]
    @State var validurl = true;

    init(){
      //does not work currently
      navigationState.createNewWebView(withRequest: URLRequest(url: URL(string: "https://www.google.com")!))
    }
    
    var body: some View {
        VStack(){
            Button("create new tab"){
               tablist.append(navigationState)
               //create and set new webview
            }
            Text(navigationState.url?.absoluteString ?? "(none)")
           if(validUrl){
            WebView(request: URLRequest(url: URL(string: "https://www.google.com")!), navigationState: navigationState)
            } else{InvalidURL()}
            HStack {
                Button("Back") {
                    navigationState.webView.goBack()
                }
                Button("Forward") {
                    navigationState.webView.goForward()
                }
              TextField(){onCommit: {    
                navigationState.selectedWebView?.load(URLRequest(url: URL(string: urlInput)!))
             }}
            }
        }
         List {
                ForEach(tabs, id: \.self) { tab in
                    Button(action: {
                        //set to current webview
                    }, label: {
                        Text(tab.webView.url)
                    })
                }.onDelete(perform: delete)
            }
    }
}
为初始化编辑

我在NavigationState下面添加了这段代码,但是我一直得到一个空白屏幕

override init(){
        super.init()
        let wv = WKWebView()
        wv.navigationDelegate = self
        self.webViews.append(wv)
        self.selectedWebView = wv
        wv.load(URLRequest(url: URL(string: "https://www.google.com")!))
    }

下面是一个相对简单的实现(先编写代码,然后解释):

我没有尝试存储
NavigationState
s数组,而是重构
NavigationState
来保存一个web视图数组。当前URL和所选web视图是@Published值,以便父视图可以看到URL、所选视图等

WebView
必须进行重大更改,因为它必须更新在任何给定时间显示的
WKWebView

这是相当粗糙的边缘代码。如果这是我自己的项目,我会做更多的重构,但这应该会让你开始


关于使用无效URL显示错误,这实际上是第二个问题,可能需要更清楚地说明(无效URL的组成部分是什么?它来自何处?你的意思是用户只需输入一个(在你没有描述的UI的某个部分),还是他们单击页面上的无效链接?

哇,谢谢你的帮助!我对SwiftUI真的很陌生,我从你身上学到了很多:)嗨,詹姆斯,很高兴能帮上忙。当你有机会的时候,请把答案标记为正确的,如果你没有机会的话,请继续进行,如果答案有帮助的话,请投上一票!对于无效url,还有一个在提交时加载url的textinput。我想知道如何检查url是否有效,以及是否应该显示invalidURLView或WKWebView。还有一种方法可以在初始化时创建选项卡吗?
URL(字符串:)
返回一个可选选项(现在,您正在使用
强制展开它)。在用户输入中,如果返回
nil
,则可能会出现错误。关于选项卡,现在有一个空的WebView数组。如果为
NavigationState
创建一个
init
方法,则可以为第一个选项卡预先填充一个初始值(确保也加载请求)。将
override init()
调用中的所有内容包装在
super.init()
下面的
DispatchQueue.main.async{}
块中。我不确定为什么在这种情况下它需要这样,但它是有效的。

class NavigationState : NSObject, ObservableObject {
    @Published var currentURL : URL?
    @Published var webViews : [WKWebView] = []
    @Published var selectedWebView : WKWebView?
    
    @discardableResult func createNewWebView(withRequest request: URLRequest) -> WKWebView {
        let wv = WKWebView()
        wv.navigationDelegate = self
        webViews.append(wv)
        selectedWebView = wv
        wv.load(request)
        return wv
    }
}

extension NavigationState : WKNavigationDelegate {
    func webView(_ webView: WKWebView, didCommit navigation: WKNavigation!) {
        if webView == selectedWebView {
            self.currentURL = webView.url
        }
    }
}

struct WebView : UIViewRepresentable {
    
    @ObservedObject var navigationState : NavigationState
    
    func makeUIView(context: Context) -> UIView  {
        return UIView()
    }
    
    func updateUIView(_ uiView: UIView, context: Context) {
        guard let webView = navigationState.selectedWebView else {
            return
        }
        if webView != uiView.subviews.first {
            uiView.subviews.forEach { $0.removeFromSuperview() }
            
            webView.frame = CGRect(origin: .zero, size: uiView.bounds.size)
            uiView.addSubview(webView)
        }
    }
}

struct ContentView: View {
    @StateObject var navigationState = NavigationState()
    
    var body: some View {
        VStack(){
            Button("create new tab"){
                navigationState.createNewWebView(withRequest: URLRequest(url: URL(string: "https://www.google.com")!))
            }
            Text(navigationState.currentURL?.absoluteString ?? "(none)")
            WebView(navigationState: navigationState)
                .clipped()
            HStack {
                Button("Back") {
                    navigationState.selectedWebView?.goBack()
                }
                Button("Forward") {
                    navigationState.selectedWebView?.goForward()
                }
            }
            
            List {
                ForEach(navigationState.webViews, id: \.self) { tab in
                    Button(action: {
                        navigationState.selectedWebView = tab
                    }) {
                        Text(tab.url?.absoluteString ?? "?")
                    }
                }
            }
        }
        
    }
}