I've noticed a lack of questions related to true multi-touch in iOS. I'm not talking about touch events for one finger, I'm talking about touch events for 3 or more fingers. Are there any sources or documentation articles about gesture handling for large amounts of touch input? And if not, are there any base methods that any of you have used in the past that work?
(P.S. My ultimate goal is to NSLOG a 3 finger swipe down).
Use gesture recognizers—they handle touch processing for you and most let you specify the minimum number of fingers for the gesture to be recognized. In your case, for example:
// -viewDidLoad
UISwipeGestureRecognizer *swipeRecognizer = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:#selector(swiped:)];
swipeRecognizer.direction = UISwipeGestureRecognizerDirectionDown;
swipeRecognizer.numberOfTouchesRequired = 3;
[self.view addGestureRecognizer:swipeRecognizer];
[swipeRecognizer release];
…
- (void)swiped:(UISwipeGestureRecognizer *)recognizer
{
if(recognizer.state == UIGestureRecognizerStateRecognized)
{
// got a three-finger swipe
}
}
Related
Cocos2d is prety new for me so i don't know what i should do with this situation:
I want to make a game thats something like risk. Now i made a background image like a world map (just to test). and on this map i want a swipe gesture so i can move accross the map on my ipad ( the map is prety big so i want to swipe it arround).
My problem is i don't know what the objects are called i should use. And how i can implement the gestures the best way (do i need to calculate the movement myself?).
Thanks!
Stefan.
You could maybe connect UIKit's Pan Gesture Recognizer to CCDirector's view and handle pan gesture in CCLayer class. In that way, you could have handle method which moves background with every pan movement. (Code with cocos2d 1.0.1, similar could be done with 2.0 version)
UIPanGestureRecognizer* pan = [[[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(handlePanGesture:)] autorelease];
CCDirector* director = [CCDirector sharedDirector];
[[director openGLView] addGestureRecognizer:pan];
Handler method would look like this:
- (void)handlePanGesture:(UIGestureRecognizer*)gestureRecognizer {
// If there is more than one pan gesture recognizer connected with this method, you should remember pan and check if gestureRecognizer is equal to pan
switch (gestureRecognizer.state) {
case UIGestureRecognizerStateBegan: {
// Do something that needs to be done when pan gesture started
break;
}
case UIGestureRecognizerStateChanged: {
// Get pan gesture recognizer translation
CGPoint translation = [(UIPanGestureRecognizer*)gestureRecognizer translationInView:gestureRecognizer.view];
// Invert Y since position and offset are calculated in gl coordinates
translation = ccp(translation.x, -translation.y);
// Here you should move your background, probably in oposite direction of translation vector, something like
background.position = ccp(background.position.x - translation.x, background.position.y - translation.y);
// Refresh pan gesture recognizer
[(UIPanGestureRecognizer*)gestureRecognizer setTranslation:CGPointZero inView:gestureRecognizer.view];
break;
}
case UIGestureRecognizerStateEnded: {
// Do some work that should be done after panning is finished
break;
}
default:
break;
}
}
I think you're looking for this to add an object:
CCSprite *objectName = [CCSprite spriteWithFile:#"fileName.png"];
[self addChild:objectName];
By default, I believe the object will be in the bottom left corner.
in ios 5 i was able to disable the double tap zoom by just overriding it with a new double tap gesture. But it seems that the double tap gesture is no longer in the gesturerecognizer array that comes with the mkmapview.
NSArray *gestureRecognizers = [_mapView gestureRecognizers];
for (UIGestureRecognizer *recognizer in gestureRecognizers) {
NSLog(#"%#", recognizer);
}
returns nothing in ios 6, where in ios 5 it would return 2 recognizers, one for single tap and one for double tap.
I'd look through the gesture recognizers of MKMapView's subviews. It's probably still there somewhere.
Of course, messing around with another view's GRs is slightly dubious and will likely break the next time Apple changes something about MKMapView...
EDIT: For the benefit of anyone else reading this, please check that it's a UITapGestureRecognizer and that numberOfTapsRequired == 2 and numberOfTouchesRequired == 1.
Also, instead of disabling double-taps on the map entirely, consider adding a double-tap GR on the annotation and then do [mapDoubleTapGR requireGestureRecognizerToFail:annotationDoubleTapGR]. Again, hacky — don't blame me if it breaks on the next OS update!
This worked for me:
[_mapView.subviews[0] addGestureRecognizer:MyDoubleTapOverrider];
Do you want to let the user do anything with the view? If not, it sufficient to set userInteractionEnabled to NO. If so, what specific interactions do you need to allow? Everything but double-tapping? Why disable that one interaction?
The more we know about your use case, the better the answers we can provide.
This works for me:
//INIT the MKMapView
-(id) init{
...
[self getGesturesRecursive:mapView];
...
}
And then let the recursive function loop through the subviews and find the GR:s.
-(void)getGesturesRecursive:(UIView*)v{
NSArray *gestureRecognizers = [v gestureRecognizers];
for (UIGestureRecognizer *recognizer in gestureRecognizers) {
if ([recognizer isKindOfClass:[UITapGestureRecognizer class]]) {
[v removeGestureRecognizer:recognizer];
}
}
for (UIView *v1 in v.subviews){
[self getGesturesRecursive:v1];
}
}
This example removes all tap-GR:s. But I guess you can specify to remove whatever you'd like.
You can use a long tap gesture instead, that works.
How do i implement and code for recognizing swipes across the screen, and also making it so that if it swipes on a uiimageview then things happen, // code, how can i do this? THanks
Use UISwipeGestureRecognizer. Something like this to detect a swipe on imageView:
UISwipeGestureRecognizer *swipe = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:#selector(imageSwiped:)];
swipe.direction = UISwipeGestureRecognizerDirectionRight;
[imageView addGestureRecognizer:swipe];
[swipe release];
Then define a method - (void)imageSwiped:(UISwipeGestureRecognizer *)sender and react to the swipe there.
I suggest you use a UISwipeGestureRecongizer for it, unless you have a very specific gesture to recongize.
Try reading out : Event Handling Guide for iOS from Apple.
So, I iterate through a loop and create UIViews which contain UIImageViews (So that I can selectively show any given part). These UIViews are all stored in a UIScrollView.
I add gesture recognizers to the UIViews in the loop where I created them.
When I run the program, only the items initially visible within the UIScrollView have their gestures recognized. If I scroll over to previously hidden items and then tap on them, nothing happens at all (the gesture is never recognized or attempted to be).
Initialization code:
UITapGestureRecognizer* gestRec = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(handleGesture:)];
gestRec.delegate = self;
[imageholder addGestureRecognizer:gestRec];
Code that deals with the gesture:
- (void)handleGesture:(UIGestureRecognizer *)gestureRecognizer
{
float count = [self._imageHolders count];
NSLog(#"handling gesture: %f",count);
while(count--){
UIView* object = (UIView*) [self._imageHolders objectAtIndex:count];
// NSLog(#"Whats going on: %#, %#, %b",object,gestureRecognizer.view, object == gestureRecognizer.view);
if(object == gestureRecognizer.view){
object.alpha = .1;
count = 0;
}
// [object release];
}
}
Any ideas?
---- Update :
I've explored a variety of the available functions in scrollview, UIView and the gesture recognizer and have tried messing with the bounds in case something was cut off that way... Interestingly enough, if there is one item only partially visible and you move it over so it's completely visible, only the portion originally visible will recognize any gestures.
I don't know enough about how the gesture recognizer works within the UIKit architecture to understand this problem. The Apple example for a scrollview with gestures doesn't seem to have this problem, but I can't find any real differences, except that I am nesting my UIImageViews within their own UIViews
I had a similar problem and found it was caused by adding the sub-views to a top-level view then adding that top-level view to the scroll-view. The top-level view had to be sized to the same dimensions as the contentSize (not the bounds) of the scroll-view otherwise it wouldn't pass on touch events to its subviews even when they had scrolled into view.
Try to set the cancelsTouchesInView property to NO.
UITapGestureRecognizer* gestRec = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(handleGesture:)];
gestRec.delegate = self;
**gestRec.cancelsTouchesInView = NO;**
[imageholder addGestureRecognizer:gestRec];
In one of my iPhone projects, I have three views that you can move around by touching and dragging. However, I want to stop the user from moving two views at the same time, by using two fingers. I have therefore tried to experiment with UIView.exclusiveTouch, without any success.
To understand how the property works, I created a brand new project, with the following code in the view controller:
- (void)loadView {
self.view = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 320, 460)];
UIButton* a = [UIButton buttonWithType:UIButtonTypeInfoDark];
[a addTarget:self action:#selector(hej:) forControlEvents:UIControlEventTouchUpInside];
a.center = CGPointMake(50, 50);
a.multipleTouchEnabled = YES;
UIButton* b = [UIButton buttonWithType:UIButtonTypeInfoDark];
[b addTarget:self action:#selector(hej:) forControlEvents:UIControlEventTouchUpInside];
b.center = CGPointMake(200, 50);
b.multipleTouchEnabled = YES;
a.exclusiveTouch = YES;
[self.view addSubview:a];
[self.view addSubview:b];
}
- (void)hej:(id)sender
{
NSLog(#"hej: %#", sender);
}
When running this, hej: gets called, with different senders, when pressing any of the buttons - even though one of them has exclusiveTouch set to YES. I've tried commenting the multipleTouchEnabled-lines, to no avail. Can somebody explain to me what I'm missing here?
Thanks,
Eli
From The iPhone OS Programming Guide:
Restricting event delivery to a single view:
By default, a view’s exclusiveTouch property is set to NO. If you set
the property to YES, you mark the view so that, if it is tracking
touches, it is the only view in the window that is tracking touches.
Other views in the window cannot receive those touches. However, a
view that is marked “exclusive touch” does not receive touches that
are associated with other views in the same window. If a finger
contacts an exclusive-touch view, then that touch is delivered only if
that view is the only view tracking a finger in that window. If a
finger touches a non-exclusive view, then that touch is delivered only
if there is not another finger tracking in an exclusive-touch view.
It states that the exclusive touch property does NOT affect touches outside the frame of the view.
To handle this in the past, I use the main view to track ALL TOUCHES on screen instead of letting each subview track touches. The best way is to do:
if(CGRectContainsPoint(thesubviewIcareAbout.frame, theLocationOfTheTouch)){
//the subview has been touched, do what you want
}
I was encountering an issue like this where taps on my UIButtons were getting passed through to a tap gesture recognizer that I had attached to self.view, even though I was setting isExclusiveTouch to true on my UIButtons. Upon reviewing the materials here so far, I decided to put some code in my tap gesture code that checks if the tap location is contained in any UIButton frame and if that frame is also visible on the screen at the same time. If both of those conditions are true, then the UIButton will already have handled the tap, and the event triggered in my gesture recognizer can then be ignored as a pass through of the event. My logic allows me to loop over all subviews, checking if they are of type UIButton, and then checking if the tap was in that view and the view is visible.
#objc func singleTapped(tap: UITapGestureRecognizer)
{
anyControlsBreakpoint()
let tapPoint = tap.location(in: self.view)
// Prevent taps inside of buttons from passing through to singleTapped logic
for subview in self.view.subviews
{
if subview is UIButton {
if pointIsInFrameAndThatFrameIsVisible(view: subview, point: tapPoint)
{
return // Completely ignores pass through events that were already handled by a UIButton
}
}
}
Below is the code that checks if point was inside a visible button. Note that I hide my buttons by setting their alpha to zero. If you are using the isHidden property, your logic might need to look for that.
func pointIsInFrameAndThatFrameIsVisible(view : UIView, point : CGPoint) -> Bool
{
return view.frame.contains(point) && view.alpha == 1
}