【问题标题】:How to create a browser tab for WKWebView in SwiftUI如何在 SwiftUI 中为 WKWebView 创建浏览器选项卡
【发布时间】: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)
            } 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")!))
    }

【问题讨论】:

    标签: swiftui


    【解决方案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 { } 块中。我不确定为什么在这种情况下它需要它,但它确实有效。
    猜你喜欢
    • 2018-04-08
    • 2020-07-27
    • 2015-12-27
    • 2022-11-14
    • 1970-01-01
    • 1970-01-01
    • 2023-03-21
    • 2021-09-15
    • 2013-07-27
    相关资源
    最近更新 更多