iPhone: Move UIImageView continuously with swipe - iphone

I want to move an image (cBlock) continuously with a gesture. The direction of the swipe gesture determines the direction of the movement. I am able to move, but not continuously. Although the finger is held down, the movement starts/stops. Can anyone see why?
I believe touchesMoved is called continuously, as the log statements appear to be firing off continuously. Also, this logic does not seem to account for the finger being held down while the direction becomes positive, then negative (ex. users pans right, then pans left without lifting finger.) Is there a proven solution for that case?
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
gestureStartPoint = [touch locationInView:gridView];
prevGesturePoint = gestureStartPoint;
}
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event{
UITouch *touch = [touches anyObject];
CGPoint currentPosition = [touch locationInView:gridView];
CGFloat deltaX = currentPosition.x - prevGesturePoint.x;
CGFloat deltaY = currentPosition.y - prevGesturePoint.y;
if(running == YES){
if(fromTop || fromBottom){
if((currentPosition.x - prevGesturePoint.x) > 0) {
NSLog(#"swipe right.");
[cBlock movXSinglePixel:10]; // move 10px
} else if((currentPosition.x - prevGesturePoint.x) < 0) {
NSLog(#"swipe left.");
[cBlock movXSinglePixel:-10]; //move -10px
}
}
}
[container setNeedsDisplay];
prevGesturePoint = currentPosition;
}
- (void)movXSinglePixel:(int)dir{
[UIView beginAnimations:#"center" context:nil];
[UIView setAnimationDuration:0.01];
[UIView setAnimationCurve:UIViewAnimationCurveEaseIn];
if(tX + dir > 8 || tX + dir < 0){
// Attempt to move out of grid bounds.
return;
}
CGPoint center = [self center];
center.x += (dir * 1.0f);
[self setCenter:center];
tX += dir;
[UIView commitAnimations];
}

Related

iphone - how to scroll uiscrollview from event touched by uibutton in scrollview

structure
UIViewController
- UIScrollview
- UIButton
I'm going to make scrollview can receive button event. So whenever user do scrolling(dragging) on the button, the scrollview react as scrolling itself.
I made button down and moved handler with event forwarding like below
- (IBAction)buttonTouchedMove:(id)sender withEvent:(UIEvent *)event {
[[sender nextResponder] touchesMoved:[event allTouches] withEvent:event];
}
- (IBAction)buttonTouchedDown:(id)sender withEvent:(UIEvent *)event {
[[sender nextResponder] touchesBegan:[event allTouches] withEvent:event];
}
and to move scrollview as touches change, I made below codes
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
self.oldPoint = [touch locationInView:self.view];
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
CGPoint offset = self.scrollView1.contentOffset;
UITouch *touch = [touches anyObject];
self.newPoint = [touch locationInView:self.view];
int diffX = newPoint.x - oldPoint.x;
offset.x = offset.x - diffX;
[scrollView1 setContentOffset:offset animated:YES];
self.oldPoint = self.newPoint;
}
but, scrollview react strange.. not move enough as I touch moved.
// get the button width
CGFloat buttonWidth = self.theButton.frame.size.width;
// replace your button name here
// now get the view width
CGFloat viewWidth = self.view.frame.size.width;
// replace your view name here
// now get the multiplier which i said u as 10
CGFloat mult = viewWidth / buttonWidth;
// and now use it in your code
.
.
.
diffX = mult*(newPoint.x - oldPoint.x);
.
.
.
Final answer, Using UIView animation, I got what i want.
- (IBAction)buttonTouchedDown:(id)sender withEvent:(UIEvent *)event {
UITouch *touch = [[event allTouches] anyObject];
self.oldPoint = [touch locationInView:self.view];
self.velocity = 0;
isButtonTouchedDown = YES;
}
- (IBAction)buttonTouchedMove:(id)sender withEvent:(UIEvent *)event {
CGPoint offset = self.scrollView1.contentOffset;
UITouch *touch = [[event allTouches] anyObject];
self.newPoint = [touch locationInView:self.view];
int diffX = self.newPoint.x - self.oldPoint.x;
velocity = diffX;
offset.x = offset.x - diffX;
[self.scrollView1 setContentOffset:offset animated:NO];
self.oldPoint = self.newPoint;
}
There is a trick..I calculated velocity by differentiating moved distance in TouchedMove function.
In Button TouchUp event handler, I controlled Scrollview with velocity value and CurveEaseout animation to scroll more. By offering very short duration of animation and repeating it if there is no event(button touched down). It becomes very similar with scrollview's animation.
- (IBAction)buttonTouchedUp:(id)sender withEvent:(UIEvent *)event {
CGPoint offset = self.scrollView1.contentOffset;
amountX = (self.velocity)*(abs(self.velocity));
dX = amountX;
isButtonTouchedDown = NO;
if (offset.x - amountX < 0) {
offset.x = 0;
[scrollView1 setContentOffset:offset animated:YES];
}
else if (abs(dX) < 70) { // 70: content item size
offset.x = roundf(offset.x/70)*70;
[scrollView1 setContentOffset:offset animated:YES];
}
else {
[self endScrollAnimation];
}
- (void)startAnimation:(float)x moveAmount:(float)moveAmount
{
CGPoint offset = self.scrollView1.contentOffset;
[UIView beginAnimations:nil context:nil];
[UIView setAnimationDuration:duration];
offset.x = x < 0 ? offset.x + moveAmount : offset.x -moveAmount;
[UIView setAnimationCurve:UIViewAnimationCurveLinear];
[UIView setAnimationDelegate:self];
[UIView setAnimationDidStopSelector:#selector(endScrollAnimation)];
[scrollView1 setContentOffset:offset animated:NO];
[UIView commitAnimations];
}
- (void)endScrollAnimation
{
CGPoint offset = self.scrollView1.contentOffset;
float slowMoveAmount = abs(amountX) * 0.05;
float fastMoveAmount = abs(amountX) * 0.1;
if (isButtonTouchedDown) {
return;
}
else if (abs(dX) > abs(amountX)*0.35 && offset.x > 0) {
[self startAnimation:dX moveAmount:fastMoveAmount];
dX = dX < 0 ? dX + fastMoveAmount : dX -fastMoveAmount;
}
else if (abs(dX) > slowMoveAmount && offset.x > 0) {
[self startAnimation:dX moveAmount:slowMoveAmount];
dX = dX < 0 ? dX + slowMoveAmount : dX - slowMoveAmount;
}
else {
offset.x = roundf(offset.x/70)*70;
[scrollView1 setContentOffset:offset animated:YES];
}
}

iPhone - Inertial dragging

I have an object that the user has to drag around the screen. This is a regular UIImageView. Currently, the image can be dragged around but when you release it, it stops where your finger lifted the image. What I want is to create momentum, so I can give an impulse to the image and it scrolls until it bounces the screen edge.
I don't want nothing very complex like adding physics libraries and stuff like that. I want to keep it as minimum as possible.
How do I do that? Can you guys point me to some tutorial or in the right direction?
thanks.
Well one way of doing this without using any physics library is this:
First in your class create 4 instance variables like this:
CFTimeInterval startTime;
CGPoint startPoint;
CGPoint oldPoint;
BOOL imageViewTouched;
Than in your - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event method have this code:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [[event allTouched] anyObject];
// Test to see if the touched view is your image view. If not just return.
if (touch.view != <#yourImageView#>) {
return;
}
startPoint = [touch locationInView:self.view];
oldPoint = startPoint;
startTime = CACurrentMediaTime();
imageViewTouched = YES;
}
For - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event do this:
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
// Fingers were moved on the screen but your image view was not touched in the beginning
if (!imageViewTouched) {
return;
}
UITouch *touch = [[event allTouches] anyObject];
CGPoint newPoint = [touch locationInView:self.view];
<#yourImageView#>.frame = CGRectOffset(<#yourImageView#>.frame, newPoint.x - oldPoint.x, newPoint.y - oldPoint.y);
oldPoint = newPoint;
}
Now for - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event try this:
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
// Fingers were removed from the screen but your image view was not touched in the beginning
if (!imageViewTouched) {
return;
}
imageViewTouched = NO;
UITouch *touch = [[event allTouches] anyObject];
CGPoint endPoint = [touch locationInView:self.view];
CFTimeInterval endTime = CACurrentMediaTime();
CFTimeInterval timeDifference = endTime - startTime;
// You may play with this value until you get your desired effect
CGFloat maxSpeed = 80;
CGFloat deltaX = 0;
CGFloat deltaY = 0;
if (timeDifference < 0.35) {
deltaX = (endPoint.x - startPoint.x) / (timeDifference * 10);
deltaY = (endPoint.y - startPoint.y) / (timeDifference * 10);
}
if (deltaX > maxSpeed) { deltaX = maxSpeed; }
else if (deltaX < -maxSpeed) { deltaX = -maxSpeed; }
else if (deltaX > -5 && deltaX < 5) { deltaX = 0; }
if (deltaY > maxSpeed) { deltaY = maxSpeed; }
else if (deltaY < -maxSpeed) { deltaY = -maxSpeed; }
else if (deltaY > -5 && deltaY < 5) { deltaY = 0; }
[UIView begginAnimations:nil context:nil];
[UIView setAnimationDuration:0.5f];
[UIView setAnimationCurve:UIViewAnimationCurveEaseOut];
<#yourImageView#>.frame = CGRectOffset(<#yourImageView#>.frame, deltaX, deltaY);
[UIView commitAnimations];
}
Please let me know if this makes sense and/or works for you. Cheers!

Identifying pinch gesture drag gesture on iPhone?

I have a small image on a view. The view is the object process multi-touch actions. If a finger drag on the view, the image will translate its position. And if user use 2 fingers to make pinch gesture, the image will scale its size. And I do work as section code below:
//touch detect methods
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
NSLog(#"Touch began");
//Devide into 2 cases: 1 touch and 2 touches.
if ([touches count] == 1) {
NSLog(#"Touch began cout = 1");
currentImageCenter = focusImage.center;
UITouch *touch = [[touches allObjects] objectAtIndex:0];
previousPoint = [touch locationInView:self];
isTwoFingerTouching = FALSE;
}
else if([touches count] == 2){
NSLog(#"Touch began cout = 2");
UITouch *touch = [[touches allObjects] objectAtIndex:0];
beginFirstPoint = [touch locationInView:self];
touch = [[touches allObjects] objectAtIndex:1];
beginSecondPoint = [touch locationInView:self];
isTwoFingerTouching = TRUE;
}
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
if ([touches count] == 1 && isTwoFingerTouching == FALSE) {
UITouch *touch = [touches anyObject];
CGPoint currentPosition = [touch locationInView:self];
//Calculate distance
double deltaX = currentPosition.x - previousPoint.x;
double deltaY = currentPosition.y - previousPoint.y;
NSLog(#"Touch move detect 1 touch ");
focusImage.center = CGPointMake(currentImageCenter.x+deltaX, currentImageCenter.y+deltaY);
}
else if([touches count] == 2){
NSLog(#"Touch move detect 2 touches");
CGPoint currentFirstPoint;
CGPoint currentSecondPoint;
UITouch *touch = [[touches allObjects] objectAtIndex:0];
currentFirstPoint = [touch locationInView:self];
CGPoint previousFirstPoint = [touch previousLocationInView:self];
touch = [[touches allObjects] objectAtIndex:1];
currentSecondPoint = [touch locationInView:self];
CGPoint previousSecondPoint = [touch previousLocationInView:self];
//Compare previous points with current points.
//Pinch gesture
CGFloat beginDistance = distanceBetweenPoints(previousFirstPoint, previousSecondPoint);
CGFloat currentDistance = distanceBetweenPoints(currentFirstPoint, currentSecondPoint);
if (currentDistance > 0 && beginDistance > 0) {
double scale = currentDistance/beginDistance;
NSLog(#"%f", scale);
//Rotation
CGPoint vector1 = CGPointMake(previousFirstPoint.x - previousSecondPoint.x, previousFirstPoint.y - previousSecondPoint.y);
CGPoint vector2 = CGPointMake(currentFirstPoint.x - currentSecondPoint.x, currentFirstPoint.y - currentSecondPoint.y);
//[vector1, vector2].
double zValue = vector1.x*vector2.y - vector1.y*vector2.x;
CGFloat rotateAngle = angleBetweenLines(previousFirstPoint, previousSecondPoint, currentFirstPoint, currentSecondPoint);
//zValue < 0, vector1 rotate counter-clockwise, so the angle should be negative.
if (zValue < 0) {
rotateAngle = -rotateAngle;
}
//Don't allow to zoom out if the image is too small
if (scale > 1 || focusImage.frame.size.width > 30) {
CGAffineTransform previousTransform = focusImage.transform;
CGAffineTransform mixTransform = CGAffineTransformConcat(CGAffineTransformMakeScale(scale, scale), CGAffineTransformMakeRotation(rotateAngle));
focusImage.transform = CGAffineTransformConcat(previousTransform, mixTransform);
}
}
}
}
But the problem is: I can't identify whether 1 finger or 2 fingers on screen. When I touch 2 fingers, and I move (for rotate and scale) those fingers, [touches count] in touchesMoved: method still equal 1 occasionally. Any one experienced this, please tell me how to solve my problem?
Any reason why you are not using UIGestureRecognizers? It will save you a lot of work if you just use a UIPanGestureRecognizer and a UIPinchGestureRecognizer.
In any case, iOS has always behaved like that. Unless you touch with both fingers at the exact same millisecond, you will detect a one finger touch first then detect two finger touch. You need to employ some sort of a cancelling mechanism in your code if you still want to go the -touchesBegan..., -touchesMoved..., etc., method.

Implementing touch-based rotation in cocoa touch

I am wondering what is the best way to implement rotation-based dragging movements in my iPhone application.
I have a UIView that I wish to rotate around its centre, when the users finger is touch the view and they move it. Think of it like a dial that needs to be adjusted with the finger.
The basic question comes down to:
1) Should I remember the initial angle and transform when touchesBegan is called, and then every time touchesMoved is called apply a new transform to the view based on the current position of the finger, e.g., something like:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
CGPoint currentPoint = [touch locationInView:self]; //current position of touch
if (([touch view] == self)
&& [Utility getDistance:currentPoint toPoint:self.middle] <= ROTATE_RADIUS //middle is centre of view
&& [Utility getDistance:currentPoint toPoint:self.middle] >= MOVE_RADIUS) { //will be rotation gesture
//remember state of view at beginning of touch
CGPoint top = CGPointMake(self.middle.x, 0);
self.initialTouch = currentPoint;
self.initialAngle = angleBetweenLines(self.middle, top, self.middle, currentPoint);
self.initialTransform = self.transform;
}
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event{
UITouch *touch = [touches anyObject];
CGPoint currentPoint = [touch locationInView:self]; //current position of touch
if (([touch view] == self)
&& [Utility getDistance:currentPoint toPoint:self.middle] <= ROTATE_RADIUS
&& [Utility getDistance:currentPoint toPoint:self.middle] >= MOVE_RADIUS) { //a rotation gesture
//rotate tile
float newAngle = angleBetweenLines(self.middle, CGPointMake(self.middle.x, 0), self.middle, currentPoint); //touch angle
float angleDif = newAngle - self.initialAngle; //work out dif between angle at beginning of touch and now.
CGAffineTransform newTransform = CGAffineTransformRotate(self.initialTransform, angleDif); //create new transform
self.transform = newTransform; //apply transform.
}
OR
2) Should I simply remember the last known position/angle, and rotate the view based on the difference in angle between that and now, e.g.,:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
CGPoint currentPoint = [touch locationInView:self]; //current position of touch
if (([touch view] == self)
&& [Utility getDistance:currentPoint toPoint:self.middle] <= ROTATE_RADIUS
&& [Utility getDistance:currentPoint toPoint:self.middle] >= MOVE_RADIUS) { //will be rotation gesture
//remember state of view at beginning of touch
CGPoint top = CGPointMake(self.middle.x, 0);
self.lastTouch = currentPoint;
self.lastAngle = angleBetweenLines(self.middle, top, self.middle, currentPoint);
}
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event{
UITouch *touch = [touches anyObject];
CGPoint currentPoint = [touch locationInView:self]; //current position of touch
if (([touch view] == self)
&& [Utility getDistance:currentPoint toPoint:middle] <= ROTATE_RADIUS
&& [Utility getDistance:currentPoint toPoint:middle] >= MOVE_RADIUS) { //a rotation gesture
//rotate tile
float newAngle = angleBetweenLines(self.middle, CGPointMake(self.middle.x, 0), self.middle, currentPoint); //touch angle
float angleDif = newAngle - self.lastAngle; //work out dif between angle at beginning of touch and now.
CGAffineTransform newTransform = CGAffineTransformRotate(self.transform, angleDif); //create new transform
self.transform = newTransform; //apply transform.
self.lastTouch = currentPoint;
self.lastAngle = newAngle;
}
The second option makes more sense to me, but it is not giving very pleasing results (jaggy updates and non-smooth rotations). Which way is best (if any), in terms of performance?
Cheers!
It is actually much simpler than what you have tried.
You need three data points:
The origin of your view.
The location of the current touch
The location of the previous touch
The Touch object passed to you actually contains the last touch location. So you don't need to keep track of it.
All you have to do is calculate the angle between two lines:
Origin to Current Touch
Origin to Previous Touch
Then convert that to radians and use that in your CGAffineTransformRotate(). Do that all in your touchesMoved handler.
Here is a function to calculate what you need just that:
static inline CGFloat angleBetweenLinesInRadians(CGPoint line1Start, CGPoint line1End, CGPoint line2Start, CGPoint line2End) {
CGFloat a = line1End.x - line1Start.x;
CGFloat b = line1End.y - line1Start.y;
CGFloat c = line2End.x - line2Start.x;
CGFloat d = line2End.y - line2Start.y;
CGFloat line1Slope = (line1End.y - line1Start.y) / (line1End.x - line1Start.x);
CGFloat line2Slope = (line2End.y - line2Start.y) / (line2End.x - line2Start.x);
CGFloat degs = acosf(((a*c) + (b*d)) / ((sqrt(a*a + b*b)) * (sqrt(c*c + d*d))));
return (line2Slope > line1Slope) ? degs : -degs;
}
Courtesy of Jeff LaMarche at:
http://iphonedevelopment.blogspot.com/2009/12/better-two-finger-rotate-gesture.html
Example:
UITouch *touch = [touches anyObject];
CGPoint origin = [view center];
CGFloat angle = angleBetweenLinesInRadians(origin, [touch previousLocationInView:self.superview.superview], origin, [touch locationInView:self.superview.superview]);
Have you considered using UIRotationGestureRecognizer? Seems like that has the logic already baked in, and might make things simpler.

iPhone/Cocoa Animation having effect I don't understand

I have created a basic unlock slider like you use to unlock the iPhone.
In my touchesEnded function, I use an animation to move the slider back to home.
The first time the user moves the slider (and doesn't actually hit the end to unlock), the animation works fine and the slider moves back to home.
However, the next time the user tries to move the slider (and touchesMoved gets called), the slider gets pushed offscreen (to the left) by the amount they had slid the slider before.
So, what's happening?
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
self.dragging = NO;
self.startX = 0;
[UIView beginAnimations:nil context:nil];
[UIView setAnimationDuration:0.5];
self.unlock.frame = CGRectMake(0, 0, 75, 35);
[UIView commitAnimations];
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
if (!self.dragging ) { return; }
UITouch *touch = [[event allTouches] anyObject];
CGPoint point = [touch locationInView:self];
int offset = point.x - self.startX;
NSLog(#"%i", offset);
if (offset > (LENGTH-SIZE)) {
offset = (LENGTH-SIZE);
}
CGAffineTransform myTransform = CGAffineTransformMakeTranslation(offset, 0.0);
[self.unlock setTransform:myTransform];
if (point.x >= (LENGTH-(SIZE-self.startX))) {
NSLog(#"Slider reached end");
[self.sliderView removeFromSuperview];
}
}
EDIT
Here's the working function, cleaned up a bit, though it could be better (thanks to a couple answered questions on SO from one hacker). I'm going to blog the complete module since there doesn't seem to be a standard widget:
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
if (!self.dragging ) { return; }
UITouch *touch = [[event allTouches] anyObject];
CGPoint point = [touch locationInView:self];
if (point.x >= (LENGTH-(SIZE-self.startX))) {
NSLog(#"Slider reached end");
[self.sliderView removeFromSuperview];
}
// offset based on where the user clicked the slider
int offset = point.x - self.startX;
if (offset > (LENGTH-SIZE)) {
offset = (LENGTH-SIZE);
}
//move the slider
CGRect oldFrame = self.unlock.frame;
CGRect newFrame = CGRectMake(self.frame.origin.x+offset, self.frame.origin.y-5, oldFrame.size.width, oldFrame.size.height);
self.unlock.frame = newFrame;
}
IN your previous question I didn't realize you were moving the slider by changing its transform. Mixing up motion by changing its frame and transform is going to be moderately complicated.
Maybe you could try doing something more like this:
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
if (!self.dragging ) { return; }
UITouch *touch = [[event allTouches] anyObject];
CGPoint point = [touch locationInView:self];
int offset = point.x - self.startX;
NSLog(#"%i", offset);
if (offset > (LENGTH-SIZE)) {
offset = (LENGTH-SIZE);
}
CGRect oldFrame = self.frame;
CGRect newFrame = CGRectMake(oldFrame.origin.x+offset, oldFrame.origin.y, oldFrame.size.width, oldFrame.size.height);
self.frame = newFrame;
if (point.x >= (LENGTH-(SIZE-self.startX))) {
NSLog(#"Slider reached end");
[self.sliderView removeFromSuperview];
}
}
That moves the slider by modifying its frame, not its translation, which should cause it to interact better with the snap back animation. Alternatively, rather than resetting the frame in the implicit animator you could reset the translation there.
You need to reset the view transform in your touchesEnded handler:
[self.unlock setTransform:CGAffineTransformIdentity];
Try that instead of animating the frame property.