Share arbitrary struct from HostApp to Extension and get arbitrary result back

I want to share a Transferable (JSON-encoded) data struct from some HostApp with an Extension of my ContainingApp, and get a different Transferable (also JSON-encoded) data struct back on success.

Since I want to present my ContainingApp's AppIcon in the sharing sheet to make it easy for the user to find it, I started building a Sharing Extension and not an Action Extension. AFAIK the difference is only in the presentation (Sharing Extension: Icon+name of the ContainingApp vs Action Extension: simple b/w system icon plus a string describing the action (e.g. "Copy", "Save to Files")), and the data flow is identical. Please correct me if I'm wrong.

I added the Sharing Extension to my ContainingApp (which are both in the same app group so they can use a shared container to exchange data). The (real) HostApp is from a different company we are collaborating with, and thus is not in our app group. Once everything runs I will add a tailored NSExtensionActivationRule to make sure our Sharing Extension is only shown to our partner's HostApp. Currently I am still using TRUEPREDICATE. The goal is that after the user tapped the "Continue with ContainingApp" (Share-)button in the HostApp, iOS will only show my ContainingApp icon and nothing else, since that's the only useful choice for the user.

Side Question 1: The best user experience would be if the HostApp could directly present our extension when the user tapped the "Continue with ContainingApp"-button, without the user needing to choose it manually in the Share-sheet, but I guess this is not possible for privacy/security reasons, right?

In the debugger of the HostApp I see this error:

	Type "com.myapp.shareInput" was expected to be exported in the Info.plist of Host.app, but it was imported instead.
		Library: UniformTypeIdentifiers | Subsystem: com.apple.runtime-issues | Category: Type Declaration Issues

but I definitely want to define and export both ShareInput and ShareResult as UTExportedTypeDeclarations in my extension, and all 3rd-party apps (like this demo HostApp) using my extension need to import them.

Side Question 2: Can I just ignore this error? And tell the 3rd-party app developers they also can ignore it?

After the user tapped on the ContainingApp icon in the sharing dialog, my Sharing Extension will show its dialog, presenting the shared item it got from the HostApp, and let the user edit the text.

When the user taps the "Save"-button in my extension, it creates a ShareResult struct to send back to the HostApp, and dismisses the sheet.

This (kinda) works when I share plain text with the 􀈂Text button in my HostApp. My ContainingApp icon is shown together with Mail, Messages, and other apps that can process plain text; with shortcuts to persons and devices (AirDrop targets) in the line above, and with actions (Copy, New Quick Note, Save to Files, Save to Citator, Certificat, Airdrop) below.

When I choose my ContainingApp, the extension runs and shows the text it got. ("Kinda" because I am still struggling to send data back. See below...) So the principal operation works...

Side Question 3: In the HostApp, can I use ShareLink() to present the Share-sheet and receive the result struct or do I always need to

        activityViewController!.completionWithItemsHandler = completionHandler
		windowScene.keyWindow?.rootViewController?.present(activityViewController!, animated: true, completion: nil)

and process the result in the completionHandler?

If returning (any) data from the extension is possible with ShareLink() also, then how? I didn't find any sample showing this...

I implemented the ShareLink() anyway (and ignore the result part for the moment).

When I try to share a ShareInput struct with the 􀈂ShareLink button, the same persons are sorted differently, there are less app icons (9 instead of 13), and less actions (only 3: New Quick Note, Save to Files, AirDrop): Note that while the preview correctly shows the preview text provided ("shareInput"), the preview image left of it is blank (instead of arrowshape.right.fill):

	        let preview = SharePreview("shareInput", image: Image(systemName: "arrowshape.right.fill"))

When I choose my ContainingApp, the extension runs ...

On iOS17, I see that indeed my ShareInput data arrived in my extension:

	❗️itemProvider=<NSItemProvider: 0x301b1c460> {types = (
	    "com.myapp.shareInput"
	)}
		Library: ShareExtension | Subsystem: com.myapp.containingdemo.ShareExtensionDemo | Category: ShareSheet

However, on iOS 16 it doesn't work:

	Host[8615:634470] [Type Declaration Issues] Type "com.myapp.shareInput" was expected to be exported in the Info.plist of Host.app, but it was imported instead.
	Host[8615:634462] [ShareSheet] Couldn't load file URL for Collaboration Item Provider:<NSItemProvider: 0x280f49180> {types = (
    		"com.myapp.shareInput"
	)} : (null)

