I am trying to make this app:
User places finger on screen to be traced.
A second finger traces around the first finger and an outline of the first finger should be drawn.(essentially just a drawing app that ignores the first finger being held on the screen)
Problem:
Instead of an outline it creates a star like shape. It seems that the line being drawn tries to connect to both touch points.
Question:
Is there a way to ignore a specific touch in a multi touch app?
Is there way to cancel the first touch in a single touch app?
My Code:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
touchCount++;
NSLog(#"Touches Count: %i", touchCount);
if(touchCount >= 2)
{
drawingTouch = [touches anyObject];
CGPoint p = [drawingTouch locationInView:self];
[path moveToPoint:p];
drawingPoints = [[NSMutableArray alloc]init];
}
else
{
//???
}
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
NSArray *allTouches = [touches allObjects];
if(touchCount >= 2 && [allTouches count]>1)
{
UITouch *theTouch = allTouches[1];
CGPoint p = [theTouch locationInView:self];
[path addLineToPoint:p]; // (4)
[self setNeedsDisplay];
[drawingPoints addObject:[NSValue valueWithCGPoint:p]];
}
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
[self touchesMoved:touches withEvent:event];// This was copied from a tutorial but I will remove it and see what happens.
}
Touch points are internally calculated as the center of the touched area; more specifically, the point output to touchesBegan, touchesMoved and touchesEnded is slightly above the center. An outline could only be the combination of all points touched by the second finger, but it would be significantly larger than the first finger by itself. All you can do is to create a circle around the first touch point which is drawn in parallel to the movement of the second finger.
The star like pattern you describe indicates that you do not assign the right touch points to the right fingers. Try to use the pointer to the touches in the (NSSet *)touches as keys in a NSDictionary and collect the points in a NSMutableArray which you put in the dictionary, one for each finger.
In subsequent calls to touchesBegan, touchesMoved and touchesEnded iOS will use the same address for subsequent touches of the same finger. When you track the pointer, you know which touch event belongs to which finger. Then you can decide which touch to process and which to ignore, but all touches will be reported in those calls to touchesBegan, touchesMoved and touchesEnded.
You can not trace around the finger (as in drawing outlines). The touch event represents a single point in the coordinate system; it does not represent all of the pixels on screen which are being touched.
Related
I have 2 images on one ViewController. When I tap the view, image1 jumps. For image2, I can touch and drag the image around the view. My problem is that when I touch image2 to drag, image1 jumps. I don't want image1 to jump when dragging image2.
I was thinking I could accomplish this without UIGestureRecognizer; just using a simple if statement. Is that possible?
The mess I have so far...
and please note, because I've been at this for a while, my code for this doesn't make whole lot of sense, because nothing I'm trying is working, so I stripped it out. I just feel it should be as simple as, if image2 touch and drag event is happening, then image1's touch event should not.
GameViewController.h
bool flicked;
GameViewController.m
-(IBAction)startGame:(id)sender {
flicked = 0;
//start game code...
}
IMAGE2 - This image can be dragged around the view
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
//flicked = 1;
UITouch *myTouch = [[event allTouches] anyObject];
Image2.center = [myTouch locationInView:self.view];
//flicked = 0;
}
IMAGE1 - This image jumps when view is tapped
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
if ((flicked = 0)) //OR (!flicked)?
{
image1jump = 30;
}
else {
image1jump = 0;
}
}
Try installing two UIGestureRecognizers, one on the single tap imageView, and another on the touch and drag ImageView. Set them accordingly (likely a tapGestureRecognizer and a PanGesturecognizer). It's hard to imagine a scenario where overriding the touch handling methods will be easier than a gesture recognizer for what your describing.
so I have a mutable array that holds several circles which are UIViews.
right now I have my touches began method setup like this.
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
CGPoint touchLocation = [touch locationInView:self.view];
for (Circle *circle in playerOneCircles)
{
if ([circle.layer.presentationLayer hitTest:touchLocation])
{
[circle playerTap:nil];
break;
}
}
}
this works fine. but it gives problems with overlapping views.
I want other UIviews to also respond to the touchesbegan method (that would then trigger other methods). but if 2 objects would overlap then my touchesbegan would trigger the wrong method.
so I would like to define multiple UITouches that only respond to certain objects instead of anyObject. how would I have to define the UITouch to only work with objects from my mutable array?
EDIT
Added comments to answer your comment to explain the code.
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
// We want to find all the circles that contain the touch point.
// A circle does not have to be "on top" (or even visible) to be touched.
// All circles that contain the touch point will be added to the touchedCircles set.
NSMutableSet *touchedCircles = [NSMutableSet set];
// To support multiple touches, we have to look at every touch object.
for (UITouch *touch in touches) {
CGPoint touchLocation = [touch locationInView:self.view];
// Search through our collection of circle objects. Any circle that
// contains this touch point gets added to our collection of touched
// circles. If you need to know which UITouch is "touching" each circle,
// you will need to store that as well.
for (Circle *circle in playerOneCircles) {
if ([circle containsPoint:touchLocation]) {
[touchedCircles addObject:circle];
}
}
}
// We have completed our search for all touches and all circles. If
// and circle was touched, then it will be in the set of touchedCircles.
if (touchedCircles.count) {
// When any circle has been touched, we want to call some special method
// to process the touched circle. Send the method the set of circles, so it
// knows which circles were touched.
[self methodAWithTouchedCircles:touchedCircles];
} else {
// None of our circles were touched, so call a different method.
[self methodB];
}
}
You would implement containsPoint for a Circle something like this...
- (BOOL)containsPoint:(CGPoint)point
{
// Since each of our objects is a circle, to determine if a point is inside
// the circle, we just want to know if the distance between that point and
// the center of the circle is less than the radius of the circle.
// If your circle is really contained inside a view, you can compute the radius
// as one-half the width of the frame.
// Otherwise, your circle may actually have its own radius property, in which case
// you can just use the known radius.
CGFloat radius = self.frame.size.width *.5;
// This is just the Pythagorean Theorem, or instance formula.
// distance = sqrt((x0-x1)^2 + (y0-y1)^2)
// and we want to check that
// distance < radius
// By simple algebra, that is the same as checking
// distance^2 < radius^2
// which saves us from having to compute the square root.
CGFloat diffX = self.center.x - point.x;
CGFloat diffY = self.center.y - point.y;
return (diffX*diffX) + (diffY*diffY) < radius*radius;
}
the rectangle will be considered two points, first point will be the touchBegan point, and on touchMove will be the second point, the rectangle will be dynamically drawn according to the user finger move, (like when you click one click on desktop and move the mouse you will get dynamically rectangle).
Thanks
Okay here is how you can draw your rect in touchesMoved (use #Nekto touchesBegan to store the starting point of your rect).
Let's assume you keep a reference on the UIView we are drawing and call it drawnView.
In touchesBegan, we set the origin of the frame for drawnView
Here is touchesMoved :
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent*)event
{
// We only take care of a single touch
if ([touches count] == 1)
{
UITouch *touch = [touches anyObject];
// Find the width and height of the rect
CGRect drawnFrame = drawnView.frame
drawnFrame.size.width = [touch locationInView:parentView].x - drawnFrame.origin.x
drawnFrame.size.height = [touch locationInView:parentView].y - drawnFrame.origin.y
[drawnView setFrame:drawnFrame];
}
}
Don't copy paste that code, I didn't write it in Xcode or anything. It could have mistakes (for sure). But I hope it can help to find your solution. And be careful, I can't tell how it will behave if you drag your finger to the top, or to the left (negative values for height and width).
You should define subclass of UIView, for example, DrawingView
In DrawingView you should implement following methods:
Save start point in method: - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
Redraw rect here - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
Hide rect (or save it) here - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
and here - (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
For example, in first method you could detect start point in following way:
CGPoint startCornerOfYourRect;
for (UITouch *touch in touches)
{
startCornerOfYourRect = [touch locationInView:self];
break;
}
Is there any easy way to detect these kinds of gestures for iPhone?
I can use touchesBegan, touchesMoved, touchesEnded. But how can I implement the gestures? thz u.
You will using UISwipeGestureRecognizer Object to detect the Touch and direction of Swipe In
UIView or anywhere in iphone sdk.
UISwipeGestureRecognizer *rightRecognizer = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:#selector(rightSwipeHandle:)];
rightRecognizer.direction = UISwipeGestureRecognizerDirectionRight;
[rightRecognizer setNumberOfTouchesRequired:1];
//add the your gestureRecognizer , where to detect the touch..
[view1 addGestureRecognizer:rightRecognizer];
[rightRecognizer release];
UISwipeGestureRecognizer *leftRecognizer = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:#selector(leftSwipeHandle:)];
leftRecognizer.direction = UISwipeGestureRecognizerDirectionLeft;
[leftRecognizer setNumberOfTouchesRequired:1];
[view1 addGestureRecognizer:leftRecognizer];
[leftRecognizer release];
- (void)rightSwipeHandle:(UISwipeGestureRecognizer*)gestureRecognizer
{
NSLog(#"rightSwipeHandle");
}
- (void)leftSwipeHandle:(UISwipeGestureRecognizer*)gestureRecognizer
{
NSLog(#"leftSwipeHandle");
}
I think this the Better solution for your problem
You are on the right track. In touchesBegan you should store the position where the user first touches the screen.
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
self.startPosition = [touch locationInView:self];
}
Similar code in touchesEnded gives you the final position. By comparing the two positions you can determine the direction of movement. If the x-coordinate has moved beyond a certain tolerance you have a left or right swipe.
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
CGPoint endPosition = [touch locationInView:self];
if (startPosition.x < endPosition.x) {
// Right swipe
} else {
// Left swipe
}
}
You do not need touchesMoved unless you want to detect and track a swipe while the user is still touching the screen. It may also be worth testing that the user has moved a minimal distance before deciding they have performed a swipe.
If you're willing to target iPhone OS 3.2 or later (all iPads or updated iPhones), use the UISwipeGestureRecognizer object. It will do this trivially, which is wickedly cool.
I'm developing a game that will use the force of the swipe as a variable user input.
I read from the documentation that on the touchesEnded event, I can get the allTouches array which is a list of the user touches collected from touchesBegan. From this I plan to get the last two touches to get the direction of the swipe. I will also get the time interval between touchesBegan and touchesEnded, from which I will get the speed of the swipe. I will use the direction and the speed to calculate the force of the swipe.
What I'd like to know is: is there a better way to do this? Is this already encapsulated in a library call somewhere?
Thanks in advance.
Solve like this
- (void)rotateAccordingToAngle:(float)angle
{
[spinWheel setTransform:CGAffineTransformRotate(spinWheel.transform, angle)];
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
[spinWheel.layer removeAllAnimations];
previousTimestamp = event.timestamp;
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
if (touch.view==spinWheel)
{
UITouch *touch = [touches anyObject];
CGPoint center = CGPointMake(CGRectGetMidX([spinWheel bounds]), CGRectGetMidY([spinWheel bounds]));
CGPoint currentTouchPoint = [touch locationInView:spinWheel];
CGPoint previousTouchPoint = [touch previousLocationInView:spinWheel];
CGFloat angleInRadians = atan2f(currentTouchPoint.y - center.y, currentTouchPoint.x - center.x) - atan2f(previousTouchPoint.y - center.y, previousTouchPoint.x - center.x);
[self rotateAccordingToAngle:angleInRadians];
CGFloat angleInDegree = RADIANS_TO_DEGREES(angleInRadians);
revolutions+= (angleInDegree/360.0f);
}
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
if (touch.view==spinWheel)
{
NSTimeInterval timeSincePrevious = event.timestamp - previousTimestamp;
CGFloat revolutionsPerSecond = revolutions/timeSincePrevious;
NSLog(#"%.3f",revolutionsPerSecond);
[self startAnimationWithRevolutions:revolutionsPerSecond forTime:5.0f];
}
revolutions = 0;
}
- (void)animationDidStop:(CAAnimation *)theAnimation finished:(BOOL)flag
{
spinWheel.userInteractionEnabled = TRUE;
if (timerUpdate) {
[timerUpdate invalidate];
timerUpdate = nil;
}
}
-(void)updateTransform{
spinWheel.transform = [[spinWheel.layer presentationLayer] affineTransform];
}
-(void)startAnimationWithRevolutions:(float)revPerSecond forTime:(float)time
{
spinWheel.userInteractionEnabled = FALSE;
float totalRevolutions = revPerSecond * time;
timerUpdate = [NSTimer scheduledTimerWithTimeInterval:0.1f target:self selector:#selector(updateTransform) userInfo:nil repeats:YES];
[CATransaction begin];
[CATransaction setValue:[NSNumber numberWithFloat:time] forKey:kCATransactionAnimationDuration];
CABasicAnimation* spinAnimation = [CABasicAnimation
animationWithKeyPath:#"transform.rotation"];
CGAffineTransform transform = spinWheel.transform;
float fromAngle = atan2(transform.b, transform.a);
float toAngle = fromAngle + (totalRevolutions*4*M_PI);
spinAnimation.fromValue = [NSNumber numberWithFloat:fromAngle];
spinAnimation.toValue = [NSNumber numberWithFloat:toAngle];
spinAnimation.repeatCount = 0;
spinAnimation.removedOnCompletion = NO;
spinAnimation.delegate = self;
spinAnimation.timingFunction = [CAMediaTimingFunction functionWithName:
kCAMediaTimingFunctionEaseOut];
[spinWheel.layer addAnimation:spinAnimation forKey:#"spinAnimation"];
[CATransaction commit];
}
I've accomplished something along these lines rather simply by just comparing [touch locationInView:self] to [touch previousLocationInView:self] in touchesEnded of the Class (subclass of UIView) of the object that will be moved. This will give you a vector with location, direction & rough sense of velocity at the moment user released finger from the iPhone.
You could use a UIPanGestureRecognizer which has function velocityInView. See https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIPanGestureRecognizer_Class/
Rather than using allTouches on the UIEvent, you should use the touches set that you get in the call to your UIView's (or other UIResponder's) touchesEnded:withEvent: method. This ensures that you don't get touches that belong to other views.
Since the iPhone is a multi-touch device, the set contains all the touches that are associated with that event. In other words, if the user is touching the screen with two fingers, there should be two UITouch objects in the set, etc.
This means that the set does not contain all the points traversed since a touch began until it ended. To track that, you have to save the start point and time in touchesBegan:withEvent:, and then when the touch ends you calculate the speed based on that.
Note that if the set contains several points (which means the user is touching the screen with several fingers), you have to try to keep track of which UITouch object corresponds to which finger. You will want to do this in touchesMoved:withEvent:.
Since you get the touches in a set, you can't use an index or some key value to keep track of the touches you are interested in. I believe that the recommended way of doing this is to just assume that the touch that is closest to the point you saved on the previous event comes from the same finger. If you want to be more exact you can also use the UITouch's previousLocationInView: method.
If you're lazy, you can also simply do [[touches allObjects] objectAtIndex:0] and hope that this will give you the right touch (ie the one originating from the same finger) on each event. This actually often works, but I don't think you should use it in production code, especially not if you're trying to create an app with multi-touch functionality.