Reputation: 2762
I have an app with a Share button. I want to customize what content is shared based on the activity type. For example, Messages might get an image and text, whereas AirDrop would just get a file.
I actually have this working perfectly, and the code I'm using has worked fine in every version of iOS through iOS 10. But I've realized I'm returning nil where I'm not supposed to, so I'm trying to figure out how to fix that.
I do something like this to set up my activity view controller:
JUNActivityProvider *fileProvider = [[JUNActivityProvider alloc] initWithPlaceholderItem:[NSObject new]];
fileProvider.objectID = objectID;
fileProvider.fileURL = fileURL;
JUNActivityProvider *textProvider = [[JUNActivityProvider alloc] initWithPlaceholderItem:[NSString new]];
textProvider.objectID = objectID;
...
UIActivityViewController *activityController = [[UIActivityViewController alloc]
initWithActivityItems:@[fileProvider,imageProvider,textProvider,urlProvider,printFormatter]
applicationActivities:nil];
Then in JUNActivityProvider
, I have an item
method that customizes the return value based on the activityType
:
- (id)item {
if (self.fileURL) {
if ([self.activityType isEqualToString:UIActivityTypeAirDrop]) {
// Create the file
return url;
}
} else if ([self.placeholderItem isKindOfClass:[UIImage class]]) {
if ([self.activityType isEqualToString:UIActivityTypeAirDrop] == NO &&
[self.activityType isEqualToString:UIActivityTypeMail] == NO &&
[self.activityType isEqualToString:UIActivityTypePrint] == NO) {
// Create the image
return image;
}
} else if ([self.placeholderItem isKindOfClass:[NSString class]]) {
if ([self.activityType isEqualToString:UIActivityTypeMail]) {
return @"example one";
} else if ([self.activityType isEqualToString:UIActivityTypeMessage] ||
[self.activityType isEqualToString:UIActivityTypeCopyToPasteboard]) {
return @"example two";
}
}
return nil;
}
That return return nil
at the end is the problem. It works fine and does exactly what I want—when it's nil that item isn't shared. The written documentation doesn't say that it must return a value, but the header file does:
- (nonnull id)item;
// called on secondary thread when user selects an activity. you must subclass and return a non-nil value.
I don't want to risk a crash by returning nil when a nonnull value is expected, so I need to fix this. As far as I can tell my only option is to stop using UIActivityItemProvider
, and instead implement the UIActivityItemSource
protocol on my own. That protocol includes the method activityViewController:itemForActivityType:
, which clearly states that you can return nil there:
May be nil if multiple items were registered for a single activity type, so long as one of the items returns an actual value.
Perfect. But here's the problem: activityViewController:itemForActivityType:
is called on the main thread, which is causing problems with one of my items in particular. Here's a summary of what's happening:
activityViewController:itemForActivityType:
is called on the main thread, that locks up while it's working.I'm at a loss for how to deal with this. Basically I need to keep the method from returning until I'm ready, but I can't lock up the main thread since I need to do some work there. This seems… impossible? Is there any way to make this work?
Upvotes: 1
Views: 726
Reputation: 2762
After filing an enhancement request I was just about to give up and settle on either returning nil
or [NSNull null]
. But then I realized there is absolutely a solution to this problem.
While UIActivityItemProvider includes a bunch of its own functionality, it still very much implements the UIActivityItemSource protocol. I knew that. What I hadn't considered is that this means I can just override activityViewController:itemForActivityType:
and return nil
there when it's appropriate.
So the last line of my item
method now looks like this:
return self.placeholderItem;
You could also return [NSNull null]
here, or really any object. I chose the placeholderItem because it seems a little safer—at the very least I know it's returning an object of the expected type, in case anything about the implementation ever changes.
Then all I have to do is add my own implementation of activityViewController:itemForActivityType:
(where we are allowed to return nil):
- (nullable id)activityViewController:(UIActivityViewController *)activityViewController itemForActivityType:(UIActivityType)activityType {
id item = [super activityViewController:activityViewController itemForActivityType:activityType];
if ([item isEqual:self.placeholderItem]) return nil;
return item;
}
Just call super to get the item, return nil if it's something you don't want to include, or return the item if it is. Note that if your placeholderItem
might ever be equal to something you actually do want to share, you will need to change this implementation a bit—but the same basic concept should work.
Upvotes: 3