What type should I use? - ios

I have a result coming from a method that is either of kind TWTweetComposeViewControllerResult or SLComposeViewControllerResult.
I need to pass this to a method. Something like
[self doSomething:result];
How do I declare this method?
- (void) doSomething:(SLComposeViewControllerResult) result ?
- (void) doSomething:(TWTweetComposeViewControllerResult) result ?
- (void) doSomething:(NSNumber *) result ?

Make two different methods that handle each. Each enum is a different type and should be treated as such. You can also forward the result on, using a boolean if you are just indicating success, or your own custom enum if you need more information.
- (void)doSomethingSL:(SLComposeViewControllerResult) result
{
// made up, idk what the result enum would be be
[self doSomething:(result == SLComposeSuccess)];
}
- (void)doSomethingTweet:(TWTweetComposeViewControllerResult) result
{
// made up, idk what the result enum would be be
[self doSomething:(result == TWTweetSuccess)];
}
- (void)doSomething:(BOOL)success
{
}
If you are still convinced that you want to handle them in a uniform way and ignore types, you could always cast the results to an int in the method and forward them on.

Both SLComposeViewControllerResult and TWTweetComposeViewControllerResult are both enums, with 0 meaning cancelled and 1 meaning done.
So any of these should be OK:
- (void) doSomething:(SLComposeViewControllerResult) result;
- (void) doSomething:(TWTweetComposeViewControllerResult) result;
- (void) doSomething:(NSInteger) result;
[edit] Note this comment in TWTweetComposeViewController.h:
// This class has been deprecated in iOS 6. Please use SLComposeViewController (in the Social framework) instead.
So you should just use the SLComposeViewControllerResult version.

Related

Method parameters are nil when called using IMP in Release configuration

Our Swift application needed some lower-level C/Objective-C code, so we added a Dynamic Library to make integration with the application easier.
The library has a single, shared instance of a controller, but the style of the callbacks doesn't work well for closures, so we went with a protocol. However since multiple classes need to use this controller it would need to have multiple delegates. So each class registers itself as a delegate and when a protocol method is called it iterates through each delegate, gets the IMP for the selector, and calls it.
On debug builds this worked fine, it was only until we used the Release configuration that we noticed that the parameters to these functions were nil in the implementation of the protocol methods, even if they were not nil when called.
This is how our protocol methods are called:
- (void) delegateCall:(SEL)sel withObject:(id)object {
for (id delegate in self.delegates) {
if ([delegate respondsToSelector:sel]) {
IMP imp = [delegate methodForSelector:sel];
void (*func)(__strong id,SEL,...) = (void (*)(__strong id, SEL, ...))imp;
func(delegate, sel, object);
}
}
}
Let's use the example protocol method: - (void) blah:(NSNumber * _Null_unspecified)aNumber;
If we call [self delegateCall:#selector(blah:) withObject:#32];, the object will be nil in the implementation of blah:
func blah(_ aNumber: NSNumber) {
if aNumber == nil {
print("The number is nil somehow?!?!?!") // <-- Release
} else {
print("The number is: \(aNumber.intValue)") // <-- Debug, prints 32
}
}
If we use call the method in code on the delegates (rather than using IMP) the issue does not happen:
for (id delegate in self.delegates) {
[delegate blah:#32];
}
Having never tried casting an IMP instance to a function with variadic arguments, I can't say for sure how it would/should work (it would probably involve parsing a va_list, for instance), but since you know that you have one and only one parameter, I think you should be able to solve this particular problem by just eliminating your use of variadic arguments when you cast your IMP instance to a function pointer:
- (void) delegateCall:(SEL)sel withObject:(id)object {
for (id delegate in self.delegates) {
if ([delegate respondsToSelector:sel]) {
IMP imp = [delegate methodForSelector:sel];
void (*func)(__strong id, SEL, id) = (void (*)(_strong id, SEL, id))imp;
func(delegate, sel, object);
}
}
}
Since you know that the argument is already an id, this should be a perfectly safe replacement.
As to why your original implementation works in a debug build but not in a release build, I can only guess; it might be related to the fact that release builds typically strip all symbols during link, and the runtime might be able to take advantage of the symbols, if present, in order to guess the correct argument ordering when invoking? Perhaps the compiler uses the wrong calling convention in a release configuration when generating the call to a function declared with a fixed argument footprint but invoked with variadic arguments? I'd be interested in further information if anyone has a more definitive answer to the debug/release question.
See the discussion on calling conventions here for a possible alternative using reinterpret_cast, if in fact your problem is due to a calling convention mismatch.

How can I vary the items used by UIActivityViewController in this scenario?

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.

How to use Objective-C function with a block input in Swift

I'm using a Objective-C SDK. The function I want to use take a parameter that
delegates the delegate for handling user consent
- (void) startWithDelegate: (id<SharingDelegate>) delegate;
The SharingDelegate requires me to implement 2 functions, one of them has a block input
(void) getUserConsent: (void (^)(BOOL)) consentHandler deviceN: (NSString *) deviceN;
Can anyone tell me how to implement this function in Swift? what is the signature?
If it is a method (hard to tell because your display of it is garbled), I take it that it would be something like this:
func getUserConsent(consentHandler:Bool -> (), deviceN: String) {
// body goes here
}

Calling a BOOL method and dealing with the result

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])
{
}

