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?
Post
Replies
Boosts
Views
Activity
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...
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)?
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?
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?
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...
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 Matt,
when I try to use your solution
struct MyTransferable: some Transferable {
the compiler chokes on the "some" keyword:
'some' types are only permitted in properties, subscripts, and functions
How did you get this to compile?
Thanks, Marc
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
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?
Here is the github project: https://github.com/Fesh-com/Sharing-Extension
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...)
I ran into almost the same problem. Luckily I didn't increment the major number, but only the minor. However, since we're an Open Source project of course the major is "0" (though we are considering going to 1.0.0 - maybe next year), and thus our minor also had to increase (from 0.9.x to 0.10.0). Since then I really take care to check the version and build numbers before submitting to Testflight...
Same problem here. I still got an SE 1st gen (iOS 15.7) and an XS Max (iOS 16.7.1) which I can use for fast debugging, but my brand new 15 Pro Max is slow as molasses (same app), whether I plug in the cable or not.
From the docs:
Important
Before configuring your app to use Tap to Pay on iPhone, you must set up the necessary entitlement. For more information, see Setting up the entitlement for Tap to Pay on iPhone.
https://developer.apple.com/documentation/proximityreader/setting-up-the-entitlement-for-tap-to-pay-on-iphone