I have a view that the users are allowed to finger paint. The code is working perfectly if the area is touched with one finger. For example: I touch it with one finger and move the finger. Then, a line is drawn as I move the first finger. If I touch with a second finger the same view, the line that was being drawn by the first finger stops.
I would like to ignore any touch beyond the first, i.e., to track the first touch but ignore all others to the same view.
I am using touchesBegan/moved/ended.
I have used this to detect the touches
UITouch *touch = [[event allTouches] anyObject];
lastPoint = [touch locationInView:myView];
I have also tried this
lastPoint = [[touches anyObject] locationInView:myView];
but nothing changed.
How do I do that - track the first touch and ignore any subsequent touch to a view?
thanks.
NOTE: the view is NOT adjusted to detect multiple touches.
A given touch will maintain the same memory address as long as it is in contact with the screen. This means you can save the address as an instance variable and ignore any events from other objects. However, do not retain the touch. If you do, a different address will be used and your code won't work.
Example:
Add currentTouch to your interface:
#interface MyView : UIView {
UITouch *currentTouch;
...
}
...
#end
Modify touchesBegan: to ignore the touch if one is already being tracked:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
if(currentTouch) return;
currentTouch = [touches anyObject];
...
}
Modify touchesMoved: to use currentTouch instead of getting a touch from the set:
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
if(!currentTouch) return;
CGPoint currentPoint = [currentTouch locationInView:myView];
...
}
Modify touchesEnded: and touchesCancelled: to clear currentTouch, but only if currentTouch has ended or been cancelled.
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
if(currentTouch && currentTouch.phase == UITouchPhaseEnded) {
...
currentTouch = nil;
}
}
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
if(currentTouch && currentTouch.phase == UITouchPhaseCancelled) {
...
currentTouch = nil;
}
}
yourView.multipleTouchEnabled = NO;
From the reference documents on UIView
multipleTouchEnabled
A Boolean value that indicates whether
the receiver handles multitouch
events.
#property(nonatomic, getter=isMultipleTouchEnabled) BOOL
multipleTouchEnabled Discussion
When set to YES, the receiver receives
all touches associated with a
multitouch sequence. When set to NO,
the receiver receives only the first
touch event in a multitouch sequence.
The default value of this property is
NO.
Other views in the same window can
still receive touch events when this
property is NO. If you want this view
to handle multitouch events
exclusively, set the values of both
this property and the exclusiveTouch
property to YES.
Related
Currently, if I tap with two fingers on my screen and remove one of them but keep pressing with the other finger, the touchesEnded-method will get called.
But I want that the method only does something if there are no more touches in the view.
How can I check within the touchesEnded-method if there are still touches on the scene?
The touchesBegan and touchesEnded methods get called each time for each touch that occurs on the screen. Each touch on the screen is associated with a UITouch object. You can find all touches that are associated with the event in the (NSSet*) touches object that is a parameter for these methods.
You need to keep track of the original touch using a global variable.
In your gameScene's implementation:
#implementation GameScene
{
UITouch* currentTouch; //This variable will keep track of the touch.
}
Now, in the touchesBegan method:
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
/* Called when a touch begins */
for (UITouch *touch in touches) {
if (currentTouch == nil) {
currentTouch = touch;
//Handle touch code here, not outside.
}
}
}
And in the touchesEnded method:
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
for (UITouch *touch in touches) {
if ([touch isEqual:currentTouch]) {
//This is the tracked touch, handle touch ending here.
currentTouch = nil;
}
}
}
The above code should help you handle touch for a single touch object.
For a better understanding, you can read up on the Apple's touch handling guide here.
How to determine the property or perhaps the object underneath the object i am hovering or dragging?
To put my question clearly, lets take I am hovering a uiview, I want to find out what (object or view) is underneath the view I am hovering.
in custom view you can override touchesEnded method.This sample code may help your custom view hit test problem.
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
if ([touches count] == 1) {
UITouch *touch = [touches anyObject];
CGPoint point = [touch locationInView:custom_view];
if (CGRectContainsPoint(custom_view.bounds, point)) {
//if touch hit to custom_view
};
}
[super touchesEnded:touches withEvent:event];
}
For one, if you know the frames of both objects you can use CGRectIntersectsRect.
if (CGRectIntersectsRect(topObjectsRect, bottomObjectsRect)) {
//
}
Additionally, you could get the point that was touched and then use the following to check if that point is in a certain rectangle.
if (CGRectContainsPoint(CGRectMake(someX, someY, someWidth, someHeight), pointOfTouch))
{
//
}
I've a view with 10 character labels (big letters). Each character (label) has a different tag. The functionality that I'm developing is "Trace".
When user moves his finger over the view, i want to detect which character is touched.
I think I've to implement,
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
But I'm not knowing how to identify touch on the label and identify the character.
Can some one help me?
If you want to know the view that the touch began on (which view the user put their finger down on), you can read any of the touch items returned in the NSSet like so:
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
NSInteger viewTag = [[[touches anyObject] view] tag];
//Check your view using the tags you've set...
}
However, even as the finger moves across the screen, this view property will ONLY return the view initially touched and not the view currently under the finger. To do this, you will need to track the coordinates of the current touch and determine which view it falls into, perhaps like this:
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
CGPoint point = [touch locationInView:self.view];
//If all your views are collected as an array
for(UIView *view in views) {
if (CGRectContainsPoint([view frame], point))
{
//Bingo
}
}
}
The key to success on this is to make sure that you are getting your touches in coordinates reference to the proper views, so make sure to call locationInView: and CGRectContainsPoint() appropriately with values that match your application's view hierarchy and where your placing this code (i.e. the View, ViewController, etc.)
To detect simple touches you can use UIGestureRecognizer. Read the documentation for more on those. For more complex operations you do need to implement:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
To identify which item was touched you can give your item's tags values:
myView.tag = 4;
Then just check the tag value of the view reporting the touch and you know which it is.
Im having a little problem on handling touches in my apps.
I set my touchesBegan like this:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *currentTouch = [[event allTouches] anyObject];
touchPoint = [currentTouch locationInView:self.view];
if (CGRectContainsPoint(image1.frame, touchPoint)) {
image1IsTouched = YES;
}
}
Then i set my touch move like this:
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *currentTouch = [[event allTouches] anyObject];
currentPoint = [currentTouch locationInView:currentTouch.view];
if(image1IsTouched == YES) {
image1.center = CGPointMake(currentPoint.x,currentPoint.y);
.....
}
}
Now i tried my app on actual unit and thats where i notice my problem. While im touching the image1 with 1 finger the app is doing ok and its checking for collision everytime i drag my finger. The problem occurs when i touch the screen with another finger while touching/dragging the image. The image im currently touching will jump to the other finger. I've tried [myView setMultipleTouchEnable:NO]; & using NSArray on touches and comparing the [touches count] with the touch but its not working. Can someone show me how to set a uiimageview to act on single touch only. Thanks.
First, you should use UITouch *currentTouch = [touches anyObject]; to get the current touch.
Second, you should check that touches.count == 1 to make sure there's only one finger on the screen, and ignore touch input if there's more than one, unless you wanted to support multitouch.
Please can someone help sort a noob out? I've posted this problem on various forums and received no answers, while many searches for other answers have turned up stackOverflow, so I'm hoping this is the place.
I've got a BeachView.h (subclass of UIScrollView, picture of a sandy beach) covered with a random number of Stone.h (subclass of UIImageView, a random PNG of a stone, userInteractionEnabled = YES to accept touches).
If the user touches and moves on the beach, it should scroll.
If the user taps a stone, it should call method "touchedStone".
If the user taps the beach where there is no stone, it should call method "touchedBeach".
Now, I realize this sounds dead simple. Everyone and everything tells me that if there's something on a UIScrollView that accepts touches that it should pass control on to it. So when I touch and drag, it should scroll; but if I tap, and it's on a stone, it should ignore beach taps and accept stone taps, yes?
However, it seems that both views are accepting the tap and calling both touchedStone AND touchedBeach. Furthermore, the beach tap occurs first, so I can't even put in a "if touchedStone then don't run touchedBeach" type flag.
Here's some code.
On BeachView.m
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
if (self.decelerating) { didScroll = YES; }
else { didScroll = NO; }
UITouch *touch = [[event allTouches] anyObject];
CGPoint touchLocation = [touch locationInView:touch.view];
NSLog(#"touched beach = %#", [touch view]);
lastTouch = touchLocation;
[super touchesBegan:touches withEvent:event];
}
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
didScroll = YES;
[super touchesMoved:touches withEvent:event];
}
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
if (didScroll == NO && isPaused == NO) {
[self touchedBeach:YES location:lastTouch];
}
[super touchesEnded:touches withEvent:event];
}
On Stone.m
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
[parent stoneWasTouched]; // parent = ivar pointing from stone to beachview
}
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [[event allTouches] anyObject];
CGPoint touchLocation = [touch locationInView:touch.view];
NSLog(#"touched stone = %#", [touch view]);
[parent touchedStone:YES location:touchLocation];
}
After a stone tap, My NSLog looks like this:
Touched beach = <BeachView: 0x1276a0>
ran touchedBeach
Touched Stone = <Stone: 0x1480c0>
ran touchedStone
So it's actually running both. What's even stranger is if I take the touchesBegan and touchesEnded out of Stone.m but leave userInteractionEnabled = YES, the beachView registers both touches itself, but returns the Stone as the view it touched (the second time).
Touched beach = <BeachView: 0x1276a0>
ran touchedBeach
Touched beach = <Stone: 0x1480c0>
ran touchedBeach
So PLEASE, I've been trying to sort this for days. How do I make it so a tapped stone calls only touchedStone and a tapped beach calls only touchedBeach? Where am I going wrong?
Is true, iPhone SDK 3.0 and up, don't pass touches to -touchesBegan: and -touchesEnded: **UIScrollview**subclass methods anymore. You can use the touchesShouldBegin and touchesShouldCancelInContentView methods that is not the same.
If you really want to get this touches, have one hack that allow this.
In your subclass of UIScrollView override the hitTest method like this:
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
UIView *result = nil;
for (UIView *child in self.subviews)
if ([child pointInside:point withEvent:event])
if ((result = [child hitTest:point withEvent:event]) != nil)
break;
return result;
}
This will pass to you subclass this touches, however you can't cancel the touches to UIScrollView super class.
Prior to iPhone OS 3.0, the UIScrollView's hitTest:withEvent: method always returns self so that it receives the UIEvent directly, only forwarding it to the appropriate subview if and when it determines it's not related to scrolling or zooming.
I couldn't really comment on iPhone OS 3.0 as it's under NDA, but check your "iPhone SDK Release notes for iPhone OS 3.0 beta 5" :)
If you need to target pre-3.0, you could override hitTest:withEvent: in BeachView and set a flag to ignore the next beach touch if the CGPoint is actually in a stone.
But have you tried simply moving your calls to [super touches*:withEvent:] from the end of your overridden methods to the start? This might cause the stone tap to occur first.
I had a similar problem with a simpleView and it is added to a scrollView , and whenever I touched the simpleView , the scrollView used to get the touch and instead of the simpleView , the scrollView moved . To avoid this , I disabled the srcolling of the scrollView when the user touched the simpleView and otherwise the scrolling is enabled .
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
UIView *result = [super hitTest:point withEvent:event] ;
if (result == simpleView)
{
scrollView.scrollEnabled = NO ;
}
else
{
scrollView.scrollEnabled = YES ;
}
return result ;
}
This could be related to a bug in iOS7 please review my issue (bug report submitted)
UIScrollView subclass has changed behavior in iOS7