I am trying to implement a wheel type functionality. I calculate the center point of the view and find the angle created on moving in touchesMoved: method. For the first move it rotated by some degree. But for next move it comes back to the original position of the view and then rotating.Actually i want the rotation from the end point of the previous rotation.
Any thing missing?Any help
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
float a, b,c;
UITouch *aTouch = [touches anyObject];
if ([aTouch view] == dialView) {
CGPoint loc = [aTouch locationInView:dialView];
CGPoint prevloc = [aTouch previousLocationInView:dialView];
c = [self GetSides:prevloc point2:loc];
a = [self GetSides:loc point2:cntrePoint];
b = [self GetSides:prevloc point2:cntrePoint];
float angle = acos((a*a+b*b-c*c)/(2*a*b));// Calculating angle created on moving
CABasicAnimation *rotate ;
rotate = [CABasicAnimation animationWithKeyPath:#"transform.rotation"];
rotate.fromValue = [NSNumber numberWithFloat:0];
rotate.toValue = [NSNumber numberWithFloat:((angle*M_PI)/180)];
rotate.autoreverses = NO;
rotate.fillMode = kCAFillModeForwards;
rotate.duration = 1;
rotate.repeatCount = 1;
rotate.removedOnCompletion = NO;
rotate.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
[dialView.layer addAnimation:rotate forKey:#"360"];
}
}
The animation you are performing, which looks to be drawn from this answer, has both the to and from values specified, so it will simply rotate from 0 to angle radians every time. That means that your image will jump back to the start on every animation.
Normally, if you were to remove the fromValue line, your animation should go from the current angle to your new angle, but it looks like the transform property behaves a little differently. You'll need to set the fromValue to be the current value of the rotational transform extracted from your layer's presentationLayer. The following code should rotate to the target angle from your current angle:
CABasicAnimation *rotate;
rotate = [CABasicAnimation animationWithKeyPath:#"transform.rotation.z"];
rotate.fromValue = [[dialView.layer presentationLayer] valueForKeyPath:#"transform.rotation.z"];
rotate.toValue = [NSNumber numberWithFloat:angle];
rotate.fillMode = kCAFillModeForwards;
rotate.duration = 1;
rotate.removedOnCompletion = NO;
rotate.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
Note that I've removed the degrees-to-radians conversion in the toValue, because your acos() calculation returns values in radians.
EDIT (10/23/2009): Corrected my earlier assumption about the functioning of fromValue with the transform property.
Related
I made one demo project (from Moving-MKAnnotationView demo on github) for moving car on map following is its link
https://github.com/pratikbhiyani/Moving-MKAnnotationView
I edit my code on the basis of given answer by vinaut but still problem is that when we zoom or scroll the map animation get distract in ios 7 and in ios 6 when we zoom or scroll the map annotation set to its original angle for a while.
Below is a screen shot of my Demo Project
Here is some code i change
- (void) setPosition : (id) posValue;
{
NSLog(#"set position");
//extract the mapPoint from this dummy (wrapper) CGPoint struct
MKMapPoint mapPoint = *(MKMapPoint*)[(NSValue*)posValue pointerValue];
CLLocationCoordinate2D coord = MKCoordinateForMapPoint(mapPoint);
CGPoint toPos;
if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 7) {
toPos = [self.mapView convertCoordinate:coord toPointToView:self.mapView];
}
else
{
CGFloat zoomFactor = self.mapView.visibleMapRect.size.width / self.mapView.bounds.size.width;
toPos.x = mapPoint.x/zoomFactor;
toPos.y = mapPoint.y/zoomFactor;
}
[self setTransform:CGAffineTransformMakeRotation([self getHeadingForDirectionFromCoordinate:MKCoordinateForMapPoint(previousPoint) toCoordinate: MKCoordinateForMapPoint(mapPoint)])];
if (MKMapRectContainsPoint(self.mapView.visibleMapRect, mapPoint)) {
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:#"position"];
animation.fromValue = [NSValue valueWithCGPoint:self.center];
animation.toValue = [NSValue valueWithCGPoint:toPos];
animation.duration = 1.0;
animation.delegate = self;
animation.fillMode = kCAFillModeForwards;
//[self.layer removeAllAnimations];
[self.layer addAnimation:animation forKey:POSITIONKEY];
//NSLog(#"setPosition ANIMATED %x from (%f, %f) to (%f, %f)", self, self.center.x, self.center.y, toPos.x, toPos.y);
}
self.center = toPos;
previousPoint = mapPoint;
}
My goal is to move car same like in uber app.
It seems that something changed with the conversion functions for CLCoordinate2D/MKMapPoint/CGPoint...
Detecting a point in a MKPolygon broke with iOS7 (CGPathContainsPoint)
The annotation disappears because the conversion beetween MkMapPoints and CGIPoints does not work anymore, if you log the "position" of the CALayer you will get points way outside the view. No idea why it works when doing touch events.
If you change the function to :
- (void) setPosition : (id) posValue;
{
//extract the mapPoint from this dummy (wrapper) CGPoint struct
MKMapPoint mapPoint = *(MKMapPoint*)[(NSValue*)posValue pointerValue];
CLLocationCoordinate2D coord = MKCoordinateForMapPoint(mapPoint);
CGPoint toPos = [self.mapView convertCoordinate:coord toPointToView:self.mapView];
if (MKMapRectContainsPoint(self.mapView.visibleMapRect, mapPoint)) {
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:#"position"];
animation.fromValue = [NSValue valueWithCGPoint:self.center];
animation.toValue = [NSValue valueWithCGPoint:toPos];
animation.duration = 0.8;
animation.delegate = self;
animation.fillMode = kCAFillModeForwards;
//[self.layer removeAllAnimations];
[self.layer addAnimation:animation forKey:POSITIONKEY];
//NSLog(#"setPosition ANIMATED %x from (%f, %f) to (%f, %f)", self, self.center.x, self.center.y, toPos.x, toPos.y);
}
self.center = toPos;
}
It should be working again.
I'm the original contributor to Moving-MKAnnotationView (https://github.com/100grams/Moving-MKAnnotationView.git). This component was originally written using iOS4.3 and a lot has changed since. :-)
The root cause here was the conversion from MKMapPoint to CGPoint (screen coordinates). Although the code worked before, it breaks on iOS7 and I fixed it by using this to convert the lat/lon coordinate to screen coordinates:
convertCoordinate:toPointToView:
I've committed this fix, with a few other updates, to https://github.com/100grams/Moving-MKAnnotationView.git so it now works on iOS7/Xcode5.
The problem of distracting a car while zoom/scrolling the map. Actually it can not be achieved by adding an animation to annotation. I have found out the Interpolate function through which I can get the locations between "From" and "To" coordinates and set it to the annotation(setting annotation coordinate in milliseconds) will look like an animation.
It is not an issue of iOS or Map, if you are adding animation to annotation it will add to annotation layer not respect to the map point.
I'm rotating an arrow by 45 degrees. When an animation ends, the arrow returns to its original state (0 degrees). I need that the arrow doesn't return to original state and it will be rotate by 45 degrees at the end of animation.
CABasicAnimation *fullRotationShort = [CABasicAnimation animationWithKeyPath:#"transform.rotation"];
fullRotationShort.fromValue = [NSNumber numberWithFloat:0];
fullRotationShort.toValue = [NSNumber numberWithFloat:M_PI/28*4.7];
fullRotationShort.duration = 1.0f;
fullRotationShort.repeatCount = 1;
[imgViewArrowShort.layer addAnimation:fullRotationShort forKey:nil];
Assuming your arrow is a UIImageView named arrow.
You really don't need such complicated CAAnimations for this.
[UIView animateWithDuration:1.0 animations:^{
arrow.transform = CGAffineTransformMakeRotation(0.25 * M_PI);
}];
Set the property:
fullRotationShort.fillMode = kCAFillModeForwards;
fullRotationShort.cumulative = YES;
fullRotationShort.removedOnCompletion=NO;
i Have a UIImageview object moving along a path and i have use CABasic animation and BezierCure to move the object along the path, Now the problem is if i touch the moving object the touch event is not recognized by the UIImageView but the touch event is received by main view. can anybody help me to solve this problem?
Below is my code
UITouch *touch = [[event allTouches] anyObject];
if (![touch.view isEqual: beaconPose1]) {
return;
}
[touch.view.layer removeAllAnimations];
CGPoint location = [touch locationInView: self.view];
CGRect frame = touch.view.frame;
frame.origin.x = location.x;
frame.origin.y = location.y;
///frame.origin.x += xDisplacement;
//frame.origin.y += yDisplacement;
touch.view.frame = frame;
-- Code where User Interaction is enabled
[beaconPose1 setUserInteractionEnabled:true];
CAKeyframeAnimation *pathAnimation = [CAKeyframeAnimation animationWithKeyPath:#"position"];
pathAnimation.duration = 1.0f;
pathAnimation.path = beaconPose3Path.CGPath;
pathAnimation.calculationMode = kCAAnimationLinear;
CABasicAnimation *fullRotation = [CABasicAnimation animationWithKeyPath:#"transform.rotation"];
fullRotation.fromValue = [NSNumber numberWithFloat:0];
fullRotation.toValue = [NSNumber numberWithFloat:((360*M_PI)/180)];
fullRotation.duration = pathAnimation.duration;
fullRotation.repeatCount = pathAnimation.duration/fullRotation.duration;
[beaconPose1.layer addAnimation:fullRotation forKey:#"360"];
[beaconPose1.layer addAnimation:pathAnimation forKey:#"movingAnimation"];
I figured out how to do this,
The object under CAKeyframeAnimation or CABasicAnimation will not respond to touch event, instead what we can do is calculate if the location of the touch is withing the area of the object using Pythagorus theorem.
I'm afraid the currently accepted answer is wrong:
When the user puts his finger on the screen, all the views are hit-tested with the touch position. However, when hit-testing them, the view's frame is requested. This is, however, linked to the modelLayers frame, which is the end point of your animation.
What you would want is hit-testing the presentationLayer, but I guess this is not possible.
I'm having difficulties adding momentum to my spinning wheel.
I have this wheel (something like this) and I'm rotating it around it's center by using a single touch event.
No problems here, but when the touch (aka drag) ends; I want the wheel to keep it's momentum and ease out it's movement.
Anyone who can give me some pointers, it doesn't necessarily have to be in objective-c.
AS3, javascript or JAVA will also be sufficient.
* UPDATE (code for rotating the wheel) *
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
rotation = _startAngle = atan2(384 - touchPoint.y, 512 - touchPoint.x);
};
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
CGPoint touchPoint = [[touches anyObject] locationInView:self.view];
rotation = atan2(384 - touchPoint.y, 512 - touchPoint.x);
rotation = fmod(rotation - _startAngle, M_PI * 2.0f);
[wheel setTransform:CGAffineTransformMakeRotation(_circleRotationOffset + rotation)];
};
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
_circleRotationOffset = fmod(_circleRotationOffset + rotation, M_PI * 2);
};
You want the momentum to reduce due to friction; friction is a force that's a function of velocity. So technically you've got a differential equation going on. That's not really worth investing too much thought into though, because the solution is probably more easily reached by hand waving.
So: store current angle and current angular velocity. n times a second (probably via an NSTimer or a CADisplayLink) add the angular velocity to the angle, then multiply the angular velocity by something to make it smaller — such as 0.995. Constants closer to 1.0 will make it take longer to slow down; if you go above 1.0 it'll obviously accelerate. This is effectively a form of Euler integration but, again, it's not worth worrying about.
It's possibly also worth putting a minimum cap on angular velocity so that if it drops below, say 0.01 radians/second then you snap it down to 0. That effectively modifies your model of friction slightly to jump from kinetic to static friction at an opportune moment, and acts as a floating point precision buffer zone.
To get initial velocity from a drag you can just work out the vector from the centre of the wheel to the start of the drag, rotate that vector by 90 degrees, do a dot product with that and the drag vector and scale according to distance from the centre.
If you are spinning the UIImageView with code like:
[UIView beginAnimations:#"rotateImage" context:nil];
[UIView setAnimationDuration:4.0];
wheelImageView.transform = CGAffineTransformMakeRotation(3.14159265*5);
[UIView commitAnimations];
You could use [UIView setAnimationCurve:UIViewAnimationCurveEaseOut];
This makes it so the animation will start off fast and start to slow down over time.
Click Here for More info on UIViewAnimationCurve
I managed to get some nice results.
How I did it, should be tweaked more, but this is the basics:
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
CGPoint touchPoint = [[touches anyObject] locationInView:self.view];
rotation = _startAngle = atan2(384 - touchPoint.y, 512 - touchPoint.x);
[_history removeAllObjects];
};
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
CGPoint touchPoint = [[touches anyObject] locationInView:self.view];
[_history insertObject:[NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithDouble:CFAbsoluteTimeGetCurrent()], #"time", [NSValue valueWithCGPoint:touchPoint], #"point", [NSNumber numberWithFloat: _circleRotationOffset + rotation], #"rotation", nil] atIndex:0];
if ([_history count] == 3) {
[_history removeLastObject];
}
rotation = atan2(384 - touchPoint.y, 512 - touchPoint.x) - _startAngle;
[circleImage setTransform:CGAffineTransformMakeRotation(_circleRotationOffset + rotation)];
};
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
CGPoint tp = [[touches anyObject] locationInView:self.view];
_circleRotationOffset += rotation;
NSDictionary *lo = [_history lastObject];
CGPoint pp = [[lo objectForKey:#"point"] CGPointValue];
double timeDif = CFAbsoluteTimeGetCurrent() - [[lo objectForKey:#"time"] doubleValue];
float lastRotation = [[lo objectForKey:#"rotation"] floatValue];
// Calculate strength
float dist = sqrtf(((pp.x - tp.x) * (pp.x - tp.x)) + ((pp.y - tp.y) * (pp.y - tp.y)));
float strength = MIN(1.0f, dist / 80.0f) * (timeDif / .025) * M_PI;
float p = _circleRotationOffset;
float dif = _circleRotationOffset - lastRotation;
BOOL inc = dif > 0;
if (dif > 3 || dif < -3) { // Some correction
inc = !inc;
}
if (inc) {
_circleRotationOffset += strength;
} else {
_circleRotationOffset -= strength;
}
[circleImage.layer removeAllAnimations];
CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:#"transform.rotation.z"];
animation.duration = MAX(strength / 2.5, 1.0f);
animation.cumulative = YES;
animation.repeatCount = 1;
animation.values = [NSArray arrayWithObjects:
[NSNumber numberWithFloat:p],
[NSNumber numberWithFloat: _circleRotationOffset], nil];
animation.keyTimes = [NSArray arrayWithObjects:
[NSNumber numberWithFloat:0],
[NSNumber numberWithFloat:1.0], nil];
animation.timingFunctions = [NSArray arrayWithObjects:
[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut], nil];
animation.removedOnCompletion = YES;
animation.delegate = self;
animation.fillMode = kCAFillModeForwards;
[circleImage.layer addAnimation:animation forKey:#"rotate"];
};
I have an arrow that spins when the user clicks a button using CAKeyframeAnimation.
The position the arrow points to dictates what image is seen by the user.
This works fine.
The final position of the rotation is selected randomly and sets up the next image for the user when they click the button.
-(void)rotateAnimation
{
[self setDialStartPosition];
[self setDialEndPosition];
//Spin the Arrow
CALayer *layer = imageView.layer;
CAKeyframeAnimation *animation;
animation = [CAKeyframeAnimation animationWithKeyPath:#"transform.rotation.z"];
animation.duration = 8;
animation.cumulative = NO;
animation.repeatCount = 0;
animation.values = [NSArray arrayWithObjects: // i.e., Rotation values for the 3 keyframes, in RADIANS
[NSNumber numberWithFloat:[self dialStartPosition] * M_PI],
[NSNumber numberWithFloat:2.5 * M_PI],
[NSNumber numberWithFloat:[self dialEndPosition] * M_PI], nil];
animation.keyTimes = [NSArray arrayWithObjects: // Relative timing values for the 3 keyframes
[NSNumber numberWithFloat:0],
[NSNumber numberWithFloat:.055],
[NSNumber numberWithFloat:1], nil];
animation.timingFunctions = [NSArray arrayWithObjects:
[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn],
// from keyframe 1 to keyframe 2
[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut], nil]; // from keyframe 2 to keyframe 3
animation.removedOnCompletion = NO;
animation.fillMode = kCAFillModeForwards;
[layer addAnimation:animation forKey:nil];
....
I have this working, but I want the user to be able to move the arrow manually using a touch and have the arrow point in the direction of their finger as they move it around.
When they lift their finger and hit the button, i want the position of the Arrow to dictate what image is then seen by the user. So touchesEnded would have to update the dialStartPosition.
Here is my touches code..
double wrapd(double _val, double _min, double _max)
{
if(_val < _min) return _max - (_min - _val);
if(_val > _max) return _min - (_max - _val);
return _val;
}
- (void) touchesBegan:(NSSet *)_touches withEvent:(UIEvent *)_event
{
UITouch* touch = [_touches anyObject];
CGPoint location = [touch locationInView:self.view];
m_locationBegan = location;
}
- (void) touchesMoved:(NSSet *)_touches withEvent:(UIEvent *)_event
{
UITouch* touch = [_touches anyObject];
CGPoint location = [touch locationInView:self.view];
[self updateRotation:location];
}
- (void) touchesEnded:(NSSet *)_touches withEvent:(UIEvent *)_event
{
UITouch* touch = [_touches anyObject];
CGPoint location = [touch locationInView:self.view];
m_currentAngle = [self updateRotation:location];
}
- (float) updateRotation:(CGPoint)_location
{
float fromAngle = atan2(m_locationBegan.y-imageView.center.y, m_locationBegan.x-imageView.center.x);
float toAngle = atan2(_location.y-imageView.center.y, _location.x-imageView.center.x);
float newAngle = wrapd(m_currentAngle + (toAngle - fromAngle), 0, 2*3.14);
CGAffineTransform cgaRotate = CGAffineTransformMakeRotation(newAngle);
imageView.transform = cgaRotate;
return newAngle;
}
Heres whats happing now..
Before I hit the button I can move the arrow around with a touch the way I want.
When I hit the button it doesn't respond to where I have set the position manually with my touch.
Once the CAKeyframeAnimation plays it no longer will respond to any touches unless I change
animation.fillMode = kCAFillModeForwards;
to
animation.fillMode = kCAFillModeRemoved;
but if I do this it removes the animation after it runs and shows the arrow in the spot I left it when I set it manually.
I'm quite new a programming so any help would be appreciated
Thanks