macossafariswiftuiwkwebviewfinder

How do I get the file open dialog to work, when called through a SwiftUI WKWebView inside a macOS native app?


I'm currently running a simplistic WkWebView made in SwiftUI - with all the advice that I was able to glimpse from this StackOverflow post. It opens up a website. The website has a browser button. If I click on the browser button on Safari, a Finder window opens. If I do the same thing inside the .app created with Xcode, a Finder window does not open.

This is the stuff I've enabled in the Sandbox:

XCode App Sandbox settings

The code that I use is this (all from ContentView.swift)

import SwiftUI
import WebKit


public struct WebBrowserView {

    private let webView: WKWebView = WKWebView()

    // ...

    public func load(url: URL) {
        webView.load(URLRequest(url: url))
    }

    public class Coordinator: NSObject, WKNavigationDelegate, WKUIDelegate {

        var parent: WebBrowserView

        init(parent: WebBrowserView) {
            self.parent = parent
        }

        public func webView(_: WKWebView, didFail: WKNavigation!, withError: Error) {
            // ...
        }

        public func webView(_: WKWebView, didFailProvisionalNavigation: WKNavigation!, withError: Error) {
            // ...
        }

        public func webView(_: WKWebView, didFinish: WKNavigation!) {
            // ...
        }

        public func webView(_ webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!) {
            // ...
        }

        public func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
            decisionHandler(.allow)
        }

        public func webView(_ webView: WKWebView, createWebViewWith configuration: WKWebViewConfiguration, for navigationAction: WKNavigationAction, windowFeatures: WKWindowFeatures) -> WKWebView? {
            if navigationAction.targetFrame == nil {
                webView.load(navigationAction.request)
            }
            return nil
        }
    }

    public func makeCoordinator() -> Coordinator {
        Coordinator(parent: self)
    }
}


#if os(macOS) // macOS Implementation (iOS version omitted for brevity)
extension WebBrowserView: NSViewRepresentable {

    public typealias NSViewType = WKWebView

    public func makeNSView(context: NSViewRepresentableContext<WebBrowserView>) -> WKWebView {

        webView.navigationDelegate = context.coordinator
        webView.uiDelegate = context.coordinator
        return webView
    }

    public func updateNSView(_ nsView: WKWebView, context: NSViewRepresentableContext<WebBrowserView>) {

    }
}
#endif

struct BrowserView: View {

    private let browser = WebBrowserView()

    var body: some View {
        HStack {
            browser
                .onAppear() {
                    self.browser.load(url: URL(string: "http://specificwebsitewithbrowserbutton")!)
                }
        }
        .padding()
    }
}


struct ContentView: View {

    @State private var selection = 1

    var body: some View {
                BrowserView()
                .frame(maxWidth: .infinity, maxHeight: .infinity)
    }
}

So, how do I give this .app enough permissions so that a Finder window will open when I click a "Upload" button on the website?


Solution

  • You have to implement the following WKUIDelegate delegate method

       /** @abstract Displays a file upload panel.
         @param webView The web view invoking the delegate method.
         @param parameters Parameters describing the file upload control.
         @param frame Information about the frame whose file upload control initiated this call.
         @param completionHandler The completion handler to call after open panel has been dismissed. Pass the selected URLs if the user chose OK, otherwise nil.
        
         If you do not implement this method, the web view will behave as if the user selected the Cancel button.
         */
        @available(OSX 10.12, *)
        optional func webView(_ webView: WKWebView, runOpenPanelWith 
                      parameters: WKOpenPanelParameters, initiatedByFrame frame: WKFrameInfo, 
                      completionHandler: @escaping ([URL]?) -> Void)
    

    Here is example of implementation

    func webView(_ webView: WKWebView, runOpenPanelWith parameters: WKOpenPanelParameters, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping ([URL]?) -> Void) {
        let openPanel = NSOpenPanel()
        openPanel.canChooseFiles = true
        openPanel.begin { (result) in
            if result == NSApplication.ModalResponse.OK {
                if let url = openPanel.url {
                    completionHandler([url])
                }
            } else if result == NSApplication.ModalResponse.cancel {
                completionHandler(nil)
            }
        }
    }