Objective-C: passing a string parameter to tapgesture #selector - ios

What I am trying to achieve is this: when I tap an specific UIImageView, the UITapGesture will pass a string into the tap method.
My code follows below: Imagine I have an UIImageView object there already, and when I tap this image, it will make a phone call,
UITapGestureRecognizer *tapFirstGuy = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(makeCallToThisPerson:#"1234567")];
[imageViewOne addGestureRecognizer:tapFirstGuy];
- (void)makeCallToThisPerson:(NSString *)phoneNumber
{
NSString *phoneNum = [NSString stringWithFormat:#"tel:%#", phoneNumber];
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:phoneNum]];
}
However, I am getting the following compile error: #selector(makeCallToThisPerson:#"1234567");
I cannot figure what is happening. Why can't I pass string to the private method?

One more way you can try writing subclass of UITapGestureRecognizer like that below:-
#interface customTapGestureRecognizer : UITapGestureRecognizer
#property (nonatomic, strong) NSString * phoneNumber;
#end
// customTapGestureRecognizer.m
#implementation customTapGestureRecognizer
#end
// =====================
....
customTapGestureRecognizer *singleTap = [[customTapGestureRecognizer alloc] initWithTarget:self action:#selector(makeCallToThisPerson:)];
singleTap.phoneNumber = #"1234567";
-(void) makeCallToThisPerson:(UITapGestureRecognizer *)tapRecognizer {
customTapGestureRecognizer *tap = (customTapGestureRecognizer *)tapRecognizer;
NSLog(#"phoneNumber : %#", tap.phoneNumber);
}
Note:- The advantages of writing subclass is that you can pass more number of data easily in your UITapGestureRecognizer.

This is wrong, It's not a valid selector.
UITapGestureRecognizer *tapFirstGuy = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(makeCallToThisPerson:#"1234567")];
You need to change to this:
UITapGestureRecognizer *tapFirstGuy = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(makeCallToThisPerson:)];
In your methods the parameter will be the tapGesture, that's it:
- (void)makeCallToThisPerson:(UITapGestureRecognizer *)tapGesture
You can do several things, in order to pas a parameter:
1.- Use de tag of the imageView, the tapGesture knows the view than it´s attached you can use the tag view:
UITapGestureRecognizer *tapFirstGuy = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(makeCallToThisPerson:)];
imageViewOne.tag = 1234567
[imageViewOne addGestureRecognizer:tapFirstGuy];
- (void)makeCallToThisPerson:(UITapGestureRecognizer *)tapGesture
{
NSString *phoneNum = [NSString stringWithFormat:#"tel:%ld",tapGesture.view.tag];
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:phoneNum]];
}
Other option, subclassing in order to attach a NSString to imageView as a new property.

The action should be just the selector for a method whose signature must be "method that takes a single id parameter and returns void". The id parameter will (typically) be the object sending the message.
The target (the object the action is sent to) can use the sender parameter to extract additional information if needed, but that additional information needs to be asked for. It isn't supplied gratis.
That is, your ImageView subclass might have methods like:
- (void)setPhoneNumber:(NSString *)phoneNumber; // set a phoneNumber property
- (void)prepareToBeTapped
{
UITapGestureRecognizer *tapFirstGuy = [[UITapGestureRecognizer alloc]
initWithTarget:self action:#selector(makeCallToThisPerson:)];
[self addGestureRecognizer:tapFirstGuy];
}
- (void)makeCallToThisPerson:(id)sender
{
NSString *phoneURL = [NSString stringWithFormat:#"tel:%#", phoneNumber];
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:phoneURL]];
}
That is, it's not the action nor even the UITapGestureRecognizer that knows the phone number. The target must know (or be able to obtain) the phone number some other way, perhaps carrying it as a settable property.

#OnikIV's suggestion of using the tag would work fine if the value you want to pass is an integer.
If you want to pass a string however it won't work so well.
You could set the tag to a unique value, then in the gesture recoginizer's action method, convert the tag value to an NSNumber and use it as a key to look up a value in a dictionary of phone numbers.
Another other option is to use associative storage to attach strings to your image views. Associative Storage is a technique that lets you attach key/value pairs to any arbitrary NSObject.
I have a sample project called AssociativeStorageDemo on github that shows how to use it.

Related

Adding a tap Gesture that calls a method in another class

