Multitouch with UIPanGestureRecognizer - iphone

I'm trying to implement UIPanGestureRecognizer for my view. How do I add multitouch? Below is the code from my view (a subclass of UIView). I would like to be able to know the location and velocity for all of the touches at the same time. The current code only prints out the location and velocity for one touch. Changing the properties minimumNumberOfTouches and maximumNumberOfTouches doesn't work. Thank you very much for your help.
UIPanGestureRecognizer *panGestureRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(handlePanGesture:)];
panGestureRecognizer.cancelsTouchesInView = NO;
[self addGestureRecognizer:panGestureRecognizer];
- (void)handlePanGesture:(UIPanGestureRecognizer *)panGestureRecognizer
{
CGPoint location = [panGestureRecognizer locationInView:panGestureRecognizer.view];
CGPoint velocity = [panGestureRecognizer velocityInView:panGestureRecognizer.view];
NSLog(#"Location: %#", NSStringFromCGPoint(location));
NSLog(#"Velocity: %#", NSStringFromCGPoint(velocity));
}

From the apple documentation for UIGestureRecogniser
(CGPoint)locationOfTouch:(NSUInteger)touchIndex inView:(UIView *)view
Parameters
touchIndex
The index of a UITouch object in a private array maintained by the receiver. This touch object represents a touch of the current gesture.
view
A UIView object on which the gesture took place. Specify nil to indicate the window.
Return Value
A point in the local coordinate system of view that identifies the location of the touch. If nil is specified for view, the method returns the touch location in the window’s base coordinate system.
(NSUInteger)numberOfTouches
Return Value
The number of UITouch objects in a private array maintained by the receiver. Each of these objects represents a touch in the current gesture.
Discussion
Using the value returned by this method in a loop, you can ask for the location of individual touches using the locationOfTouch:inView: method.
For example:
(Objective-C)
for(int i=0; i<[panGestureRecogniser numberOfTouches]; i++)
{
CGPoint pt = [panGestureRecogniser locationOfTouch:i inView:self];
}
(Swift)
for i in 0..<panGestureRecogniser.numberOfTouches {
let pt = recognizer.location(ofTouch: i, in: panGestureRecognizer.view)
}
As for velocity I believe there is only one value for it and there is no way to get the velocity of each touch without writing a custom method that calculates the difference between each touch over a series of calls. There is no guarantee that the touches will be at the same index each time however.
Note: As for the minimum and maximum number of touches, these will need to be set accordingly to get multiple touches.

Related

frame by frame animation with UIGestureRecognizer

I would like to show an animation with images of an object rotating.
I have an NSArray with frames of the object and I would like to display them frame by frame with the property of UIImageView animationImages.
The problem is that I would like to control the animation with UISwipeGestureRecognizer (Right and Left). And depending on the speed of the gesture the object should rotate faster or slower. I think is not possible because the gesture it is called just once and not continuously.
(NOTE: this is my first question in StackOverflow)
Thank you in advance
EDIT: I just post my solution. Maybe it can be useful for anybody. I think it's useful.
Firstly: add gesture to the view.
self.recognizer = [[UISwipeGestureRecognizer alloc] init];
self.recognizer.cancelsTouchesInView=NO;
[self.view addGestureRecognizer:self.recognizer];
Secondly: in tocuhMoved method I display the frame that I need depending of the direction of the previous touch. It is called by the event of the user.
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event{
NSInteger image= [self.animationView.animationImages indexOfObject:self.animationView.image];
UITouch *touch = [touches anyObject];
NSLog(#"Touch moved with index:%i", image);
if ((positionTouch.x<[touch locationInView:self.animationView].x)) {
if (image==24) {
self.animationView.image=[self.animationView.animationImages objectAtIndex:0];
}
else{
self.animationView.image=[self.animationView.animationImages objectAtIndex:image+1];
}
}
else if((positionTouch.x>[touch locationInView:self.animationView].x)){
if (image==0) {
self.animationView.image=[self.animationView.animationImages objectAtIndex:24];
}
else{
self.animationView.image=[self.animationView.animationImages objectAtIndex:image-1];
}
}
positionTouch= [touch locationInView:self.animationView];
}
Don't forget <UIGestureRecognizerDelegate>
with this method, I fill the array of frames...
-(void)addPictures{
NSMutableArray* mArray=[[NSMutableArray alloc]initWithCapacity:25];
for (int i=0; i<25; i++) {
[mArray insertObject:[UIImage imageNamed:[NSString stringWithFormat:#"Picture %i.jpg", i+1]] atIndex:i];
}
self.animationView.image=[mArray objectAtIndex:0];
self.animationView.animationImages=mArray;
[self.view addSubview:self.animationView];
}
It's actually very simple to control the speed of an animation. In fact, it's so simple (because CALayer conforms to the CAMediaTiming protocol) that the property to control it is literally called speed. It's all a matter of using the gesture as a sort of analog for a slider, and calculating an x value relative to it's location onscreen. This should be a suitable starting point:
//Speed changes necessitate a change in time offset, begin time, and
//speed itself. Simply assigning to speed it not enough, as the layer needs
//to be informed that it's animation timing function is being mutated. By
//assigning a new time offset and a new begin time, the layer
//automatically adjusts the time of any animations being run against it
layer.timeOffset = [layer convertTime:CACurrentMediaTime() fromLayer:nil];
layer.beginTime = CACurrentMediaTime();
layer.speed = ([self.gesture locationInView:self.view].x / CGRectGetWidth(self.view.bounds));

How to make a sprite follow a trail?

I'm making a game where the player draws a line and then a sprite should follow and run on it. I have a mutable array and also a draw method which works well. but I'm having trouble figuring out a way to move the sprite. I have tried different approaches but I can't get the iterator to work.
It's supposed to work by iterating through the array. which is populated with CGPoint locations previously stored. I try to move the sprite in ccTouchedEnded but it highlights [toucharray objectAtIndex:0] and says "passing 'id' to parameter of incompatible type 'CGPoint (aka 'struct CGPoint')"
-(void) ccTouchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
//remove objects each time the player makes a new path
[toucharray removeAllObjects];
}
-(void) ccTouchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [ touches anyObject];
CGPoint new_location = [touch locationInView: [touch view]];
new_location = [[CCDirector sharedDirector] convertToGL:new_location];
CGPoint oldTouchLocation = [touch previousLocationInView:touch.view];
oldTouchLocation = [[CCDirector sharedDirector] convertToGL:oldTouchLocation];
oldTouchLocation = [self convertToNodeSpace:oldTouchLocation];
// add touches to the touch array
[toucharray addObject:NSStringFromCGPoint(new_location)];
[toucharray addObject:NSStringFromCGPoint(oldTouchLocation)];
}
-(void)draw
{
glEnable(GL_LINE_SMOOTH);
for(int i = 0; i < [toucharray count]; i+=2)
{
CGPoint start = CGPointFromString([toucharray objectAtIndex:i]);
CGPoint end = CGPointFromString([toucharray objectAtIndex:i+1]);
ccDrawLine(start, end);
}
}
-(void) ccTouchesEnded:(NSSet *)touches withEvent:(UIEvent *)event{
// here is the line I can't get to work to move the sprite
_sprite.position = ccpAdd(ccpMult([toucharray objectAtIndex:0], progress), ccpMult([toucharray objectAtIndex:0+1], 1-progress));
}
I did this before, by (what you have so far) creating an array
Mine was with multiple sprites on screen, so I also had 1 definition "Touched sprite"
ontouchstart empty array and add first point (start point) + set TouchedSprite to the Sprite that was closest to the start point)
ontouchmove add point to array
ontouchend (run the array on the sprite by actions (get back to that later)
A few problems is what I got, (1) I could only control 1 sprite at a time and (2) the drawn line had way too many points.
Solution to number 1:
Create a subclass Object of a sprite and create your sprites like that. Within that object create an array (make it accessible with #property and #synthesize) which will allow you to place the drawn path points in. Also create a public method called "runPathAction".
So onTouchStart you clean the Array and set the selected spriteObject. OnTouchMove add the items, OntouchEnd set the array of the selected spriteObject to the local array and then run the "runPathAction" method. (You could have passed it to the method, but I like doing it this way, just in case I want to access the array)
Solution to number 2:
I found that drawing the line creates WAY to many points. So, i created a boolean operator called "CanDraw" and a schedule with a time interval of 0.1 (you can play around with it) to a method which sets canDraw to YES. and then in the onTouchMove you check for "canDraw == YES", add the point and set canDraw=NO;
This way you will have an interval of 0.1 seconds to add the points. Don't forget to remove the schedule onTouchEnd!!!
Now how to run the actions.
You will want a steady speed, so you will need to set a speed variable.
Walk through the array of points and calculate the distance between each point to create a total distance. (PS Don't forget that the first pint is from CurrentLocation to Point0 in the array)
When you have the total distance you can figure out how much delay time you should set in your actions per step. (if you don't do this, you don't know the delay time and if you fix that you get weird movement).
Create a method,
check the "count" of the array. If count = 0 return; (finished!!! do cleanup or whatever)
otherwise run an actionsequence, which at the end calls itself. (the check for count will handle the "break".
grab 1 item of the array CGPoint p = (objectAtIndex:0); and remove that item from the array (removeAtIndex:0).
Run the action with the delay time and there you go!
Record the positions of the path in an array or a list and iterate through that to move your sprite along the trail. I did this in a game I made to create a particle trail behind the player. I used a size 20 array and iterated through that on an interval, updating the array at the iterator's position with the location of my character and then moving the particle effect to the location stored in the array at the position of the iterator plus 1.
You'll need to seed the array with the starting location so that you don't have null values and you'll need a special case for when you're at the end of the array because you don't want to read from an out of bounds location, instead have your code read from the 0 position.

Setting boundary conditions for object to traverse a path through dragging the object

I am developing an application in which there is one object which is draggable.The user can drag this object in a specified path, not outside the path.
For achieving this I had to set the boundary conditions so that if the touch point is beyond that so the object does not move.
My problem is that my object size is lets say 30*30.
Now if I touch the object from its hair means at above portion and then drag it to down it gives one value, but if I touch the same object at some below position lets say his chin , then move the object at the same position where we moved the previous one.Now in this case, the the point of view are different from the previous one.
So I am not able to understand what should I set the boundary conditions,if I set the conditions of first value then in his case the second touch process will not able to move, which is wrong and if I set the second case value then the first case object will able to move below the boundary also.
I am not using cocos 2D.It is simple ipad application.
For more understanding I have added the image now if you touch the green circle on his left portion and moved towards the right, the last value is like 760 but if you touch on the right portion and moved towards the right then the last points come as 780 something. same case happens if object is dragged vertically.
My basic requirement is that the object should only move inside the path shown in image not outside it.
- (void)touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event {
// Retrieve the touch point
CGPoint pt = [[touches anyObject] locationInView:gamePice];
startLocation = pt;
}
- (void)touchesMoved:(NSSet*)touches withEvent:(UIEvent*)event
{
CGPoint pt1 = [[touches anyObject] locationInView:gamePice];
CGPoint pt = [[touches anyObject] locationInView:self.view];
NSLog(#"moved=%f|%f",pt.x,pt.y);
if (pt.x>=306 && pt.y>=148 && pt.x<=720 && pt.y<=175) { //these conditions how to set
CGRect frame = [gamePice frame];
frame.origin.x += pt1.x - startLocation.x;
frame.origin.y += pt1.y - startLocation.y;
[gamePice setFrame:frame];
}
}
Please suggest how to implement this feature or what approach should I follow.
Make an outer CGRect, lets say , CGRectMake(306, 148,400,100) , its your path rect, you many need multiple rects for that, but for now start with one.
make new frame for gamePice, that will be the frame according to touch location, lets say you make it like this,
CGRect frame = [gamePice frame];
frame.origin.x += pt1.x - startLocation.x;
frame.origin.y += pt1.y - startLocation.y;
then your condition will be,
if (CGRectContainsRect(outerRect, frame) )
{
[gamePice setFrame:frame];
}
// else dont move...

(iphone) how to set view.center when detaching a view from scroll view and adding it to another view?

I'd like to move a view from a scrollview to a uiview.
I'm having trouble changing it's center(or frame) so that it remains in the same position in screen (but in a different view, possibly the superview of scrollview).
How should I convert the view's center/frame?
Thank you.
EDIT:
CGPoint oldCenter = dragView.center;
CGPoint newCenter = [dragView convertPoint: oldCenter toView: self.navigationView.contentView];
dragView.center = newCenter;
[self.navigationView.contentView addSubview: dragView];
I can also use (NSSet*) touches since i'm in touchesBegan:
I was having hard time to make it work but the doc wasn't so clear to me.
You can use convertPoint:toView: method of UIView. It is used to convert a point from one view's coordinate system to another. See Converting Between View Coordinate Systems section of UIView class reference. There are more methods available.
-edit-
You are using the wrong point when calling convertPoint: method. The given point should be in dragView's coordinate system where as dragView.center is in its superview's coordinate system.
Use the following point and it should give you the center of dragView in its own coordinate system.
CGPoint p;
p = CGPointMake(dragView.bounds.size.width * 0.5, dragView.bounds.size.height * 0.5);

iPhone touch screen events

in iPhone SDK there are touchesBegan, touchesEnded and touchesMoved functions that are called when a touch event appears. My problem is that when I put a finger on the screen and I'm not moving it there is no way to know if there is a finger on the screen or not. Is there a way to get current touch screen state?
In any of touchesBegan, touchesEnded and touchesMoved functions I can use fallowing code to know the state for every touch, but outside them I can't:|
NSSet *allTouches = [event allTouches];
for(GLuint index = 0; index < [allTouches count]; ++index)
{
UITouch *touch = [[allTouches allObjects] objectAtIndex:index];
if([touch phase] != UITouchPhaseCancelled)
{
CGPoint touchPoint = [touch locationInView:self];
//do something with touchPoint that has state [touch phase]
}
}
You should keep a list of all points where the touches currently are. Make sure you don't just retain the UIEvent or UITouch objects (they're not guaranteed to live long) - instead, create your own data data structure. Odds are all you need is to keep track of the points where touches are currently down.
If you don't want to fix your broken design, why doesn't keeping a running list of active touches solve your problem?
NSMutableSet *touches;
In touchesBegan you add them, in touchesEnded and touchesCancelled you remove them...
You might use a "global" object (i.e. singleton) to track those and synchronize data access. I cannot see how querying this set would be different than asking UIKit directly for this information.
If you need information on touches on standard UIKit objects (i.e. UIButton), you probably need to subclass those - and I am not sure if you can get the desired effect with all classes (as they - or their subviews - could handle touches in any way they want).