Two fingered swipe v. one fingered drag in iOS - iphone

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.

Related

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.

iOS: Can I override pinch in/out behavior of UIScrollView?

I'm drawing a graph on a UIView, which is contained by a UIScrollView so that the user can scroll horizontally to look around the entire graph.
Now I want to zoom the graph when a user pinches in with two fingers, but instead of zooming in a view with the same rate for X and Y direction, I want to zoom only in the X direction by changing the X scale, without changing the Y scale.
I think I have to catch the pinch in/out gesture and redraw the graph, overriding the default zooming behavior.
But is there a way to do this?
I've been having a very difficult time to catch the pinch gesture on the UIScrollView, as it cancels the touches when it starts to scroll. I want the zooming to work even after the UIScrollView cancels the touches. :(
Thanks,
Kura
Although you cannot delete the existing pinch gesture recognizer, you can disable it and then add your own:
// Disable existing recognizer
for (UIGestureRecognizer* recognizer in [_scrollView gestureRecognizers]) {
if ([recognizer isKindOfClass:[UIPinchGestureRecognizer class]]) {
[recognizer setEnabled:NO];
}
}
// Add our own
UIPinchGestureRecognizer* pinchRecognizer =
[[UIPinchGestureRecognizer alloc] initWithTarget:self
action:#selector(pinch:)];
[_scrollView addGestureRecognizer:pinchRecognizer];
[pinchRecognizer release];
Then in
- (void) pinch:(UIPinchGestureRecognizer*)recognizer { .. }
use
[recognizer locationOfTouch:0 inView:..]
[recognizer locationOfTouch:1 inView:..]
to figure out if the user is pinching horizontally or vertically.
You should instead access the gestureRecognizers (defined in UIView), there are several of them being used by the scroll view,
figure out which one is the pinch recognizer and call removeGestureRecognizer: on the scroll view, then create your own and have it do the work, add it back with addGestureRecognizer:.
these are all public API,
the recognizers and what order they are in are not (currently),
so program defensively when accessing them
(this is a perfectly valid way to manipulate UIKit views, and Apple won't/shouldn't have issues with it - though they will not guarantee it works in any future release)
You should be able to subclass UIScrollView and override the touchesBegan: method. Don't call [super touchesBegan:] but instead, adjust the zoom as you like:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
//Anything you want. Probably you would want to store all the touches
//or their values, so that you can compare them to the touches
//in the touchesEnded: method,
//thus letting you know what the pinch amount was
}
If you like, you can judge whether it's a pinch or not, and if it's not, call the super method, and only handle it yourself for custom pinches.
Edsko & bshirley answers are good, but they don't tell where to place the code.
First, I placed it in viewDidLoad method, but no Pinch Gesture Recognizer was found in the scrollview (maybe because my scrollview is an IBOutlet).
Then I tried in viewWillAppear or viewDidAppear and the UIPinchGestureRecognizer was here.

Stealing two-finger pan from mkmapview

I've placed a UIview above an MKMapView with the intention of stealing the two-finger pan from the map view when I am tracking a user's location. If you notice Google maps app does this on the phone as well.
But I'm at a loss as to how to do it. I want to be able to enable it and disable it at will depending on whether the location is being tracked or not. All other gestures and touches should be passed onto the mapview
thanks in advance.
Assuming you're going for OS 4 and above, the easy way is NOT to use a UIView, but instead to add a gesture-recognizer directly onto the mapview, e.g.:
UIPanGestureRecognizer* gest = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(handlePanGesture:)];
[mapView addGestureRecognizer:gest];
...and then all swipe gestures (in that case) would get passed to [self handlePanGesture:(UIPanGestureRecognizer*) x];
If you look in the docs around gesture recognizers, there's a wide variety of options, and you can add them to a pretty fine-level of granularity (tap vs swipe, how many fingers used, etc)

Custom actions for UIGestureRecognizers (with custom parameters)

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.

How to detect touch on UIImageView inside UIScrollview?

I have a UIScrollview in my app and I populate it with LOTS of UIImageViews approx 900. They are all very small and consist of only two different images over and over again.
Now I am having a lot of trouble detecting a touch on one of these UIImageViews.
I have assigned them all a unique TAG so as to be able to distinguish between them but I am really struggling to detect the touch.
The goal is just to be able to change the image of the touched UIImageView.
Due to the large amount of views involved a simple loop checking touch coordinates against each UIImageViews frame is just hanging my app.
Does anyone have any suggestions?
Thanks in Advance.
Ben
In my case the easiest way to do it was adding a gesture recognizer:
UITapGestureRecognizer *singleTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(singleTapGestureCaptured:)];
//Default value for cancelsTouchesInView is YES, which will prevent buttons to be clicked
singleTap.cancelsTouchesInView = NO;
[myScrollView addGestureRecognizer:singleTap];
Then use this method to capture the touch:
- (void)singleTapGestureCaptured:(UITapGestureRecognizer *)gesture
{
CGPoint touchPoint=[gesture locationInView:myScrollView];
}
UIImageView has touch processing turned off by default. To change that set userInteractionEnabled to YES (see the docs here http://developer.apple.com/library/ios/#DOCUMENTATION/UIKit/Reference/UIImageView_Class/Reference/Reference.html)
Also if you want to know which view was hit in a view hierarchy you can use hitTest:withEvent: (see docs in UIView) which will be much faster than you looping through the hierarchy.
On a wider note I don't think having 900 UIImageView's on the screen all at once is going to be a good long term strategy. Have you investigated drawing the screen's content via CoreGraphics?
The best way to solve this issue is to subclass UIScrollView, add touchesBegan etc methods to it.
#interface MyScroll : UIScrollView
{
}
You could easily implement this method on the UIImageView object:
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
if ([self pointInside:point withEvent:event]) {
//You clicked inside the object
}
return [super hitTest:point withEvent:event]
}
Use the return to tell the application the next touch responder (such as the uiimageview or the scrollview).