Simply I want to add a tap gesture for a UIImageView that when the user touches, calls a method that is implemented in another class (not the same class that contains the UIImageView).
Can I do this and if so how can i do it ?
You can do that. But you need the instance of the target class (class in which method is going to execute) as delegate or something in the gesture adding class.
UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc]initWithTarget:self.delegate action:#selector(doSomething)];
Here delegate will be the instance of the target class that you have to set before going to that class.
Edit
I will explain a little more. Suppose you have two view controller classes VCA and VCB. you want to call a method in VCA from VCB through a tap gesture.
in VCB.h
#property (nonatomic,assign)VCA *delegate;
in VCB.m
UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc]initWithTarget:self.delegate action:#selector(doSomething)];
in VCA.m
You will present/ push VCB
VCB * viewController = [[VCB alloc]init];
viewController.delegate = self;
// push or present viewController
Declare recognizer selector on init
UITapGestureRecognizer *recognizer =
[UITapGestureRecognizer
initWithTarget:objectOfAnotherClass
action:#selector(methodImplementedOnObjectOfAnotherClass:)];
dont forget about defining number of taps (numberOfTapsRequired) and optional gesture recognizer required to fail (requireGestureRecognizerToFail:)
Link to article about selector.
One straightforward approach is calling the method of another class from the selector method of tapGesture ie.
Add UITapGestureRecogniser to the imageview
UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc]initWithTarget:self action:#selector(tapRecognised)];
[imageView addGestureRecognizer:tapGesture];
Dont forget to enable the userInteraction of the UIImageView, because by default for imageview userInteraction is disabled.
you can do this like below.
imageView.userInteractionEnabled = YES;
Then in the selector of tapGestureRecogniser call the method of another class
- (void)tapRecognised
{
[next tappedOnImage];
}
Here next is the object of another class. You can use delegation also to call the method.
Hope this will help you.
here self.desired view is the view in which you want to add gesture and you should add gesture in following way
and "webViewTapped.delegate = self" means you want to call a function of the same class when user tap and you can change it to your desired function by using delegate like self.delegate
UITapGestureRecognizer *viewTapped = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(tapAction:)];
webViewTapped.numberOfTapsRequired = 1;
webViewTapped.delegate = self;
[self.desiredView addGestureRecognizer:viewTapped];
- (void)tapAction:(UITapGestureRecognizer *)sender;
{
// do your stuff here
}

Pass a NSDictionary as parameter to UITapGestureRecognizer

I want to pass a NSArray as a parameter to UITapGestureRecognizer and access it in downloadOptionPressed method. How can I do this ?
The NSArray
NSArray *parameters = [NSArray arrayWithObjects:currentTrack, nil];
Creating the UITapGestureRecognizer
UITapGestureRecognizer *downloadOptionPressed = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(timeFrameLabelTapped:)];
[downloadOption addGestureRecognizer:downloadOptionPressed];
The downloadOptionPressed method
-(void)downloadOptionPressed:(UIGestureRecognizer*)recognizer{
}
Is there a reason you can't store the information in the owning view controller? Is it for abstraction?
You can always extend UITapGestureRecognizer to carry more data:
#interface UserDataTapGestureRecognizer : UITapGestureRecognizer
#property (nonatomic, strong) id userData;
#end
#implementation UserDataTapGestureRecognizer
#end
...
UserDataTapGestureRecognizer *downloadOptionPressed =
[[UserDataTapGestureRecognizer alloc] initWithTarget:self
action:#selector(timeFrameLabelTapped:)];
downloadOptionPressed.userData = parameters;
...
- (void)downloadOptionPressed:(UserDataTapGestureRecognizer *)recognizer {
NSArray *parameters = recognizer.userData;
}
You can use associated object to pass argument along with tap gesture instance.
You can check this objective-c-associated-objects
It will solve your problem.
Sometimes with passing an index is enough, in that case the tag property view is your ally. In the following exampled I pretended to add a long press into a tableview cell. And once the event was triggered, I just wanted to know which cell was long pressed:
let longPress = UILongPressGestureRecognizer(target: self, action: "longPress:")
cell.tag = indexPath.row
cell.addGestureRecognizer(longPress)
...
func longPress(guesture: UILongPressGestureRecognizer) {
print("\(guesture.view!.tag)")} }

Why is a method invoked from a singleton using a different instance object than the NSNotificationCenter?

