Custom actions for UIGestureRecognizers (with custom parameters) - iphone

Short version of my problem:
I cannot figure out how to make the "action" for my UITapGestureRecognizer take additional parameters, and actually use them.
Here's the rundown of my problem:
I am trying to make it so that my iPad app records (with NSLog) the coordinates of the UITouch that occurs whenever they press one of my app's UIButtons. The location of the touch needs to be relative to the button that was touched.
What I've done:
I have implemented a UITapGestureRecognizer and added it to each of my buttons. My problem is with the action to use, since it needs to be dynamic for each and every button.
I currently have this code:
UITapGestureRecognizer *iconClickRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(logIcon:withTag:)];
[iconClickRecognizer setNumberOfTapsRequired:1];
[iconClickRecognizer setNumberOfTouchesRequired:1];
[iconClickRecognizer setDelegate:self];
[[self.view viewWithTag:1] addGestureRecognizer:iconClickRecognizer];
[iconClickRecognizer release];
When I know that this works, I will use a for-loop to add the iconClickRecognizer to all of the buttons by their tag.
The logIcon:(int)withTag method is shown here:
-(void)logIcon:(UIGestureRecognizer *)gestureRecognizer withTag:(int)tag {
NSLog(#"tag X: %f", [gestureRecognizer locationInView:(UIView*)[self.view viewWithTag:tag]].x);
NSLog(#"tag Y: %f", [gestureRecognizer locationInView:(UIView*)[self.view viewWithTag:tag]].y);
}
What isn't working:
When I hard-code a tag into logIcon method, it records the information correctly. However, I do not know how to make this method dynamic, and actually use the "tag" parameter.
Any help would be greatly appreciated.
Thanks,
Alex

The issue is that you can only get one argument from target / action style registration, that is the sender (in this case the gesture recognizer itself). You're not able to pass arbitrary contexts. But you could, in your action, check the recognizer's view property and examine that view's tag.

The docs for UIGestureRecognizer class specify that the action must be of the form:
- (void)handleGesture;
- (void)handleGesture:(UIGestureRecognizer *)gestureRecognizer;
not your form of:
- (void)logIcon:(UIGestureRecognizer *)gestureRecognizer withTag:(int)tag
So you could ask the gestureRecognizer where it is on the whole window, then compare to your buttons, or you could walk through your buttons and ask the gesture where it is with respect to each button.
Probably best would be to subclass UIButton and make each button itself the target; then you know exactly what view you're in.

Related

How to detect touch inside an UITableViewCell

I have a custom UITableViewCell with four UIImageView inside it. I want to detect touch on these imageViews and notify my ViewController (which is containing the UITableView) which ImageView inside which cell has been tapped. How may I do that?
Try this:
-(void)createGestureRecognizers {
for(/*each of the UIImageViews*/) {
UITapGestureRecognizer *singleFingerTap = [[UITapGestureRecognizer alloc]
initWithTarget:self action:#selector(handleSingleTap:)];
singleFingerTap.numberOfTapsRequired = 1;
[imageView addGestureRecognizer:singleFingerTap];
imageView.tag = i;
[singleFingerTap release];
}
}
- (void)handleSingleTap:(UIGestureRecognizer *)sender {
UIImageView *imageView = (UIImageView *)sender.view;//this should be your image view
}
There are a couple variants to hit the target. In fact it doesn't matter at all how you are doing it at the cell's level. It may be buttons, image view with tap gesture recognizers or custom drawing and proceeding touch events. I will not provide you with code now as you have a lot of code already given and may find a lot more by little search, thought it may be provided by demand. The real "problem" is in transporting message to the controller. For that purpose I've found only two reasonable solutions. First is the notifications and second is the delegations. Note that the notifications method may lead to visible lag between tap and actual event occurrence as sometimes notifications are brought to objects with little delay...
Hope that it helps.
Use four buttons and set them with different tag values. Then just check the tag value to see which one was pressed.

How do I display a pop-up menu when a user touches content within a Quartz-drawn PDF?

I created an iPhone PDF viewer using Quartz that also has a page-flip animation. This works well, but now I'd like to add some more interactive functionality.
In particular, I'd like to add the ability to pop up a menu when the user touches some part of the PDF content. How can I make my PDF display respond to touch events over certain parts of the displayed PDF material?
It's pretty easy. Just add a UIGestureRecognizer to your scrollview.
You may wanna make some calculations to get the correct touch point relative to the pdf.
I've written an pdf iOS framework that does exactly that, you may wanna check it out. There I create the tap recognizers in the ScrollView's init method:
UITapGestureRecognizer *singleTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(singleTapped:)];
singleTap.numberOfTapsRequired = 1;
[singleTap requireGestureRecognizerToFail:doubleTap];
[self addGestureRecognizer:singleTap];
[singleTap release];
(Note that I added here the "requireGestureRecognizerToFail:doubleTap" because I also respond to double and tripple taps for zomming)

UIButton with two state - touch and long touch

I'd like to make a button perform different methods, depending on if the user taps or does a long tap.
I've tried:
UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:#selector(doRewind)];
[uiNextButton addGestureRecognizer:longPress];
[longPress release];
But the app registers my touch only when I touch the button and move the finger a little bit.
What am I doing wrong?
If you are setting both a "normal" tap and a "long" tap gesture, there could be interactions between the two of them.
Have you tried setting the minimumPressDuration property for UILongPressGestureRecognizer?
Also, using requireGestureRecognizerToFail: can be useful to make one of the two gesture handler fire only if the other one did not.
Have a look at the relevant document for those two methods.
If this does not help, please, give more details about your view and all the gesture handlers you are defining.

Two fingered swipe v. one fingered drag in iOS

In my app I have a UIView derived class Canvas that uses touchesBegan: withEvent:, touchesMoved: withEvent:, and touchesEnded: withEvent: to draw in the canvas. I also want to use a swipe to load the previous (or next) canvas in an array. I tried setting up the following gesture (and a similar one for right):
UISwipeGestureRecognizer* leftSwipe = [[UISwipeGestureRecognizer alloc] initWithTarget: self action: #selector(pageFlipNext)];
leftSwipe.direction = UISwipeGestureRecognizerDirectionLeft;
leftSwipe.numberOfTouchesRequired = 2;
[_canvas addGestureRecognizer: leftSwipe];
[leftSwipe release];
But my two fingered swipes are still being treated as one-fingered drawing instructions.
How do I get my app to handle the two-fingered swipe correctly?
First of all, I would get started verifying whether _canvas.multipleTouchEnabled is set to YES. If it isn't, set it to YES.
You might also want to consider
leftSwipe.delaysTouchesBegan = YES;
This will delay the touches to be sent to the _canvas until the gesture fails. You can also use a UIPanGestureRecognizer and do something like this,
[pan requireGestureRecognizerToFail:leftSwipe];
I think that you can use UIPanGestureRecognizer
UIPanGestureRecognizer is a concrete
subclass of UIGestureRecognizer that
looks for panning (dragging) gestures.
The user must be pressing one or more
fingers on a view while they pan it.

Is it possible to differentiate between a long press and a tap on a button?

Can we call different actions / delegates in response to the two different events of
A tap on a UIButton
A tap-and-hold on a UIButton
?
Yes, it's reasonably easy to implement this using a UILongPressGestureRecognizer (on iPhone OS 3.2+). A long press will be handled by the gesture recognizer, and a short tap will pass through to the button's normal action.
For example, I subclassed UIButton and added the following method for specifying a long touch action to go along with a tap (longPressGestureRecognizer is an instance variable):
- (void)setLongTouchAction:(SEL)newValue
{
if (newValue == NULL)
{
[self removeGestureRecognizer:longPressGestureRecognizer];
[longPressGestureRecognizer release];
longPressGestureRecognizer = nil;
}
else
{
[longPressGestureRecognizer release];
longPressGestureRecognizer = nil;
longPressGestureRecognizer = [[UILongPressGestureRecognizer alloc] initWithTarget:[[self allTargets] anyObject] action:newValue];
[self addGestureRecognizer:longPressGestureRecognizer];
}
}
I then could do the following to set both short tap and long press actions that will be handled by the same target:
[undoButton addTarget:self action:#selector(performUndo:) forControlEvents:UIControlEventTouchUpInside];
[undoButton setLongTouchAction:#selector(showUndoOptions:)];
As you can see, this is useful for the undo buttons you see in title bars of many iPad applications.
Brad Larson's answer looks pretty good but here's another one that might give you a bit more flexibility/control of what you want or might want to do.
You subclass UIButton, you override the touchesBegan and touchesEnded methods so that when the user starts a touch you call
[self performSelector:#selector(detecetedLongTap) withObject:nil afterDelay:1.0];
and in the touchesEnded you call:
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:#selector(detecetedLongTap) object:nil];
to cancel the event if the finger was lifted too soon.
You can get full code for this in this blog post:
http://www.isignmeout.com/adding-long-tap-functionality-uibutton/
The best solution I can think of, is to create another class, and subclass UIButton. Then on Interface Builder (if that's what you're using), you can set the button's class to the custom class you just created.
So in this new class, you have to override a method called
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
This is basically telling you that someone pressed down on your button. The touches is an NSSet and it holds all the information for all the fingers that are pressing down on the screen. If you only are interested in the one that's pressing on the button itself, you'll probably have something like:
NSSet *myTouches = [event touchesForView:self.view];
So now that you have the touches that correspond to your button, you have to find out how many times the user tapped on that button. You do that with something like:
NSUInteger numTaps = [[myTouches anyObject] tapCount];
If this number is 2, that means the user just double tapped your button. Now comes the next part. How do you know if the user is holding that button? Well when the user lets go of the screen, another method gets called. You'll need to override that one as well:
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
This is where you know if the person has stopped touching the screen or if his finger is still on it. If his finger is still on it, then this event hasn't been called yet.
Now enough with the background
Here's my suggestion to you. I suggest you override the touchesBegan: method and check if the number of taps in the button is 2. If so, then start a timer that does what you need it to do, for as long as you need it to be done, and then on the touchesEnded: method, you'll go ahead and stop that timer, and this way you will have done whatever it is that you needed to do, for as long as you needed to do it OR as long as the user has held on to the button.
I hope this helps, obviously I didn't write the whole code for you, you'll have to experiment and research that stuff, but if you have any questions, I'll be happy to lend a helping hand :)