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:
I need to call some methods that run asynchronously. In order to deal with that I've tried using a dispatch semaphore. That keeps the method from returning until I've had a chance to set the return value.
Since activityViewController:itemForActivityType: is called on the main thread, that locks up while it's working.
I need to draw a UIView into an image. If I try to do that work on the main thread, nothing happens until the semaphore times out. But if I don't do it on the main thread, it crashes.
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?
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.
Related
At the moment we have a class structure like...
GenericFruitViewController
- AppleViewController
- PearViewController
- StrawberryViewController
Where the specific view controllers are subclasses and only change a small amount of implementation.
What I'd like to do is be able to swap out the GenericViewController at runtime. I want to change the way the generic view controller works and change some of the methods (this won't affect the subclassed overridden methods).
But I'd like to be able to switch this on/off (A/B testing).
At the moment we have a factory method that does something like...
- (GenericFruitViewController *)fruitControllerWithType:(FruitType)type
{
if (type == Apple) {
return [AppleViewController new];
}
return [GenericFruitViewController new];
}
What I'd like to do (ideally) is something like...
- (GenericFruitViewController *)fruitControllerWithType:(FruitType)type
{
// this is the new bit!
if (switchOnTheTesting) {
// swap GenericFruitViewController for my NewFruitViewController
}
// new bit ends
// existing code not changed
if (type == Apple) {
// this now returns a subclass of NewFruitVC if switched
return [AppleViewController new];
}
// this now returns NewFruitVC if switched
return [GenericFruitViewController new];
}
And by doing this it will then use my new VC whenever it refers to the GenericFruitVC.
Is that even possible?
Being a ReactiveCocoa newbie, I'm hoping for some advice with this:
I'm trying to create a dynamic form that contains multiple Field objects parsed from an XML file. Each Field can have muliple validation rules that will run against the Field's NSString *value param.
For the RAC part of the question-
inside each Field object, I want to bind BOOL completed to a signal that checks the Field's *value param against an array of rules. So far I've gotten here with my thinking:
#implementation Field
self = [super init];
if (self) {
RAC(self, completed) = [RACObserve(self, value) filter:^BOOL(NSString *fieldValue) {
NSLog(#"%s::self.completed = %d\n", sel_getName(_cmd), self.completed); // trying to watch the values here, with no luck
NSLog(#"%s::fieldValue = %#\n", sel_getName(_cmd), fieldValue); // same here, I'd like to be able to view the `*value` here but so far no luck
return [self validateCurrentValue]; // currently this method just checks value.length > 5
}];
}
return self;
The *value param has already been bound to my view model (successfully) and it gets updated each time a textfield changes.
What I'm looking for is a basic example or best-practice, the code above crashes when run so I know I'm missing something fundamental.
Thanks all
-filter: is simply passing values from RACObserve(self, value) through unchanged, but only if the block returns YES. So that means you're trying to set completed to values of whatever type value is. That's Probably Bad®.
But the good news is that you're really close!
Instead of filtering, you want to transform. You want to take every value and map it to something other thing. Namely whether that value passes validation. To do that, we use -map::
RAC(self, completed) = [RACObserve(self, value) map:^(NSString *fieldValue) {
return #([self validateCurrentValue]);
}];
UIActivityItemSources, it seems, can only return one kind of placeholder item? This seems strange, because I have a UIActivityItemSource that could return a string, an NSData object, or an image depending upon the activity it's given.
Is there really no way to return more than one kind of placeholder? (NSArrays don't seem to work.)
(I could imagine a solution where I instantiate a bunch of UIActivityItemProvider instances, each supporting the different datatypes mentioned above. But that seems like a lot more work than should be necessary...?)
If you add a trace inside your itemForActivityType function you will see that this function will be called multiple times. One for each activity available for share.
For example - if I want to provide different text for Twitter and mail/sms sharing I would have something like this:
- (id) activityViewController: (UIActivityViewController*) activityViewController itemForActivityType: (NSString*) activityType {
if (activityType == UIActivityTypePostToTwitter) {
return #"Sharing by Twitter";
}
else
return #"Other kind of sharing";
}
UPDATE:
If you want to provide different types of data to share (say text and images) - you need to wrote your placeholder function in a way so it returns two different kind of object when called multiple times.
- (id) activityViewControllerPlaceholderItem: (UIActivityViewController*) activityViewController {
static int step = 0;
if (step == 0) {
step = 1;
return #"text";
}
else if (step == 1) {
step = 2;
return [UIImage imageNamed: #"image"];
}
}
I am trying to work out the correct way to approach some methodology.
Workflow
When a game is created, I would like to first search to see if a game already exists with this user. If there is a game I will not create one and show a message to the user.
At present I have two methods:
+(void)createNewGameAgainst:(PFUser *)user2 withCompletion:(void (^)(BOOL success))completionHandler
+(BOOL)checkIfGameAlreadyExistsAgainst:(PFUser *)opponentUser
The createNewGame... method is called first. Then within this I make a call to [self checkIfGameAlreadyExistsAgainst:user2];.
How do I check the result of the second method, from within the first? So how do I determine what the BOOL value is of the call to the method checkIfGameAlreadyExistsAgainst?
Is this the correct way to approach this or is there a better/cleaner way possibly?
The return value of a function can be used like a variable:
BOOL gameExists = [self checkIfGameAlreadyExistsAgainst:user2]; // assign result to a new variable
if(gameExists == YES) // compare result to YES
{
}
You can skip creating a new variable and just compare the result
if ([self checkIfGameAlreadyExistsAgainst:user2] == YES) // compare result directly
{
}
And when the type is BOOL, you can omit the comparison and just do this:
if ([self checkIfGameAlreadyExistsAgainst:user2])
{
}
Obviously, with obj-c, there's usually no reason to write getters and setters (thanks to useful mr #synthesize).
So now, needing to do just this, I've come across the problem that I don't know how to write them. :p
I'm sure I'm probably not going about solving my problem the right way - it would be much easier to just subclass my object and such - but I'm trying to write category code to add properties because (in the beginning) it was quicker, and because I wanted to learn how to use category code in my app.
I've got this:
-(BOOL)isMethodStep {
return self.isMethodStep;
}
-(void)setIsMethodStep:(BOOL)theBoolean {
if(self.isMethodStep != theBoolean){
self.isMethodStep = theBoolean;
}
}
and I've tried it without the if query in the setter, but neither seem to work. Loading it with breakpoints shows that for some reason it gets stuck in a continuous loop in the getter method.
Is this code right or am I doing something wrong?
Thanks
Tom
In
-(BOOL)isMethodStep {
return self.isMethodStep;
}
return self.isMethodStep; calls the same isMethodStep method causing an infinite loop. Same thing for setter.
Just use your iVars directly in your accessor method implementations:
-(BOOL)isMethodStep {
return isMethodStep;
}
-(void)setIsMethodStep:(BOOL)theBoolean {
if(isMethodStep != theBoolean){
isMethodStep = theBoolean;
}
}
You don't want to use the self. property syntax within the setter/getter, because that invokes the setter/getter again, instead of directly assigning to the variable.
You need to just say:
-(BOOL)isMethodStep {
return isMethodStep;
}
-(void)setIsMethodStep:(BOOL)theBoolean {
isMethodStep = theBoolean;
}
(assuming "isMethodStep" is the name of your variable). I would omit the test in the setter method too...