NSItemProvider.loadItemForTypeIdentifier broken from swift

I was just trying out iOS's Action Extensions today. When you create one, Xcode inserts a template extension which just shows the image you pass it in.


This sample fails to run. As it turns out, it's due to NSItemProvider (which bridges the data between the processes)'s loadItemForTypeIdentifier method. This method takes a type identifier (e.g. kUTTypeImage) and some options (nil) as input and invokes a supplied completion handler with the object or an error if it can transform the data in to that type. Anyway, it turns out that NSItemProvider, even when invoked with a demo local image from the simulator's Photos app, is returning an NSURL instead of the expected UIImage object.


So let's check the documentation on that completion block - NSItemProviderCompletionHandler. Pasted here for convenience.


A block that receives the item provider’s data.


Declaration

SWIFT

typealias NSItemProviderCompletionHandler = (NSSecureCoding?, NSError!) -> Void



Discussion

Use this block to receive data from a call to the

loadItemForTypeIdentifier:options:completionHandler:
method. This block takes the following parameters:

item


The item to be loaded. When specifying your block, set the type of this parameter to the specific data type you want. For example, when requesting text data, you might set the type to

NSString
or
NSAttributedString
. The item provider attempts to coerce the data to the class you specify.


error


A pointer to an error object for receiving information about any problems that occurred when loading the data.


This may just be really poorly written documentation, but what it seems to suggest is that I'm supposed to specify "UIImage" for the first argument, and it'll do some magic reflection on the block signature and try to make whatever data it holds internally in to a UIImage somehow. It's kind of like a secretly-generic type.


The problem is that I can't do this with Swift. I can't write a block of type (UIImage?, NSError!)->Void and pass it off as a NSItemProviderCompletionHandler.


The sample code people seem to have had that problem, too. It looks like this should have been exported to Swift as a generic function, e.g:


func loadItemForTypeIdentifier<T:NSSecureCoding>(identifier: String, options: [NSObject:AnyObject]?, completionHandler: ((T?, NSError!)->Void)?)


In the sample code's specific case, we can get an NSData from the NSURL and load that in to a UIImage ourselves, but actually one of the major points of this API is to wrap those conversions. There's never any guarantee that it actually does give us a NSURL (that's just what I've seen from the Photos app), so we'll have to cast to make sure that it's an NSURL before doing all of these failable tasks (and what of the other cases? Can it ever just give us a UIImage? Or maybe a raw NSData? We're in undocumented waters here; who can say what'll happen?).

OK Yes, you can get raw NSData passed in to your completion block with an "image" type identifier. Safari will give you those.


That makes the API even more broken. It only takes NSSecureCoding as an input type, which means your "image" can at least be either an NSURL or an NSData.

As you see. We need to handle NSData, NSURL and all possible data types which untested apps would send as an "image". All those handlings would be properly done in the NSItemProvider, if we could pass a proper closure to loadItemForTypeIdentifier:options:completionHandler: .


As for now this works:

                    let handler: (UIImage?, NSError)->Void = {image, error in
                        //image is UIImage here
                    }
                    itemProvider.loadItemForTypeIdentifier(kUTTypeImage as String, options: nil, completionHandler: unsafeBitCast(handler, NSItemProviderCompletionHandler.self))

Though this usage of unsafeBitCast is really unsafe and fragile, which I wouldn't recommend to use in production apps.


All we can do is to send a Bug Report, and use Objective-C or just wait.

Yes I have filed a bug. Its strange to me that Apple would write such a recent API in such a bizarre way - as far as I know, there is no documented way to reflect block signatures so its fragile anyway. Why not just take a class parameter and give the block an id parameter type? They were trying to be too clever, I think.


Anyway, having looked at it again this internal conversion process is actually central to how NSItemProvider works. Going from the NSUrl/NSData to the UIImage might not be as straightforward as you think - the creator (some other app) can provide its own conversion blocks, which is actually the entire point of this API. I could tell apps to perform an action on some nonsense URL (maybe referencing some internal state which could be presented as many things - an image, text, whatever), and the data will be converted by those blocks on-the-fly as I request it in each form.

It's kind of crazy that this is still an issue. I guess the other part that seems crazy here is the API is so obtuse.


>>> itemProvider, do you have something that represents an image


<<< Yes I do, but I won't tell you if it's a URL, UIImage or NSData or if I can convert between them for you


>>> Ok, can I get it as NSData from Swift


<<< that depends, if you want to just hope that I give you that I might, change the type of the closure and I won't compile


>>> nice!

So is there a solution yet? It's almost 2 years passed and I'm at that exact same problem: I get an image but for my following code I have to know if it's an NSURL or if it's an NSData... Currently I've set it to try to cast the image element to a URL so it works for some apps and for some apps it doesn't.

So is there a solution yet?

I don’t think so. If someone can provide a bug number, I can get more details.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

There is an application on iPhone. The Action Extension is used, it works great on the iPhone, but when you add Destination = iPad, the application starts, works, but the extension does not work.

Error: -[_EXSinkLoadOperator loadItemForTypeIdentifier:completionHandler:expectedValueClass:options:] nil expectedValueClass allowing {( NSURL, NSDictionary _EXItemProviderSandboxedResource, NSUUID, NSDate, NSArray, NSData, NSString, NSNumber NSError UIImage, NSValue )} The error occurs here:

if itemProvider.hasItemConformingToTypeIdentifier(typeImage) { itemProvider.loadItem(forTypeIdentifier: typeImage, options: nil) { item, error in

Doesn't work on a real device, works on simulators. iOS 17.2, XCode 15.2.

On simulator first time the extension works and opens the main app, but on the second attempt the main app is not called. On simulator tested with iPad 6, iPad 9, iPad 10, iPad Air 6. On real iPad 6 does not work at all.

NSItemProvider.loadItemForTypeIdentifier broken from swift
 
 
Q