【问题标题】:Implement webkit with swiftUI on macOS (And create a preview of a webpage)在 macOS 上使用 swiftUI 实现 webkit(并创建网页的预览)
【发布时间】:2020-11-07 17:42:09
【问题描述】:

我正在尝试创建一个显示此站点收件箱的菜单栏应用程序。我想做一个简单的函数,用项目的 url 打开一个小弹出窗口(不打开 safari)。收件箱项目看起来像这样

struct InboxItem: View {
    @State var MesgSite: String = "https://duckduckgo.com"
    @State private var showSafari = false    
   
    var body: some View {
        VStack(alignment: .leading) {
            Text("some text")
        .background(SelectColor.opacity(0.5))
        .onLongPressGesture {
           //show preview of the MesgSite here
            self.SelectColor = .blue
            self.showSafari.toggle()
        }.popover(isPresented: self.$showSafari) {
            SafariPreview()
        }
    }
}

struct SafariPreview: View {

    var body: some View {
        VStack {
            Text("Display the webpage here")
            .padding()
        }.frame(maxWidth: 533, maxHeight: 300)
    }

}

我想,当长按该项目时,它应该像在 macOS 上的默认邮件应用程序中一样预览关联的网页,如下所示:

弹出窗口现在可以工作了

我尝试在NSViewRepresentable 中添加wkwebviewSafariView,但是我使用来自this SO post 的代码收到了(类似的)以下错误消息

Use of undeclared type 'UIViewRepresentable'

TIA!

编辑:

项目最基本的版本可以找到here on github

编辑 2:

更加关注问题

【问题讨论】:

    标签: swift macos swiftui webkit nsviewcontrollerrepresentable


    【解决方案1】:

    我也试图想出一个解决方案。由于我在网上找不到任何关于此的文档,我将给出我通过反复试验找到的解决方案。

    首先,事实证明UI... 在 macOS 上有对应的名称 NS...。因此,UIViewRepresentable 在 macOS 上将是 NSViewRepresentable。接下来我找到了this SO question,它在macOS 上有一个WKWebview 的例子。通过将该代码与this answer on another SO question 结合起来,我还可以检测到 url 的变化,并知道视图何时完成加载。

    macOS 上的 SwiftUI WebView

    这导致了以下代码。为清楚起见,我建议将其放在不同的文件中,例如 WebView.swift:

    首先,导入需要的包:

    import SwiftUI
    import WebKit
    import Combine
    

    然后创建一个模型来保存您希望能够在 SwiftUI 视图中访问的数据:

    class WebViewModel: ObservableObject {
        @Published var link: String
        @Published var didFinishLoading: Bool = false
        @Published var pageTitle: String
        
        init (link: String) {
            self.link = link
            self.pageTitle = ""
        }
    }
    

    最后,使用NSViewRepresentable 创建struct,这将是WebView() 的HostingViewController,如下所示:

    struct SwiftUIWebView: NSViewRepresentable {
        
        public typealias NSViewType = WKWebView
        @ObservedObject var viewModel: WebViewModel
    
        private let webView: WKWebView = WKWebView()
        public func makeNSView(context: NSViewRepresentableContext<SwiftUIWebView>) -> WKWebView {
            webView.navigationDelegate = context.coordinator
            webView.uiDelegate = context.coordinator as? WKUIDelegate
            webView.load(URLRequest(url: URL(string: viewModel.link)!))
            return webView
        }
    
        public func updateNSView(_ nsView: WKWebView, context: NSViewRepresentableContext<SwiftUIWebView>) { }
    
        public func makeCoordinator() -> Coordinator {
            return Coordinator(viewModel)
        }
        
        class Coordinator: NSObject, WKNavigationDelegate {
            private var viewModel: WebViewModel
    
            init(_ viewModel: WebViewModel) {
               //Initialise the WebViewModel
               self.viewModel = viewModel
            }
            
            public func webView(_: WKWebView, didFail: WKNavigation!, withError: Error) { }
    