Pass different parameters to an IBAction

My iPhone app has many buttons and I want all the buttons to call the same method, but with different parameters.
For example I want tapping one button to call the method myMethod: with the argument #"foo", and a second button should call the same method but with argument #"bar".
The so called "IBActions" must have one of these signatures:
-(void)action;
-(void)actionWithSender:(id)sender;
-(void)actionWithSender:(id)sender event:(UIEvent*)event;
You cannot add any other parameters. Nevertheless you can use sender (which is button1 or button2 in your case) to get the parameter:
-(void)actionWithSender:(UIButton*)sender {
NSString* parameter;
if (sender.tag == 1) // button1
parameter = #"foo";
else // button2
parameter = #"bar";
...
}
the real reason You cannot add additional parameter is that UIKIT will push params on the stack.
so the only way is to use tags.
A DIRTY way can be to convert a pointer to int and tagging the button with it:
myStruct params;
// fill params:
params.x=....
params.y=....
params.z=....
UIButton * btn = [UIButton......]; // create or use one from XIB
btn.tag = (int)&params;
... in Call back:
-(IBActions) doIt:(id)sender
{
myStruct * paramsPtr = (myStruct*)tag;
int i = paramsPtr->x;
NOTE: params MUST be keep static .. or allocate using malloc (more and more ugly code...).
DO NOT use a local var: it will be allocated on stack so will be removed after exiting from the setup method.
Give your various UIButton instances different tag property values.
In your IBAction method -myMethod:, you might then do something like:
- (void) myMethod:(id)sender {
switch (sender.tag) {
case (firstButtonTag):
doFooStuff;
break;
case (secondButtonTag):
doBarStuff;
break;
// etc.
}
}
The values firstButtonTag and secondButtonTag can be stored in an enum if you want to make this easy to maintain.
You can't pass parameters through an IBAction. What I usually do is give the buttons the unique tag in IB. THe tag is an integer value so I u then use a simple lookup table to convert the tag to some value.
In this case, three buttons but tags 1 to 3:
- (IBAction) buttonPressed: (UIButton*) sender
{
static const NSString* names = { #"Foo", #"Bar", #"Baz" };
id tag = [sender tag];
if (tag >= 1 && tag <= 3) {
NSLog(#"Button pressed is %#", names[tag]);
}
}
(id)Sender is shows that whatever u pass on UIButton click event is directly pass to this method and no matter that what type it is , it take automatically like if you pass button tag then it take button tag as sender.tag etc
As others have mentioned you cannot pass your custom parameter into action method. If you do not like the solution using tags you may also subclass UIButton with your custom class and add your parameter there. (By I wouldn't bother and just use tags)
You don't. The only parameter is the sender object, which you may use to have a different behavior, but what I'd do is define 2 action methods, which simply in turn call the same method with a different parameter, i.e. you'd have:
- (IBAction)button1:(id)sender
{
[self doStuff:kButton1];
}
- (IBAction)button2:(id)sender
{
[self doStuff:kButton2];
}
- (void)doStuff:(ParamType)param;
{
...
}
In defense of that method (no pun intended), I'd add that it makes clearer when you review your UI with Interface Builder to see that different buttons actually have different effects, which is harder to tell if they all call whateverAction:

Resources