The Short Version:
I ran into an issue where a method was being invoked on a different instance of an object than the NSNotificationCenter was pushing notifications to (though only one instance was created).
The Long Version:
I have a "Puzzle" object instance which is being operated on by an OpenGL update/draw loop.
I created a Control singleton to manage different touch events like so:
#implementation Controls
static Controls *SharedGameControls = nil;
-(id)init {
self = [super init];
return self;
}
+ (Controls*)SharedGameControls{
if (SharedGameControls == nil){
SharedGameControls = [[super allocWithZone:NULL] init];
}
return SharedGameControls;
}
+ (id)allocWithZone:(NSZone *)zone
{
return self.SharedGameControls;
}
- (id)copyWithZone:(NSZone *)zone
{
return self;
}
-(void)oneFingerSwipeDelegator:(UISwipeGestureRecognizer *)swipe{
switch (GState.currentMode)
{
case PuzzleLayer: {
CGPoint Origin = [swipe locationInView: _view];
//I believe this line should call oneFingerSwipe on the object instance
//provided to the singleton
[_oneFingerSwipeDelegate oneFingerSwipe:Origin Direction:swipe.direction];
break;
}
default:{
break;
}
}
}
-(void)setDefaultState{
GState = [GameState SharedGameState];
UISwipeGestureRecognizer *oneFingerSwipeUp = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:#selector(oneFingerSwipeDelegator:)];
[oneFingerSwipeUp setDirection:UISwipeGestureRecognizerDirectionUp];
[_view addGestureRecognizer:oneFingerSwipeUp];
UISwipeGestureRecognizer *oneFingerSwipeDown = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:#selector(oneFingerSwipeDelegator:)];
[oneFingerSwipeDown setDirection:UISwipeGestureRecognizerDirectionDown];
[_view addGestureRecognizer:oneFingerSwipeDown];
UISwipeGestureRecognizer *oneFingerSwipeLeft = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:#selector(oneFingerSwipeDelegator:)];
[oneFingerSwipeLeft setDirection:UISwipeGestureRecognizerDirectionLeft];
[_view addGestureRecognizer:oneFingerSwipeLeft];
UISwipeGestureRecognizer *oneFingerSwipeRight = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:#selector(oneFingerSwipeDelegator:)];
[oneFingerSwipeRight setDirection:UISwipeGestureRecognizerDirectionRight];
[_view addGestureRecognizer:oneFingerSwipeRight];
UITapGestureRecognizer * single = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(singleTapDelegator:)];
single.numberOfTapsRequired = 1;
[_view addGestureRecognizer:single];
}
-(void)singleTapDelegator:(UITapGestureRecognizer *)tap{
CGPoint origin = [tap locationInView: _view];
NSValue *Origin = [NSValue valueWithCGPoint:origin];
switch (GState.currentMode)
{
case PuzzleLayer: {
[[NSNotificationCenter defaultCenter] postNotificationName:#"puzzleTap" object:nil userInfo:[NSDictionary dictionaryWithObject:Origin forKey:#"Origin"]];
break;
}
default:{
break;
}
}
}
#end
The '_oneFingerSwipeDelegate' is defined in the .h file as
#property (nonatomic, assign) id oneFingerSwipeDelegate;
Then in the Puzzle class the events were handled thusly:
#implementation Puzzle
-(id)init{
self =[super init];
if (self){
GControls = [Controls SharedGameControls];
//I believe, possibly wrongly, that the line below will set this object instance
//to be the object oneFingerSwipe is called on
GControls.oneFingerSwipeDelegate = self;
GControls.twoFingerSwipeDelegate = self;
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(singleTap:) name:#"puzzleTap" object:nil];
_Started = false;
//other code omitted
}
return self;
}
//implementation of the delegate in Controls.h
-(void)oneFingerSwipe:(CGPoint)touchPoint Direction:(UISwipeGestureRecognizerDirection)direction{
//do some stuff with objects inside puzzle
}
//observer for 'puzzleTap'
-(void)singleTap:(NSNotification*)note{
GLKVector3 Near,Far;
NSDictionary *dict = [note userInfo];
CGPoint origin = [[dict objectForKey:#"Origin"] CGPointValue ];
//Do stuff with objects inside puzzle
}
//other code omitted
#end
So I was testing the gesture recognition to make sure everything was working and I noticed that my swipes weren't being handled.
Tapping correctly sent the notification to the singleTap method in the Puzzle object (which sets a 'selected' flag on a child object of the Puzzle instance).
Swiping correctly invoked the oneFingerSwipe method on the puzzle object, but for some reason was unable to detect which object had been 'selected' by the tap.
After taking a closer look I noticed that when I stepped in to singleTap I was being shown an address for the puzzle object which was different than the address shown when stepping into the oneFingerSwipe method called by the Control singleton.
So effectively, for some reason, the Control singleton is operating on a twin instance of the object that the notification is being sent to (or the reverse). All the objects within each instance seem to have different memory addresses than the respective twin.
As a result, when the oneFingerSwipe is called, the 'selected' flag hasn't been updated on the child object and so the logic for swiping isn't being invoked.
When I switched to using notifications for the swipe, the issue went away.
I don't really understand what is going on here. Is the Control singleton creating a copy of the Puzzle instance when I assign the puzzle instance to the oneFingerSwipe property?
I've only been working with Objective C for about 6 months and there is a great deal I don't understand.
Thanks for pushing through that wall of text, I appreciate you taking the time to have a look.
The reason it seemed that the direct method invocation was happening on a different object than the NSNotificationCenter was pushing notifications to is that it was, in fact, a different object. I had instantiated a number of different puzzle objects in a loop and each one was, in turn, setting itself as the target for oneFingerSwipe. When I was stepping through I happened to be looking at two incredibly similar but different objects and wrongly came to the conclusion that one was a copy of the other. Many thanks to the commenters for taking the time to look at this lengthy question and pointing me in the right direction. For now I've switched to solely using NSNotifications, but I'm going to do a little more reading into using delegates to see if that might better suit my needs.

Handle tap gesture with an argument iphone / ipad

My problem is similar to this one with the only exception - my ImageView appears at the same place inside the window with different content in it. Content has unique identifier which I want to use to call content-specific actions.
To quickly recap, the guy is looking for a way to pass a parameter into the selector section of the initWithTarget method.
UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(handleTapGesture:itemSKU:)];
How can I pass an attribute to the handleTapGesture method or how do I read the unique value otherwise?
Any thoughts appreciated.
EDIT: The content is being pulled from a database and is different every time. The unique identifier is pretty much like an SSN - doesn't repeat.
You could set the UIImageView tag property with your content identifier, and then read that information form the selector.
UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(handleTapGesture:)];
[imageView addGestureRecognizer:tapGesture];
imageView.tag = 0;
And then:
- (void)handleTapGesture:(UITapGestureRecognizer *)sender
{
if( ((UIImageView *) sender.view).tag == 0 ) // Check the identifier
{
// Your code here
}
}
Try extending the UIImageView and add whatever values (as properties) and methods you will need.
#interface UIImageViewWithId: UIImageView
#property int imageId;
#end
Then, if you want to be even MORE awesome you may want to encapsulate your behavior in this "widget"'s implementation. This will keep your ViewController nice and clean, and allow you to use this widget across multiple controllers.
#implementation UIImageViewWithId
#synthesize imageId;
- (void)handleTapGesture:(UIGestureRecognizer *)gesture {
NSLog("Hey look! It's Id #%d", imageId);
}
#end
Then just delegate the tap to the individual UIImageViewWithIds
UIImageViewWithId *imageView = [[UIImageViewWithId ... ]]
UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc] initWithTarget: imageView action:#selector(handleTapGesture:)];

