Use selector with two (or more) parameters - ios

I have read this question:
Relevant question
And i still don't really get how to use selectors with number of parameters.
Here is my code:
{
...
//add single tap gesture to the view
SEL mySelector = #selector(handleSingleTap:withScroll:);
UIGestureRecognizer* singleTap = [[UITapGestureRecognizer alloc]initWithTarget:self action:mySelector];
[myView addGestureRecognizer:singleTap];
...
}
and:
- (void)handleSingleTap:(UITapGestureRecognizer *)recognizer withScroll:(UIScrollView*)scroll {
...
}
But of course it will not work. the (UIScrollView*)scroll is nil at run time.
How can i set it to be (UIScrollView*)scroll for instance?
Any help would be much appreciated.

The selector of UIGestureRecognizer works only with 1 argument, the recognizer itself calls your selector with only 1 argument so any other arguments in the method will be nil since there are no more arguments in the calling stack.

What I do is sending a single parameter that is actually an NSDictionary ... so I can send lot of information in a single parameter. GL HF

Related

What happens when a parametrized selector is called with no parameters?

I was going over this example in which a selector is used. I have copied the code from there for convenience.
// MYTapGestureRecognizer.h
#interface MYTapGestureRecognizer : UITapGestureRecognizer
#property (nonatomic, strong) NSString *data;
#end
// MYTapGestureRecognizer.m
#implementation MYTapGestureRecognizer
#end
// =====================
....
MYTapGestureRecognizer *singleTap = [[MYTapGestureRecognizer alloc] initWithTarget:self action:#selector(tapDetected:)];
singleTap.data = #"Hello";
.....
// ====================
-(void)tapDetected:(UITapGestureRecognizer *)tapRecognizer {
MYTapGestureRecognizer *tap = (MYTapGestureRecognizer *)tapRecognizer;
NSLog(#"data : %#", tap.data);
}
My question is
1-When self calls the selector what parameter does it pass in the above case ?
2- Also if a selector (pointing to a method that requires parameters) is called (see example below) and no parameters are passed are there any defaults in that case ? If possible is there any documentation for that ?
Suppose the signature of MyTest is
- (void) MyTest : (NSString*) a;
Now constructing and calling a selector
SEL a = NSSelectorFromString(#"MyTest:");
[t performSelector:a]; //Works Fine and the call is made - However Notice no parameter is passed . In this case what would the value of the parameter be in the method ?
I checked the following but I could not find this information
Apple docs
Rys Tutorials
Answers to your questions:-
When self calls the selector what parameter does it pass in the above case ?
If a tap is detected and the selector is called, the parameter will be an object of UITapGestureRecognizer. This will be the same instance on which the tap gesture is detected.
Also if a selector (pointing to a method that requires parameters) is called (see example below) and no parameters are passed are there any defaults in that case ? If possible is there any documentation for that ?
Why do you want to call the method like that, is there any special purpose?. If not, you can call the method just like
[self tapDetected:nil];
or
[self performSelector:#selector(tapDetected:) withObject:nil];
If you call the method as provided in the question, most probably it will crash.
If you wish to call the method on self, pass nil parameter to it. But i do not understand what purpose is it serving you.
Also if you do not send parameters to your methods, it is going to fail at your builds. You have to pass either the parameter or nil.
Also if your method does not accept nil parameters it might cause an exception - 'NSInvalidArgumentException'
Normally if you want to access that selector via self, use it like :
[self tapDetected:nil];
You need to handle this case in your selector, like :
-(void)tapDetected:(UITapGestureRecognizer *)tapRecognizer {
if (tapRecognizer)
{
MYTapGestureRecognizer *tap = (MYTapGestureRecognizer *)tapRecognizer;
NSLog(#"data : %#", tap.data);
}
else
{
//Do your work
}
}
Also not only this, if you are not sure of parameter you are passing change your selector decalartion as id, like :
-(void)tapDetected:(id)sender {
NSString *className = NSStringFromClass([id class]);
NSLog(#"Object passed is of class : %#", className);
//And make check here
if ([id isKindOfClass:[MYTapGestureRecognizer class]])
{
//Do your work here
}
}
There are no default cases, you need to handle every case manually or else app will crash.
SEL a = NSSelectorFromString(#"MyTest:");
[t performSelector:a]; //Works Fine and the call is made - However Notice no parameter is passed . In this case what would the value of the parameter be in the method ?
It will be undefined junk. You have no guarantees about what it might contain. Most likely, it will be an invalid pointer. If you're unlucky, it might be a valid pointer to some arbitrary object and operating on it will corrupt your app's state. If you're lucky, it will crash so you can find the problem.

How to pass multiple parameters to a UITapGesture method with selector

Trying to figure out how to pass a string argument to my method which I call using a selector. It also happens to be a method I wrote to respond to a single Tap gesture
My Method looks like this :
-(void)handleSingleTap:(UITapGestureRecognizer *)recognizer Mystring:(NSString *) TheString{
}
I am trying to call the method like this :
UITapGestureRecognizer *singleTapGestureRecognizer = [[UITapGestureRecognizer alloc]initWithTarget:self action:#selector(handleSingleTap:)];
Right now my call does not include the second NSString parameter I want to pass. How do I pass that second parameter? Thanks you.
Create category for UITapGestureRecognizer to use objc_setAssociatedObject
Add below category :
#import <objc/runtime.h>
static const void *stringKey = &stringKey;
#implementation UITapGestureRecognizer (string)
- (void)setString:(NSString *)stringToBePassedInGesture
{
objc_setAssociatedObject(self, stringKey, stringToBePassedInGesture, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (NSString *)string
{
return objc_getAssociatedObject(self, stringKey);
}
#end
Use like this:
[singleTapGestureRecognizer setString:yourStringHere];
More reference from here
I have no idea what language have you come from (if any) but it does not work this way in objective-C. The object you create has a certain scope and can have an owner of sorts. That means if you created an object (your string) in a method viewDidLoad you can only use it in that method unless you assign it to some object (for instance to self using a property as already mentioned). I suggest you try to search the web about creating one of those.
In a situation as yours it would be great if the calling object could store your string as a property which could then be used in a handler method as well. That would mean you would assign the string to the tap gesture gesture.myString = myString and then in the handler you could call recognizer.myString to get this string. This can actually be achieved by subclassing the gesture recognizer and creating that property on it but I would not suggest doing something like that just to get a string passed.
So generally you can not do that the nice way and believe me I do wish it would be possible as this same issue can get extremely difficult is situations such as adding a button to a table view cell. The generic handles are very limited and using more or less anything such as buttons, cells, gesture recognizers you can not expect to get much more info then the sender itself (sometimes even less like an index path).

Passing id to button action method in Objective-C

I have UIButtons programatically created. Now, I created an method to trigger for the button like so:
-(void)createButton {
//code to create button
[mybutton addTarget:self action:#selector(myAction:)forState:UIControlStateNormal];
}
-(void)myAction:(id)sender {
if([tag sender] == 0) {
posX = 380;
} else if(....... //set posX to different values
}
[self.myScroll setContentOffset:CGPointMake(poxX, 0) animated:YES];
That's pretty much what the buttons do aside from loading data. Basically, I am using the buttons as tabs. If I tap on a button, it slides to the center. In one of these buttons, there's an "update buttons" button where I can add and remove more buttons. If I tap on one of the buttons, it would automatically be removed and if I tap add, one would automatically add. There's no problem with that. The thing is, I want to retain the "update buttons" button centered as it is technically still the selected button. Here's how the method inside the view for update buttons:
-(void)updateButtons {
NSUInteger index = [self.anArray indexOfObject:#"btnChange"];
id indexId = [NSNumber numberWithInteger: index];
//this following line causes the app to crash because it does not recognize the indexId I'm trying to set
[self myAction:indexId];
}
Everytime I execute the updateButtons function and myAction is triggered, the app crashes with an uncaught exception. So my question is, how can I properly pass an id to an action method?
Precise answer to your question is: pass nil for the sender parameter:
[self myAction:nil];
-(void)createButton
{
//code to create button
myButton.tag = 1;
[mybutton addTarget:self action:#selector(myAction:)forState:UIControlStateNormal];
}
-(void)myAction:(id)sender {
//do something here
}
you are trying to pass the NSNumber object but you need to pass the UIButton object to myAction: method, I think you should create the UIButton object in the .h file and add tag to the button and pass the reference of that button object to the method
-(void)updateButtons
{
NSUInteger index = [self.anArray indexOfObject:#"btnChange"];
if(myButton.tag == index){
[self myAction:myButton];
}
}
-(void)updateButtons {
NSUInteger index = [self.anArray indexOfObject:#"btnChange"];
mybutton.tag=index
[self myAction:nil];
}
then in
-(void)myAction:(id)sender {
//do something here
int index=[sender tag]; //this is your index
}
id in Objective-C just means any object—it's used to avoid specifying a certain type of object. When using target-action, the first argument is the sender, or the object that sent the action. For the buttons, the sender would be an instance of UIButton.
I would recommend adding NSLog(#"%#",sender); to your action method to see what type it is each time the method is called.
So what should be passed as the argument? Well, it depends on what myAction does, and you'll have to share that code to get more details on this. If myAction doesn't use the sender argument, you can safely pass nil as other answerers suggest.
Note that when you use target-action, if you're not using the sender argument, you can leave it off altogether. Just declare your method like this:
-(void)myAction
{
// code here
}
[mybutton addTarget:self action:#selector(myAction:)forState:UIControlStateNormal];
Instead of above, replace this below line
[mybutton addTarget:self action:#selector(myAction:) forControlEvents:UIControlEventTouchUpInside];
Try this:
-(void)createButton
{
//code to create button
[mybutton addTarget:self action:#selector(myAction:)forState:UIControlStateNormal];
}
-(void)myAction:(id)sender
{
//do something here
}
-(void)updateButtons
{
NSUInteger index = [self.anArray indexOfObject:#"btnChange"];
id indexId = [NSNumber numberWithInteger: index];
[self myAction:mybutton];//if u r using my button else u can use
[self myAction:nil]
}
First off, seems weird that you are using -addTarget:action:forState. UIButton is a concrete subclass of UIControl and as such, instead uses the method -addTarget:action:forControlEvents:
which can take a variable number of or-able UIControlEvent enums. Specifically, the ones you want for a UIButton would be of the subtype UIControlEventTouch....
You should be crashing right away just cause that method doesn't exist.
That being said, without looking at your code we can't really tell you exactly which line inside your -myAction: method causes the crash. But the important point here is, the method whose signature / selector you are registering via the target-action pattern doesn't necessarily even need to have a parameter of type id, UIButton or anything.
Basically, when you do -addTarget:action:forControlEvents:you are telling a subclass of UIControl that when it undergoes the desired event/s, it should invoke a method in the object you pass to the first parameter of -addTarget: (the target), whose signature is the selector you pass to action:. The selector you pass to this parameter can have one or zero parameters in turn. If you pass in one with none (say, your action method is -doSomething), when the UIControl responds to the UIControlEvent it'll simply call your method and that's that. If instead, you pass in a selector that takes one parameter, the UIControl that triggered the action is automatically passed in to that parameter, cast to whatever type your action method's parameter type is.
So for instance:
if you register like so:
[self.readingListButton addTarget:self action:#selector(doSomething) forControlEvents:UIControlEventTouchUpInside];
your action method would look like this:
- (void)doSomething
{
// Notice we don't have a parameter and so we are limited to doing stuff
// that does not require the sender to be passed in.
NSLog(#"do Something!");
}
If instead you register like this (notice the : in the selector):
[self.readingListButton addTarget:self action:#selector(doSomething:) forControlEvents:UIControlEventTouchUpInside];
You could go:
- (void)doSomething:(id)sender
{
// If the button triggers the method, sender will be an id pointer holding the memory
// address of a UIButton and we could cast it to UIButton like so: UIButton *b = (UIButton)sender
// Then again, sender might not be a button. As long as we stick to stuff that any object
// will respond to we are fine though.
NSLog(#"do Something! %#", sender);
}
or:
- (void)doSomething:(UIButton*)sender
{
// We straight out assume it is a button:
NSLog(#"do Something! %#", sender);
}
So as you can see, the choice of parameter vs no parameter and the parameter type is sort of up to you.
Now my guess is the reason why your code crashes is because when you manually call the action method you are passing it an NSNumber instead of a UIButtonand inside the method you do something with the parameter that treats it as a UIBUtton.
Think about this for instance:
- (void)doSomething:(UIButton*)sender
{
// Our parameter is a button, so we can totally change its state:
sender.selected = !sender.selected;
}
If you pass in a button to the above method, it'll work just fine. However in your second case, you pass a number. and what really happens is this:
NSNumber *n;
UIButton *b = (UIButton*)n;
[target doSomething:b];
And inside -doSomething:
- (void)doSomething:(UIButton*)sender
{
// Our parameter is a button, so we can totally change its state:
sender.selected = !sender.selected;
}
But sender now is not really a button. It's a number cast to button. The minute the code above tries to change the state, it attempts to call the method -setState: on a NSNumber which does not have that method, and so you'd get a classic exception along the lines of:
unrecognized selector sent to instance.
So bottom line, if you don't require to pass in the button or any info into the target method, just define it without parameters or, if you do require a parameter, either make the parameter polymorphic (type id) and inside your method check to see what it is and act accordingly, or stick to a parameter of type UIButton but them make sure you only call it passing in buttons.

How to get Event type from UISwitch in IOS [duplicate]

I want to implement a custom subclass of UIControl. It works beautifully except for one fatal problem that is making me spit teeth. Whenever I use sendActionsForControlEvents: to send an action message out, it omits to include a UIEvent. For example, if I link it to a method with the following signature:
- (IBAction) controlTouched:(id)sender withEvent:(UIEvent *)event
... the event always comes back as nil! The problem seems to occur within sendActionsForControlEvents:
Now, I need my IBAction to be able to determine the location of the touch. I usually do so by extracting the touches from the event. Surely there must be some way to ensure that the correct event is delivered? It's a pretty fundamental part of using a UIControl!
Anyone know a legal workaround?
I would assume that this is because the sendActionsForControlEvents: method can't know which UIEvent (if any) your control event should be associated with.
You could try to send all the actions separately (replicating what the sendActionsForControlEvents: method does, according to the documentation), so you can specifically associate them with a UIEvent:
UIEvent *event = ...;
UIControlEvents controlEvent = ...;
for (id target in [self allTargets]) {
NSArray *actions = [self actionsForTarget:target forControlEvent:controlEvent];
for (NSString *action in actions) {
[self sendAction:NSSelectorFromString(action) to:target forEvent:event];
}
}
I have ONE possible solution at the moment, but I'm not very happy about it. For others faced with the same problem though, here it is. First, declare a local variable or property for a UIEvent thus:
#property (nonatomic, assign) UIEvent * currentEvent;
Now, in your touch-handling routines, set that local variable to the current UIEvent for that routine before calling [self sendActionsForControlEvents:] like so, replacing UIControlEventTouchDown with whichever action you want to send out of course.
self.currentEvent = event;
[self sendActionsForControlEvents: UIControlEventTouchDown];
Finally, override the following method thus:
- (void) sendAction:(SEL)action to:(id)target forEvent:(UIEvent *)event
{
[super sendAction:action to:target forEvent:self.currentEvent];
}
This works, but I am not in the least bit fond of it, so if anybody has an alternative solution that doesn't rely on holding a weak reference to a UIEvent, I will be overjoyed to hear it!

GestureRecognizer Blocks with BlockKit Xcode Build Error

I'm having an issue with GestureRecognizer and BlocksKit which is giving me a build error.
Basically i want, on a gesture, to run a block. I've copied the code from the documentation here....
http://zwaldowski.github.com/BlocksKit/Documentation/Categories/UIGestureRecognizer(BlocksKit).html
... and so far i've had no success as i receive the same build error each time. I've searched around and cannot find anything with the same issue i'm having so i was hoping someone here could maybe help.
The code im using is...
UITapGestureRecognizer *singleTap = [UITapGestureRecognizer recognizerWithHandler:^(id sender) {
NSLog(#"Single tap.");
} delay:0.18];
the error i receive is...
/Users/JohnSlater/Desktop/iOS Apps/Flink/flink/ViewController.m:202:91: Incompatible block pointer types sending 'void (^)(id)' to parameter of type 'BKGestureRecognizerBlock' (aka 'void (^)(UIGestureRecognizer *, UIGestureRecognizerState, CGPoint)')
Thanks in advance
Take a look at the parameters of the block in the error message. Your code has one argument, sender. But the block needs a UIGestureRecognizer, a UIGestureRecognizerState and a CGPoint. So, it should look something like
[UITapGesture recognizerWithHandler:^(UIGestureRecognizer *recognizer, UIGestureRecognizerState UIGestureRecognizerStateEnded, CGPoint point){
}];
UITapGestureRecognizer *singleTap = [UITapGestureRecognizer recognizerWithHandler:^(UIGestureRecognizer *sender, UIGestureRecognizerState state, CGPoint point )
{
NSLog(#"Single tap.");
} delay:0.18];
Will work.
Don't know why, but examples here seem to be wrong. BKGestureRecognizerBlock declared here with 3 parameters. So your linker is right complaining.

Resources