4

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?

esaruoho
  • 896
  • 1
  • 7
  • 25

1 Answers1

8

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)
        }
    }
}
Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
Asperi
  • 228,894
  • 20
  • 464
  • 690
  • Hi @Asperi - do I put this into AppDelegate, or ContentView, please? – esaruoho Jan 19 '20 at 08:15
  • When I put this into ContentView.swift - I get told'optional' can only be applied to protocol members. Replace 'optional' with ''. Then: Expected '{' in body of function declaration – esaruoho Jan 19 '20 at 08:16
  • it should be in `Coordinator` class of your snapshot code – Asperi Jan 19 '20 at 08:18
  • Thanks, now that I placed it inside the `Coordinator` class, it no longer shoots errors. I had to remove optional and replace it with public, and put { and } after the Void). Then I was able to get it to compile. However, when pressing a Browse/Upload button inside the browser, nothing actually happens. So should I have put something inside the { and } to "call something to happen"? – esaruoho Jan 19 '20 at 08:23
  • added example of implementation )) – Asperi Jan 19 '20 at 08:27
  • Wow! @Asperi it works! I'm able to get a browse button to activate and the Finder window opens. I can even select a file and it is uploaded to the website. This is great! I'm marking your answer as the solution :) – esaruoho Jan 19 '20 at 08:31