UITapGestureRecognizer initWithTarget:action: method to take arguments?

I'm using UITapGestureRecognizer because I'm using a UIScrollView that acts as a container for my UILabels. Basically I'm trying to use an action method with arguments so I can e.g. send myLabel.tag value to the action method to know what action to take depending on what UILabel has has been triggered by a tap.
One way of doing it is having as many action methods as UILabels but that isn't very "pretty" codewise. What I would like to achieve is just having one action method with switch statements.
Is this possible or will I have to do it like this (sigh):
UITapGestureRecognizer *myLabel1Tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(myLabel1Tap)];
[myLabel1Tap addGestureRecognizer:myLabel1Tap];
UITapGestureRecognizer *myLabel2Tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(myLabel2Tap)];
[myLabel1Tap addGestureRecognizer:myLabel2Tap];
UITapGestureRecognizer *myLabelNTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(myLabelNTap)];
[myLabel1Tap addGestureRecognizer:myLabelNTap];
- (void)myLabel1Tap {
// Perform action
}
- (void)myLabel2Tap {
// Perform action
}
- (void)myLabelNTap {
// Perform action
}
Add a single gesture recognizer to the view that is the superview of your various labels:
UITapGestureRecognizer *myLabelTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(myLabelTapHandler:)];
[myLabelParent addGestureRecognizer:myLabelTap];
Then when you handle the gesture, determine which label was tapped:
-(void)myLabelTapHandler:(UIGestureRecognizer *)gestureRecognizer {
UIView *tappedView = [gestureRecognizer.view hitTest:[gestureRecognizer locationInView:gestureRecognizer.view] withEvent:nil];
// do something with it
}
You can use just one UITapGestureRecognizer and in your gesture handler (your myLaberXTap), which has the syntax:
- (void)handleGesture:(UITapGestureRecognizer*)gestureRecognizer {
...
}
use gesture.view to know which view you are working on.

Resources