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

Posted

技术标签:

【中文标题】如何在 SwiftUI 中为 WKWebView 创建浏览器选项卡【英文标题】:How to create a browser tab for WKWebView in SwiftUI 【发布时间】:2021-03-28 18:57:54 【问题描述】:

我正在尝试构建一个新的选项卡功能,但我不太确定如何实现这一点。我在设置新的或以前的 WKWebView 时遇到问题。如果 url 无效,我如何显示 errorView?

这是我目前所拥有的。

编辑:我不太确定如何初始化或如何创建无效 URL 视图。这有点像我的想法

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)
             elseInvalidURL()
            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")!))
    

【问题讨论】:

【参考方案1】:

下面是一个比较简单的实现(先代码,后解释):


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 ?? "?")
                    
                
            
        
        
    

我没有尝试存储 NavigationStates 的数组,而是重构了 NavigationState 以保存 Web 视图的数组。当前 URL 和选定的 Web 视图是 @Published 值,以便父视图可以看到 URL、选定的视图等。

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

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

关于显示无效 URL 的错误,这实际上是第二个问题,可能需要更清楚(什么构成无效 URL?它来自哪里?你的意思是如果用户输入一个(在 UI 的某些部分你不是在描述)或者如果他们点击了页面上的无效链接?)

【讨论】:

哇,谢谢大家的帮助!我真的是 SwiftUI 的新手,我从你那里学到了很多东西 :) 嗨,詹姆斯——很高兴为您提供帮助。请在有机会时将答案标记为正确,并且(如果没有)继续,如果有帮助,请给它一个支持 完成!同样对于无效的 url 是在提交时加载 url 的文本输入。我想知道如何检查 url 是否有效,并决定是否应该显示我的 invalidURLView 或我的 WKWebView。还有没有办法在初始化时创建一个选项卡? URL(string:) 返回一个可选项(现在,您必须使用! 强制解包)。在您的用户输入中,如果它返回 nil,您可能会出现错误。关于选项卡,现在有一个空的 WebView 数组。如果您为 NavigationState 创建一个 init 方法,您可以为您的第一个选项卡预先填充一个初始值(确保也加载请求)。 将您的override init() 调用中的所有内容包装在super.init() 下方的DispatchQueue.main.async 块中。我不确定为什么在这种情况下它需要它,但它确实有效。

以上是关于如何在 SwiftUI 中为 WKWebView 创建浏览器选项卡的主要内容,如果未能解决你的问题,请参考以下文章

如何在 SwiftUI 中观察 WKWebView 的加载进度?

SwiftUI 中的 WKWebView - 用户与网站交互时如何切换视图?

如何在 SwiftUI 中创建一个列表视图,每个列表项都是一个 WKWebView(我的实现非常慢并且有问题)

如何阻止 SwiftUI 重新加载我通过 UIViewRepresentable 使用的 WKWebView 页面?

SwiftUI WebKit WKWebView:如何支持多 url

在 SwiftUI 中关闭 WKWebView