'SwiftUI using NSSharingServicePicker in MacOS

I am trying to use a Share function inside my MacOS app in SwiftUI. I am having a URL to a file, which I want to share. It can be images/ documents and much more.

I found NSSharingServicePicker for MacOS and would like to use it. However, I am struggeling to use it in SwiftUI.

Following the documentation, I am creating it like this:

let shareItems = [...]

let sharingPicker : NSSharingServicePicker = NSSharingServicePicker.init(items: shareItems as [Any])

sharingPicker.show(relativeTo: NSZeroRect, of:shareView, preferredEdge: .minY)

My problem is in that show() method. I need to set a NSRect, where I can use NSZeroRect.. but I am struggeling with of: parameter. It requires a NSView. How can I convert my current view as NSView and use it that way. Or can I use my Button as NSView(). I am struggling with that approach.

Another option would be to use a NSViewRepresentable. But should I just create a NSView and use it for that method.



Solution 1:[1]

Here is minimal working demo example

demo

struct SharingsPicker: NSViewRepresentable {
    @Binding var isPresented: Bool
    var sharingItems: [Any] = []

    func makeNSView(context: Context) -> NSView {
        let view = NSView()
        return view
    }

    func updateNSView(_ nsView: NSView, context: Context) {
        if isPresented {
            let picker = NSSharingServicePicker(items: sharingItems)
            picker.delegate = context.coordinator

            // !! MUST BE CALLED IN ASYNC, otherwise blocks update
            DispatchQueue.main.async {
                picker.show(relativeTo: .zero, of: nsView, preferredEdge: .minY)
            }
        }
    }

    func makeCoordinator() -> Coordinator {
        Coordinator(owner: self)
    }

    class Coordinator: NSObject, NSSharingServicePickerDelegate {
        let owner: SharingsPicker

        init(owner: SharingsPicker) {
            self.owner = owner
        }

        func sharingServicePicker(_ sharingServicePicker: NSSharingServicePicker, didChoose service: NSSharingService?) {

            // do here whatever more needed here with selected service

            sharingServicePicker.delegate = nil   // << cleanup
            self.owner.isPresented = false        // << dismiss
        }
    }
}

Demo of usage:

struct TestSharingService: View {
    @State private var showPicker = false
    var body: some View {
        Button("Share") {
            self.showPicker = true
        }
        .background(SharingsPicker(isPresented: $showPicker, sharingItems: ["Message"]))
    }
}

backup

Solution 2:[2]

Another option without using NSViewRepresentable is:

extension NSSharingService {
    static func submenu(text: String) -> some View {
        return Menu(
            content: {
                ForEach(items, id: \.title) { item in
                    Button(action: { item.perform(withItems: [text]) }) {
                        Image(nsImage: item.image)
                        Text(item.title)
                    }
                }
            },
            label: {
                Image(systemName: "square.and.arrow.up")
            }
        )
    }
}

You lose things like the "more" menu item or recent recipients. But in my opinion it's more than enough, simple and pure SwiftUI.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2 Daniel