            public func webView(_: WKWebView, didFailProvisionalNavigation: WKNavigation!, withError: Error) { }
    
            //After the webpage is loaded, assign the data in WebViewModel class
            public func webView(_ web: WKWebView, didFinish: WKNavigation!) {
                self.viewModel.pageTitle = web.title!
                self.viewModel.link = web.url?.absoluteString as! String
                self.viewModel.didFinishLoading = true
            }
    
            public func webView(_ webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!) { }
    
            public func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
                decisionHandler(.allow)
            }
    
        }
    
    }
    

    这段代码可以如下使用:

    struct ContentView: View {
        var body: some View {
            //Pass the url to the SafariWebView struct.
            SafariWebView(mesgURL: "https://stackoverflow.com/")
        }
    }
    struct SafariWebView: View {
        @ObservedObject var model: WebViewModel
    
        init(mesgURL: String) {
            //Assign the url to the model and initialise the model
            self.model = WebViewModel(link: mesgURL)
        }
        
        var body: some View {
            //Create the WebView with the model
            SwiftUIWebView(viewModel: model)
        }
    }
    

    创建 Safari 预览

    所以现在我们掌握了这些知识,重新创建方便的 safari 预览相对容易。

    为此,请确保将@State private var showSafari = false(当您想要显示预览时切换)添加到将调用预览的视图。

    同时添加.popover(isPresented: self.$showSafari) { ... 以显示预览

    struct ContentView: View {
        @State private var showSafari = false
    
        var body: some View {
            VStack(alignment: .leading) {
                Text("Press me to get a preview")
                .padding()
            }
            .onLongPressGesture {
                //Toggle to showSafari preview
                self.showSafari.toggle()
            }//if showSafari is true, create a popover
            .popover(isPresented: self.$showSafari) {
                //The view inside the popover is made of the SafariPreview
                SafariPreview(mesgURL: "https://duckduckgo.com/")
            }
        }
    }
    

    现在SafariPreview struct 将如下所示:

    struct SafariPreview: View {
        @ObservedObject var model: WebViewModel
        init(mesgURL: String) {
            self.model = WebViewModel(link: mesgURL)
        }
        
        var body: some View {
            //Create a VStack that contains the buttons in a preview as well a the webpage itself
            VStack {
                HStack(alignment: .center) {
                    Spacer()
                    Spacer()
                    //The title of the webpage
                    Text(self.model.didFinishLoading ? self.model.pageTitle : "")
                    Spacer()
                    //The "Open with Safari" button on the top right side of the preview
                    Button(action: {
                        if let url = URL(string: self.model.link) {
                            NSWorkspace.shared.open(url)
                        }
                    }) {
                        Text("Open with Safari")
                    }
                }
                //The webpage itself
                SwiftUIWebView(viewModel: model)
            }.frame(width: 800, height: 450, alignment: .bottom)
                .padding(5.0)
        }
    }
    

    结果如下:

    【讨论】:

      【解决方案2】:

      Chiel 的答案很棒!

      但是,可能有人搜索没有完整浏览器功能的 html 渲染器视图

      适用于 MACOS

      import SwiftUI
      import WebKit
      
      struct WebView: View {
          @Binding var html: String
          
          var body: some View {
              WebViewWrapper(html: html)
          }
      }
      
      struct WebViewWrapper: NSViewRepresentable {
          let html: String
          
          func makeNSView(context: Context) -> WKWebView {
              return WKWebView()
          }
          
          func updateNSView(_ nsView: WKWebView, context: Context) {
              nsView.loadHTMLString(html, baseURL: nil)
          }
      }
      

      用法:

      @State var text = "<html><body><h1>Hello World</h1><p><strong>hell</strong> yeah!</p></body></html>"
      //......
      
      TextField("", text: $text)
              
      Divider()
      
      WebView(html: $text)
          .frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity)
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2022-01-27
        • 2011-07-03
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-01-13
        相关资源
        最近更新 更多