That error is shown before I choose the ContainingApp to share with. When I do that, I get:

	ShareExtension[8774:636786] [ShareSheet] ❗️itemProvider=<NSItemProvider: 0x28243a300> {types = (
	    "dyn.age8u",
	    "public.file-url"
	)}

which clearly shows the ShareInput struct was not transferred to the extension.

But since I don't know how to transfer the ShareResult back to the HostApp when using a ShareLink, I cannot continue this approach anyway.

When I try to share a ShareInput struct with the 􀈂JSON button (using present(activityViewController)), I see (both on iOS 16 and iOS 17):

My extension (rather, the ContainingApp's icon) is not shown as Sharing target (even though it still has TRUEPREDICATE), which means that my code didn't manage to pack the ShareInput struct for the activityViewController - and thus it doesn't know what to share.

I did the same as with the plainText item before:

        let shareInput = ShareInput(inputStr: "ShareInput as JSON")
        let preview = SharePreview("shareInput", image: Image(systemName: "arrowshape.right.fill"))
        VStack(spacing: 25.0) {
            Text("HostApp!")
            ShareButton(title: "Text", shareItems: [ItemSource(dataToShare: "sharing some text")])
            ShareButton(title: "JSON", shareItems: [ItemSource(dataToShare: shareInput)])
            ShareLink("ShareLink", item: shareInput, preview: preview)
        }

(I will continue in the next posting)

When I share both JSON and plain text:

            ShareButton(title: "JSON", shareItems: [ItemSource(dataToShare: shareInput)
                                                   ,ItemSource(dataToShare: "sharing some text")
                                                   ])

I get (iOS 16 vs. iOS 17):

where iOS 16

	ShareExtension[9236:652213] [ShareSheet] ❗️itemProvider=<NSItemProvider: 0x280afa1b0> {types = (
	    "public.plain-text"
	)}

and iOS 17 transfer:

	❗️itemProvider=<NSItemProvider: 0x300f99e30> {types = (
	    "public.plain-text"
	)}
		Library: ShareExtension | Subsystem: com.myapp.containingdemo.ShareExtensionDemo | Category: ShareSheet

So again it's clear that the ShareInput is not transferred, but only the text.

For the result, I didn't manage to pass a ShareResult struct to NSItemProvider:

I just found

        NSItemProvider(item: contactData as NSSecureCoding, typeIdentifier: UTType.vCard.identifier)

which probably works because contactData is known to iOS...

See this thread: https://forums.developer.apple.com/forums/thread/24368

Again, transferring plain text was possible...

Main Question: How can I transfer my ShareInput struct from the HostApp to the extension (iOS 16 and later), and then my ShareResult struct from the extension back to the HostApp (iOS 16 and later)?

And how can the HostApp limit the possible sharing targets? I want neither persons nor actions to appear in the sharing dialog, just apps - preferably only my ContainingApp. This should be possible with a strict NSExtensionActivationRule, right?

(Bummer, it is not possible to upload the zipped demo project. Thus I'll need to upload to github...)

Here is the github project: https://github.com/Fesh-com/Sharing-Extension

Sorry for the delayed response @marcvienna . I was looking into your questions:

In the debugger of the HostApp I see this error: Type "com.myapp.shareInput" was expected to be exported in the Info.plist of Host.app, but it was imported instead. Library: UniformTypeIdentifiers | Subsystem: com.apple.runtime-issues | Category: Type Declaration Issues

You're seeing the error because "com.fesh.shareInput" wasn't added in the app's info.plist, keep in mind that If both imported and exported declarations for a uniform type identifier exist, the exported declaration takes precedence over the imported declaration.

To add the exported and imported type declarations in the app’s Info.plist:

  • In Xcode’s project editor, choose the project or target, then click Info.
  • In the editor area, expand the Exported Type Identifiers then click the Add button (+).

Side Question 3: In the HostApp, can I use ShareLink() to present the Share-sheet and receive the result struct or do I always need to activityViewController!.completionWithItemsHandler = completionHandler >windowScene.keyWindow?.rootViewController?.present(activityViewController!, animated: true, completion: nil) and process the result in the completionHandler?

You could use ShareLink to present the share interface. For the imported content type, Transferable. handles the CodableRepresentation handles the conversion of the model type to and from binary data. However in some cases you would need to implement the closure that instantiates the item inother to convert the binary data to a type that your app is aware of For example, A file representation for transferring the profile's video:

        FileRepresentation(contentType: .mpeg4Movie) { profile in
            guard let videoURL = profile.video else {
                throw TransferError.exportFailed
            }
            return SentTransferredFile(videoURL)
        } importing: { received in
            let destination = try Profile.copyVideoFile(source: received.file)
            return Profile(video: destination)
        }

When you define a uniform type identifier, remember you need to declare it to be either an imported or exported type:

  • An exported type declaration is declared and owned by your application. For example, the Codable schema of ShareInput is an exported content type com.fesh.shareInput. Your application is the source of truth about given type, and it is also the primary handler of files with the corresponding file extension.

  • An imported type declaration is owned by some other app, that may not be present on the device when your app is installed.

For example, suppose an image editing app creates files using a proprietary format whose uniform type identifier is declared in its application bundle. If you are writing an application or plugin that would read such files, you must make sure that the system knows about the proprietary uniform type identifier, even if the actual image editing application is not available. To do so, your application would declare the uniform type identifier in its Info.plist as imported.

And how can the HostApp limit the possible sharing targets? I want neither persons nor actions to appear in the sharing dialog, just apps - preferably only my ContainingApp. This should be possible with a strict NSExtensionActivationRule, right?

Correct, NSExtensionActivationRule controls the system filtering the available share extensions the user can choose based on knowing that the share extension has declared support for some Type Identifier in the payload. I suspect the issue you might be facing is that the type identifier isn't granular enough. For example, json UTType also conforms to UTTypeText which represents text with markup.

Hi dts, thanks for chiming in.

In the debugger of the HostApp I see this error: Type "com.myapp.shareInput" was expected to be exported in the Info.plist of Host.app, but it was imported instead. Library: UniformTypeIdentifiers | Subsystem: com.apple.runtime-issues | Category: Type Declaration Issues

You're seeing the error because "com.fesh.shareInput" wasn't added in the app's info.plist

But it was added in the Host.app Info.plist - as imported, not exported.

I wrote in my first posting:

I definitely want to define and export both ShareInput and ShareResult as UTExportedTypeDeclarations in my extension, and all 3rd-party apps (like this demo HostApp) using my extension need to import them.

We expect more 3rd party apps to use our extension, which IS the source of truth for these types. So it doesn't make sense for my sample demo Host.app to export them.

However, I will add them just for debugging, to check whether that's the problem.

Side Question 3: In the HostApp, can I use ShareLink() to present the Share-sheet and receive the result struct

You could use ShareLink to present the share interface. For the imported content type, Transferable. handles the CodableRepresentation handles the conversion of the model type to and from binary data.

Both my types are really simple structs, input contains 3 strings and output contains 4. For the simplicity of this demo project I only used 1 string each, just to demonstrate the data passing as Transferable. I could easily pack this data myself as JSON in a single TEXT blob since this seems to work best in my experience up to now, but I really want to use Transferable with CodableRepresentation and understand what is needed to make this work.

However in some cases you would need to implement the closure that instantiates the item inother to convert the binary data to a type that your app is aware of

My data only exists in memory and not as file, thus a file representation doesn't make sense for me.

When you define a uniform type identifier, remember you need to declare it to be either an imported or exported type:

• An exported type declaration is declared and owned by your application. For example, the Codable schema of ShareInput is an exported content type com.fesh.shareInput. Your application is the source of truth about given type, and it is also the primary handler of files with the corresponding file extension.

Actually, my extension is the source of truth about both structs. I didn't define file extensions because the data is only in memory. It doesn't make sense to share that data via Mail, Messenger or AirDrop with another device. The only usecase is that both the 3rd party HostApp and my ContainingApp are installed on the same iPhone, and the HostApp uses my SharingExtension to communicate with my ContainingApp without switching context.

An imported type declaration is owned by some other app, that may not be present on the device when your app is installed.

We want to create a demo to show 3rd party developers how to use our SharingExtension. I understand that the 3rd party HostApp thus needs to import the types to be able to make Transferable work.

In my demo project: I imported the types both in the Host.App as well as in the Containing.App, and exported them in the ShareExtension.

I tried to export them from the Containing.App instead (and removed the declaration from the Extension's Info.plist) - but that lead to this error:

Type "com.fesh.shareInput" was expected to be declared and exported in the Info.plist of ShareExtension.appex, but it was not found.

Thus I added the export declaration back to the ShareExtension's Info.plist, and that error vanished.

Now I am exporting both types, both from the ContainingApp as well as from it's ShareExtension. Is that really OK?

OK dts, you gave some hints about the declaration. I doubt changing the declaration in my ContainingApp from import to export really makes sense, because now there are TWO parties claiming to own the data (== exporting): The ShareExtension and the ContainingApp.

Anyway, it doesn't change the behaviour of my sample project at all. I still have the same problems:

• When I share via ShareLink, in my Extension I see the data arriving, can load it and get no error (see ShareViewController, line 50ff):

if itemProvider.hasItemConformingToTypeIdentifier(shareInputType) {
  itemProvider.loadItem(forTypeIdentifier: shareInputType , options: nil) { [weak self] (input, error) in
    guard error == nil else { self?.cancel(error: error!); return }

however, I cannot convert that data into my ShareInput struct:

    if let shareInput = input as? ShareInput {
      let text = shareInput.inputStr

A breakpoint on that last line shows I never arrive there (but I do arrive on the line above starting with "if"). What am I doing wrong here?

• When I try to share via UIActivityViewController, then Transferable doesn't work. In the Share-Sheet, my ContainingApp is not shown as target to share to, thus the shared data was not converted correctly.

Text works:

     ShareButton(title: "Text", shareItems: [ItemSource(dataToShare: "sharing some text")])

but my struct doesn't:

    ShareButton(title: "JSON", shareItems: [ItemSource(dataToShare: shareInput)
//                                   ,ItemSource(dataToShare: "sharing some text")
                                             ])

When I uncomment the text item, then my ContainingApp again is shown in the ShareSheet as share target, but it receives only the text, not the shareInfo struct.

The main problems are still unsolved:

  • How can I encode/convert the ShareInput struct for UIActivityViewController?
  • When using ShareLink, the encoding/conversion works. But how can I decode/convert the data to a ShareInput struct in my ShareExtension?
  • And finally, how do I transfer the ShareResult back to the HostApp? ShareLink has no return path, or does it? And this again will need an encoding conversion in the ShareExtension and a decoding conversion in the HostApp. How do I do that?

Do you have any sample code showing this? If not, please help me with this demo project and I am happy to pass it to you to make an official demo from it.

Thanks, Marc

I was able to decode the data to a ShareInput struct in my ShareExtension

    let shareInput = try JSONDecoder().decode(ShareInput.self, from: input as! Data)

See the updated github project: https://github.com/Fesh-com/Sharing-Extension

Still don't know how to transfer the ShareResult back to the HostApp...

Hi @marcvienna ,

First, theses are great questions you have so I'll try to get to all of them as best as I can.

You wrote:

When I share via ShareLink, in my Extension I see the data arriving, can load it and get no error (see ShareViewController, line 50ff): if itemProvider.hasItemConformingToTypeIdentifier(shareInputType) { itemProvider.loadItem(forTypeIdentifier: shareInputType , options: nil) { [weak self] (input, error) in guard error == nil else { self?.cancel(error: error!); return } however, I cannot convert that data into my ShareInput struct: if let shareInput = input as? ShareInput { let text = shareInput.inputStr A breakpoint on that last line shows I never arrive there (but I do arrive on the line above starting with "if"). What am I doing wrong here?

First I would suggest you debug the shareExtension process throughout the process by attaching the debugger to the process, so you could track the breakpoints. See, Diagnosing issues in the appearance of a running app

In the host target, define "com.fesh.shareInput" as an exported type identifier by clicking on Target -> Info -> Exported Type Identifier. I wouldn't suggest you add it directly into the generated info.plist file as those get re-generated by Xcode based on your Target.

For types owned by your application/ Target, define them as an exported type declaration.

For types owned by other Apps or targets that may not be present on the device when your app is installed declare them as an imported type declaration.

You also wrote:

When using ShareLink, the encoding/conversion works. But how can I decode/convert the data to a ShareInput struct in my ShareExtension?

Since ShareInput model conforms to Transferable and contains a transfer representation, then you should use loadTransferable(type:completionHandler:) method or loadDataRepresentation(forTypeIdentifier:completionHandler:) < https://developer.apple.com/documentation/foundation/nsitemprovider/2888331-loaddatarepresentation >

struct ShareInput: Codable {
    var inputStr1: String
    var inputStr2: String
    var inputStr3: String
    // ... more input data
}

extension ShareInput: Transferable {
    static var transferRepresentation: some TransferRepresentation {
        CodableRepresentation(contentType: .shareInput)
    }
}
        _ = itemProvider.loadDataRepresentation(for: .shareInput) { data, error in
                 if let error = error {
                     self.logger.log("---->failure \(error)")
                     return
                 }
                 
                 guard let data = data else {
                     self.logger.log("---->Empty data.")
                     return
                 }
                 
                 do {
                     let shareInput = try? JSONDecoder().decode(ShareInput.self, from: data)
                     self.logger.log("---->success \(shareInput.debugDescription)")

                 } catch {
                     self.logger.log("---->failed to decode data \(error).")
                 }
             }
            
            _ = itemProvider.loadTransferable(type: ShareInput.self) { [weak self] result in
                  guard let self = self else { return }
                  
                  switch result {
                  case .success(let success):
                      self.logger.log("---->Success ")
                  case .failure(let error):
                      self.logger.log("---->failure")
                  }
              }

You wrote:

And finally, how do I transfer the ShareResult back to the HostApp? ShareLink has no return path, or does it? And this again will need an encoding conversion in the ShareExtension and a decoding conversion in the HostApp. How do I do that?

As far as I'm aware, you can't. You could use alternative data sharing methods such as a shared App groups container.

@marcvienna You wrote:

How can I encode/convert the ShareInput struct for UIActivityViewController?

Use UIActivityViewController(activityItemsConfiguration:) initializer and provide items conforming to NSItemProviderWriting instead.

For example:

struct ActivityViewController: UIViewControllerRepresentable {
    var activityItemsConfiguration: UIActivityItemsConfigurationReading

    func makeUIViewController(context: Context) -> UIActivityViewController {
        return UIActivityViewController(activityItemsConfiguration: activityItemsConfiguration)
    }
    
    func updateUIViewController(_ uiViewController: UIActivityViewController, context: Context) {
    }
    
    struct ActivityView: View {
    @State private var isShowingActivityView = false
    let customItem = ItemSource(text: "Hello, Custom World!")

    var body: some View {
        VStack {
            Button(action: {
                isShowingActivityView = true
            }) {
                Text("Share")
            }
            .sheet(isPresented: $isShowingActivityView) {
                let activityItemsConfiguration = UIActivityItemsConfiguration(objects: [customItem])
                    ActivityViewController(activityItemsConfiguration: activityItemsConfiguration)
                
            }
        }
    }
}
class ItemSource: NSObject, NSItemProviderWriting, NSItemProviderReading, Codable {
    let text: String
    
    init(text: String) {
        self.text = text
    }
    
    static var writableTypeIdentifiersForItemProvider: [String] {
        return [UTType.data.identifier]
    }
    
    func loadData(withTypeIdentifier typeIdentifier: String, forItemProviderCompletionHandler completionHandler: @escaping (Data?, Error?) -> Void) -> Progress? {
        let data = try? JSONEncoder().encode(self)
        completionHandler(data, nil)
        return nil
    }
    
    static var readableTypeIdentifiersForItemProvider: [String] {
        return [UTType.data.identifier]
    }
    
    static func object(withItemProviderData data: Data, typeIdentifier: String) throws -> Self {
        let customItem = try JSONDecoder().decode(self, from: data)
        return customItem
    }
}

Within your ShareExtension in ShareViewController.swift, you could then decode the itemProvider.loadDataRepresentation:

    private func handleItemProvider(_ itemProvider: NSItemProvider) {
        // Define type identifiers
        let shareInputType = UTType("com.example.shareinput")?.identifier ?? ""
        let textTypeIdentifiers = [UTType.plainText.identifier, UTType.text.identifier, UTType.shareInput.identifier, UTType.data.identifier]
        if textTypeIdentifiers.contains(where: itemProvider.hasItemConformingToTypeIdentifier) {
            _ = itemProvider.loadDataRepresentation(for: .data) { [weak self] data, error in
                guard let self = self else { return }
                
                if let error = error {
                    self.logger.log("Failed to load data representation: \(error)")
                    return
                }
                
                guard let data = data else {
                    self.logger.log("Data is empty.")
                    return
                }
                
                do {
                    let customItem = try JSONDecoder().decode(ItemSource.self, from: data)
                    self.logger.log("Successfully decoded customItem: \(customItem.text)")
                } catch {
                    self.logger.log("\(error.localizedDescription)")
                }
            }
        } else {
            self.logger.log("Item provider does not conform to expected type.")
        }
    }

Hi Emmanuel, thanks for your answers.

In the host target, define "com.fesh.shareInput" as an exported type identifier

But this is NOT defined by the host (there can and will be dozens of different host apps using my ShareExtension in the future). The user may have multiple of those 3rd party apps, and they could have a different idea what the data looks like. The real source of truth is only my ShareExtension. So why should I (or rather the 3rd party developers that want to use my extension - I am just building the sample for them) define that struct as exported type? Is this really necessary? In my experience it makes no difference for the ShareSheet whether the host defines it as export or import...

And finally, how do I transfer the ShareResult back to the HostApp? ShareLink has no return path, or does it? And this again will need an encoding conversion in the ShareExtension and a decoding conversion in the HostApp. How do I do that?

As far as I'm aware, you can't. You could use alternative data sharing methods such as a shared App groups container.

No, I can't. As I wrote before, the host app(s) will be build by a 3rd party. I definitely need to transfer the data back with the sharing call. If ShareLink cannot do that, then I need to fall back to UIActivityViewController with completionHandler - or do you have another suggestion?

In my sample project, the ShareButton.swift file contains the call to UIActivityViewController. However, when I pass my data (the ShareInput struct), iOS doesn't show my ContainingApp as share target - most probably I didn't pass it correctly. Do I need to encode the struct as JSON?

Hi dts,

using your sample code, I was able to transfer my ShareInput struct from the Host-App to my extension and the ShareResult struct back to the Host-App. See the sample project at github.

If I defined a SharingExtension, then returnedItems was nil. I only got something back in returnedItems when I defined an ActionExtension - even though it had the exact same code...

But there are still some problems:

Your sample used NSItemProviderWriting, NSItemProviderReading for the ItemSource. How can I add a preview image and text (so users know what they’re sharing)?

With ShareLink, I can specify a SharePreview:

  let previewTitle = "Choose the ContainingApp"
  let image = UIImage(systemName: "arrowshape.right.fill")
  let preview = SharePreview(previewTitle, image: Image(uiImage: image!))
  ShareLink("ShareLink", item: shareInput, preview: preview)

and get this:

but since ShareLink cannot receive data sent back from the extension, I need to use a UIActivityViewController with completionWithItemsHandler:

  let itemSource = InputItemSource(shareInput: shareInput, previewTitle: previewTitle, previewImage: image!)
  let configuration = UIActivityItemsConfiguration(objects: [itemSource])
  ActivityViewController(configuration: configuration)
      .presentationDetents([.medium])

As you can see, there’s no preview image and text:

I tried to extend the ItemSource class to the UIActivityItemSource protocol:

extension InputItemSource: UIActivityItemSource {
    func activityViewController(_ activityViewController: UIActivityViewController,
              thumbnailImageForActivityType activityType: UIActivity.ActivityType?,
                                      suggestedSize size: CGSize
    ) -> UIImage? {
        previewImage.image
    }

    func activityViewControllerPlaceholderItem(_ activityViewController: UIActivityViewController) -> Any {
        previewTitle
    }

and even added

    func activityViewControllerLinkMetadata(_ activityViewController: UIActivityViewController) -> LPLinkMetadata? {
        let metaData = LPLinkMetadata()
        metaData.title = previewTitle
        metaData.imageProvider = NSItemProvider(object: previewImage.image)
        return metaData
    }

but this didn’t work - the share view still doesn’t show a preview.

How can I add a preview image and text (so users know what they’re sharing)?

Second, SwiftUIs ShareLink is able to use the transferRepresentation of my ShareInput and ShareResult structs. I can call itemProvider.loadTransferable to extract my data.

However, when using an ItemProvider for NSItemProviderWriting, NSItemProviderReading for the UIActivityItemsConfiguration, the transferred item is not a ShareInput or ShareResult struct, but the ItemProvider as wrapper around my struct. Probably because the transferred item must inherit from NSObject.

Thus I cannot use UTType.shareInput.identifier, but must export and import the wrapper UTType.inputItemSource.identifier instead. While this works, it is ugly!

Is there a possibility to transfer a struct with a UIActivityItemsConfiguration, or do I need to convert my ShareInput and ShareResult structs into classes, and then add NSItemProviderWriting, NSItemProviderReading with an extension? How does ShareLink do this? It transfers the struct (which is no descendant of NSObject) and not a wrapper...

Third, I played around with the NSExtensionActivationRule. While I can easily hide my extension (and thus the ContainingApp) from being shown in the ShareSheet, I cannot prevent Freeform and Threema in the application row, and “New Quick Note”, "Save to Files”, and “Airdrop” being shown when the HostApp opens the ShareSheet:

How can the HostApp limit the possible share targets to just my ActionExtension and nothing else?

Share arbitrary struct from HostApp to Extension and get arbitrary result back
 
 
Q