I've created an animation of a plane moving along a bezier path. Is there a way to start the animation when a user swipes a certain direction? (i.e if the user pans right, the plane moves right along the path. if the user pans left, the plane moves left along the path).
Thanks for the help.
Curve Setup:
self.trackPath = [UIBezierPath bezierPath];
self.trackPath = [UIBezierPath bezierPathWithArcCenter:P(self.scrollView.center.x,self.scrollView.center.y-20)
radius:110
startAngle:DEGREES_TO_RADIANS(70)
endAngle:DEGREES_TO_RADIANS(115)
clockwise:NO];
self.plane = [CALayer layer];
self.plane.bounds = CGRectMake(0, 0, 60.0, 60.0);
self.plane.position = CGPointMake(self.scrollView.center.x,self.scrollView.center.y-20);
self.plane.contents = (id)([UIImage imageNamed:#"profile_tag"].CGImage);
[self.view.layer addSublayer:self.plane];
CAKeyframeAnimation *anim = [CAKeyframeAnimation animationWithKeyPath:#"position"];
anim.path = self.trackPath.CGPath;
anim.duration = 320.;
[self.plane addAnimation:anim forKey:nil];
self.plane.speed = 0.0;
self.plane.hidden = YES;
When Paging:
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
CGFloat pageWidth = self.scrollView.frame.size.width;
int page = floor((self.scrollView.contentOffset.x - pageWidth / 2) / pageWidth) + 1;
self.pageControl.currentPage = page;
if((scrollView.contentOffset.x >= 320) && (scrollView.contentOffset.x<=640)){
self.plane.hidden = NO;
self.plane.timeOffset = scrollView.contentOffset.x - 320;
}
}
Related
I am have one view and I am rotating that view using CABasicAnimation.
Now my problem is that how I get a perfect position of that view while rotating. I have tried many type of codes but i can't got a perfect position during rotation of that view.
CABasicAnimation *rotationAnimation = [CABasicAnimation animationWithKeyPath:#"transform.rotation.z"];
NSNumber *currentAngle = [CircleView.layer.presentationLayer valueForKeyPath:#"transform.rotation"];
rotationAnimation.fromValue = currentAngle;
rotationAnimation.toValue = #(50*M_PI);
rotationAnimation.duration = 50.0f; // this might be too fast
rotationAnimation.repeatCount = HUGE_VALF; // HUGE_VALF is defined in math.h so import it
[CircleView.layer addAnimation:rotationAnimation forKey:#"rotationAnimationleft"];
I am using this code for rotating my view.
I have also attached a one photo of my view.
Thank you In Advance Please Help If You Know.
To get view's parameters during the animation you should use view.layer.presentationLayer
ADDED:
In order to get the coordinate of top left corner of the view, use the following code:
- (CGPoint)currentTopLeftPointOfTheView:(UIView *)view
{
CGRect rect = view.bounds;
rect.origin.x = view.center.x - 0.5 * CGRectGetWidth(rect);
rect.origin.y = view.center.y - 0.5 * CGRectGetHeight(rect);
CGPoint originalTopLeftCorner = CGPointMake(CGRectGetMinX(rect), CGRectGetMinY(rect));
CGPoint rectCenter = CGPointMake(CGRectGetMidX(rect), CGRectGetMidY(rect));
CGFloat radius = sqrt(pow(rectCenter.x - originalTopLeftCorner.x, 2.0) + pow(rectCenter.y - originalTopLeftCorner.y, 2.0));
CGFloat originalAngle = M_PI - acos((rectCenter.x - originalTopLeftCorner.x) / radius);
CATransform3D currentTransform = ((CALayer *)view.layer.presentationLayer).transform;
CGFloat rotation = atan2(currentTransform.m12, currentTransform.m11);
CGFloat resultAngle = originalAngle - rotation;
CGPoint currentTopLeftCorner = CGPointMake(round(rectCenter.x + cos(resultAngle) * radius), round(rectCenter.y - sin(resultAngle) * radius));
return currentTopLeftCorner;
}
Resulting CGPoint will be the coordinate of the top left corner of your (rotated) view relative to its superview.
Set the position of the layer that you what
[layer setPosition:endpoint];
Or you may also refer this CABasicAnimation rotate returns to original position
I am trying to achieve an animation effect which will simulate a very simple representation of a wave effect by using bezierpath and keyframeanimation.
However, with key frame animation I cant get the effect properly, the animation is working from start point to end instead of making wave curves up and down.
This could be a better representation of what is happening.
Bezier Curve Wave Animation
and this is the code for the animation:
CGPoint rightCurve;
rightCurve.x = center.x + radius * cos(startAngle);
rightCurve.y = center.y + radius * sin(startAngle);
CGPoint leftCurve;
leftCurve.x = center.x + radius * cos(endAngle);
leftCurve.y = center.y + radius * sin(endAngle);
CGPoint centerCurve;
centerCurve.x = center.x;
centerCurve.y = leftCurve.y;
CGPoint controlLeftUp = CGPointMake((leftCurve.x + centerCurve.x) /2, ((centerCurve.y + ((centerCurve.x - rightCurve.x)/2)*1.3)));
CGPoint controlLeftDown = CGPointMake((leftCurve.x + centerCurve.x) /2, ((centerCurve.y - ((centerCurve.x - rightCurve.x)/2)*1.3)));
CGPoint controlRightDown = CGPointMake((rightCurve.x + centerCurve.x) /2, ((centerCurve.y - ((centerCurve.x - rightCurve.x)/2)*1.3)));
CGPoint controlRightUp = CGPointMake((rightCurve.x + centerCurve.x) /2, ((centerCurve.y + ((centerCurve.x - rightCurve.x)/2)*1.3)));
UIBezierPath* path0 = [UIBezierPath bezierPath];
[path0 moveToPoint:CGPointMake(leftCurve.x, leftCurve.y)];
[path0 addQuadCurveToPoint:CGPointMake(centerCurve.x, centerCurve.y) controlPoint:controlLeftUp];
[path0 moveToPoint:CGPointMake(centerCurve.x , centerCurve.y)];
[path0 addQuadCurveToPoint:CGPointMake(rightCurve.x, rightCurve.y) controlPoint:controlRightDown];
UIBezierPath* path1 = [UIBezierPath bezierPath];
[path1 moveToPoint:CGPointMake(rightCurve.x,rightCurve.y)];
[path1 addQuadCurveToPoint:CGPointMake(centerCurve.x, centerCurve.y) controlPoint:controlRightUp];
[path1 moveToPoint:CGPointMake(centerCurve.x , centerCurve.y)];
[path1 addQuadCurveToPoint:CGPointMake(leftCurve.x, leftCurve.y) controlPoint:controlLeftDown];
CAKeyframeAnimation* animation = [CAKeyframeAnimation animationWithKeyPath:#"path"];
animation.duration = _animationInterval;
animation.values = [NSArray arrayWithObjects:(id)path0.CGPath, (id)path1.CGPath, nil];
animation.repeatCount = HUGE_VALF;
[shapeLayer addAnimation:animation forKey:nil];
It will be amazing if someone tell me how to edit the animation or the curve.
Thanks in advance.
I've never worked with animations on iOS specifically and was hoping to be pointed in the right direction. I have so far animated a bunch of pill shaped objects in a fashion similar to what I have below, however I have yet to implement the correct masking based on the y positioning of the shape.
The pill shapes are just UIViews. I'm looking for a way to get their backgrounds to change based on their positioning; the background itself does not move, just the pills.
EDIT:
Based on Rob's answer below, I am now tying the animation to the device's movement like so:
- (void)setAnimationToDeviceMotion {
motionManager = [[CMMotionManager alloc] init];
motionManager.deviceMotionUpdateInterval = 0.1f;
NSOperationQueue *motionQueue = [[NSOperationQueue alloc] init];
[motionManager startDeviceMotionUpdatesUsingReferenceFrame:CMAttitudeReferenceFrameXArbitraryZVertical toQueue:motionQueue withHandler:^(CMDeviceMotion *motion, NSError *error) {
if (!error) {
double pitch = motion.attitude.pitch;
NSLog(#"%f", pitch);
[self addMaskToView:self.imageView withAdjustment:pitch];
}
}];
}
Assuming this is eight pill shapes that will reveal a single image underneath, you
create a UIBezierPath for the eight pill shapes
create a CAShapeLayer layer that uses that UIBezierPath (or more accurately, its CGPath)
apply this CAShapeLayer as the mask for the layer for your UIImageView
If you want to animate this over time, just replace the mask (e.g. in response to a accelerometer event, a CADisplayLink or NSTimer, or a UIPanGesture), or just update the path of the CAShapeLayer you have applied as the mask to the UIImageView's layer.
Thus, you might have something like:
- (void)addMaskToView:(UIView *)view withAdjustment:(CGFloat)adjustment
{
CAShapeLayer *mask = [CAShapeLayer layer];
mask.path = [[self pillsPathWithAdjustment:adjustment forView:view] CGPath];
self.imageView.layer.mask = mask;
}
- (UIBezierPath *)pillsPathWithAdjustment:(CGFloat)adjustment forView:(UIView *)view
{
UIBezierPath *path = [UIBezierPath bezierPath];
for (NSInteger i = 0; i < kNumberOfPills; i++)
[self addPillPathNumber:i forView:view toPath:path adjustment:adjustment];
return path;
}
- (void)addPillPathNumber:(NSInteger)index forView:(UIView *)view toPath:(UIBezierPath *)path adjustment:(CGFloat)adjustment
{
CGFloat const pillSpacing = 5.0f;
CGFloat const pillWidth = view.bounds.size.width / kNumberOfPills - pillSpacing;
CGFloat const pillHeight = view.bounds.size.height * 0.4;
CGFloat const cornerRounding = 10.0f;
CGPoint point = CGPointMake(index * (pillWidth + pillSpacing) + pillSpacing / 2.0, sinf((float) index * M_PI / kNumberOfPills + adjustment * M_PI * 2.0) * 100.0);
// top
point.x += cornerRounding;
[path moveToPoint:point];
point.x += pillWidth - cornerRounding * 2.0;
[path addLineToPoint:point];
// top right corner
[path addArcWithCenter:CGPointMake(point.x, point.y + cornerRounding) radius:cornerRounding startAngle:3 * M_PI_2 endAngle:2.0 * M_PI clockwise:YES];
point.x += cornerRounding;
point.y += cornerRounding;
// right
point.y += pillHeight - cornerRounding * 2.0;
[path addLineToPoint:point];
// lower right corner
[path addArcWithCenter:CGPointMake(point.x - cornerRounding, point.y) radius:cornerRounding startAngle:0 endAngle:M_PI_2 clockwise:YES];
// bottom
point.y += cornerRounding;
point.x -= cornerRounding;
point.x -= pillWidth - cornerRounding * 2.0;
[path addLineToPoint:point];
// lower left corner
[path addArcWithCenter:CGPointMake(point.x, point.y - cornerRounding) radius:cornerRounding startAngle:M_PI_2 endAngle:M_PI clockwise:YES];
// left
point.y -= cornerRounding;
point.x -= cornerRounding;
point.y -= pillHeight - cornerRounding * 2.0;
[path addLineToPoint:point];
// upper left corner
[path addArcWithCenter:CGPointMake(point.x + cornerRounding, point.y) radius:cornerRounding startAngle:M_PI endAngle:3.0 * M_PI_2 clockwise:YES];
[path closePath];
}
By way of illustration, this is how I'd animate this using a CADisplayLink:
- (void)startDisplayLink
{
self.displayLink = [CADisplayLink displayLinkWithTarget:self selector:#selector(handleDisplayLink:)];
self.startTime = CACurrentMediaTime();
[self.displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
}
- (void)stopDisplayLink
{
[self.displayLink invalidate];
self.displayLink = nil;
}
- (void)handleDisplayLink:(CADisplayLink *)displayLink
{
CGFloat seconds;
CGFloat fractionsOfSecond = modff(CACurrentMediaTime() - self.startTime, &seconds);
[self addMaskToView:self.imageView withAdjustment:fractionsOfSecond];
}
To do masking, you'll need to work with the view's layers. Instead of moving the views themselves up and down, create views that occupy the whole height of the parent, and then create a CALayer in the pill shape. Set the pill layer as the view's layer's mask, and move it up and down.
Something like this:
UIImageView *myImageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"redAndBlueBackground"]];
CALayer *pillLayer = [[CALayer alloc] initWithFrame:CGRectMake(0, 0, myImageView.bounds.size.width, myImageView.bounds.size.height / 2];
pillLayer.cornerRadius = 8;
myImageView.layer.maskLayer = pillLayer;
// move the pill layer to height y:
pillLayer.center = CGPointMake(myImageView.bounds.size.width / 2,
(myImageView.bounds.size.height / 2) + y);
I am trying to allow the user to change the speed of my animation. I am making a CAKeyframeAnimation with a bezier path, and I can get it to display and run correctly.
I try to change the speed by creating a new animation path with a different duration. The planes go back to the beginning (which I haven't tried to fix yet) and do speed up. The path that they are being drawn on goes away at the time it would have had the animation never changed speeds. When the planes finish, another appears at the point at which the animation was paused in the first place. I have no idea what I'm doing wrong. My question is similar to this one modifying dynamically the duration of CAKeyframeAnimation, but I don't understand what the OP said about finally using blocks.
//The first two methods are in a class subclassing UIView
/** Pause each plane's animation */
- (void)pauseAnimation
{
CFTimeInterval pausedTime = [[self layer] convertTime:CACurrentMediaTime() fromLayer:nil];
[self layer].speed = 0.0;
[self layer].timeOffset = pausedTime;
}
/** Resume each plane's animation */
- (void)resumeAnimation
{
CFTimeInterval pausedTime = [[self layer] timeOffset];
[self layer].speed = 1.0;
[self layer].timeOffset = 0.0;
CFTimeInterval timeSincePause = [[self layer] convertTime:CACurrentMediaTime() fromLayer:nil] - pausedTime;
for(SEPlane *plane in planes){
plane.planeAnimationPath.speedMultiplier = 5;
[plane.planeAnimationPath beginAnimation:self];
}
//[self layer].beginTime = timeSincePause;
}
//This method is in the class of planeAnimationPath
/** Begin animating plane along given path */
- (void)beginAnimation:(UIView *) view
{
planeAnimation = nil;
// Create animation layer for animating plane
planeAnimation = [CAKeyframeAnimation animationWithKeyPath:#"position"];
planeAnimation.path = [bezierPath CGPath];
planeAnimation.duration = approximateLength/(ANIMATION_SPEED * self.speedMultiplier);
planeAnimation.calculationMode = kCAAnimationPaced;
planeAnimation.fillMode = kCAFillModeForwards;
planeAnimation.rotationMode = kCAAnimationRotateAuto;
planeAnimation.removedOnCompletion = YES;
[planeAnimation setDelegate:self];
// Add animation to image-layer
[imageLayer addAnimation:planeAnimation forKey:animationKey];
// Add image-layer to view
[[view layer] addSublayer:imageLayer];
}
Unlike default animations, which animate from the current position to the target position, CAKeyframeAnimations don't (as far as I can tell). Besides how would you interpret an animation where the current position is not on the path?
The simplest option that I can think of is to do the following in the setter of speedMultiplier:
Create a new animation with the desired path.
Set the duration as if speedMultiplier was 1
Set the speed to speedMultiplier
Set the timeOffset to duration * "percent of new animation already complete"
Add the animation to the layer.
As you might have guessed, the tricky part is step 4. For simple paths this is easy but for arbitrary paths, it will get a bit more complicated. As a starting point, you will need the formula for bezier quadratic and cubic curves. Search for "distance parametrization of bezier curves" and you will find tons of stuff.
Here is a code sample for a simple rectangular path. The window simply has a MPView and a slider:
#implementation MPView {
IBOutlet NSSlider *_slider; // Min=0.0, Max=5.0
CALayer *_hostLayer;
CALayer *_ballLayer;
CAKeyframeAnimation *_ballPositionAnimation;
double _speed;
}
- (void) awakeFromNib
{
CGRect bounds = self.bounds;
[CATransaction begin];
[CATransaction setDisableActions:YES];
_speed = 1.0;
_hostLayer = [CALayer layer];
_hostLayer.backgroundColor = CGColorGetConstantColor(kCGColorBlack);
self.layer = _hostLayer;
self.wantsLayer = YES;
_ballLayer = [CALayer layer];
_ballLayer.bounds = CGRectMake(0, 0, 32, 32);
_ballLayer.position = CGPointMake(40, 40);
_ballLayer.backgroundColor = CGColorGetConstantColor(kCGColorWhite);
_ballLayer.cornerRadius = 16;
_hostLayer.sublayers = #[_ballLayer];
CGMutablePathRef path = CGPathCreateMutable();
CGPathMoveToPoint(path, NULL, _ballLayer.position.x, _ballLayer.position.y);
CGPathAddRect(path, NULL, CGRectInset(bounds, 40, 40));
CGPathCloseSubpath(path);
_ballPositionAnimation = [CAKeyframeAnimation animationWithKeyPath:#"position"];
_ballPositionAnimation.path = path;
_ballPositionAnimation.duration = 6;
_ballPositionAnimation.repeatCount = HUGE_VALF;
CGPathRelease(path);
[_ballLayer addAnimation:_ballPositionAnimation forKey:_ballPositionAnimation.keyPath];
[CATransaction commit];
[_slider bind:NSValueBinding toObject:self withKeyPath:#"speed" options:#{NSContinuouslyUpdatesValueBindingOption:#YES}];
}
- (double) speed
{
return _speed;
}
- (void) setSpeed:(double)speed
{
_speed = speed;
CGPoint pos = [(CALayer*)_ballLayer.presentationLayer position];
[CATransaction begin];
[CATransaction setDisableActions:YES];
_ballPositionAnimation.speed = _speed;
_ballPositionAnimation.duration = 5.0;
_ballPositionAnimation.timeOffset = _ballPositionAnimation.duration * [self percentOfPathCompleted:pos];
[_ballLayer addAnimation:_ballPositionAnimation forKey:_ballPositionAnimation.keyPath];
[CATransaction commit];
}
- (double) percentOfPathCompleted:(CGPoint)p
{
CGRect rect = CGRectInset(self.bounds, 40, 40);
double minX = NSMinX(rect);
double minY = NSMinY(rect);
double maxX = NSMaxX(rect);
double maxY = NSMaxY(rect);
double offset = 0.0;
if (p.x == minX && p.y == minY)
return 0.0;
else if (p.x > minX && p.y == minY)
offset = (p.x - minX) / rect.size.width * 0.25;
else if (p.x == maxX && p.y < maxY)
offset = (p.y - minY) / rect.size.height * 0.25 + 0.25;
else if (p.x > minX && p.y == maxY)
offset = (1.0 - (p.x - minX) / rect.size.width) * 0.25 + 0.50;
else
offset = (1.0 - (p.y - minY) / rect.size.height) * 0.25 + 0.75;
NSLog(#"Offset = %f",offset);
return offset;
}
#end
I want to animate a sub-classed UIView using Core Animation. The oddity in my case is that I want the animation to run a fixed amount every frame and not by the duration. So animating from 100 -> 200 should take longer than 100 -> 50, but the "velocity" of the view should be constant. My code currently looks like this:
CABasicAnimation* animation = [CABasicAnimation animationWithKeyPath:#"transform"];
animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn];
animation.duration = 0.4;
[[self layer] addAnimation:animation forKey:#"transform"];
self.transform = CGAffineTransformMakeTranslation(0, 100);
It this possible? How would it be done?
Why not just compute the desired time from the desired movement? Something like:
CGFloat distanceToTimeFactor = 0.1;
CGPoint current = self.center;
CGPoint new = CGPointMake(100,100);
CGFloat xDist = (new.x - current.x);
CGFloat yDist = (new.y - current.y);
CGFloat distance = sqrt((xDist * xDist) + (yDist * yDist));
CGFloat duration = distance * distanceToTimeFactor;
And then animate with that duration.