How can I animate the movement of a view or image along a curved path?
To expand upon what Nikolai said, the best way to handle this is to use Core Animation to animate the motion of the image or view along a Bezier path. This is accomplished using a CAKeyframeAnimation. For example, I've used the following code to animate an image of a view into an icon to indicate saving (as can be seen in the video for this application):
First of all import QuartzCore header file#import <QuartzCore/QuartzCore.h>
UIImageView *imageViewForAnimation = [[UIImageView alloc] initWithImage:imageToAnimate];imageViewForAnimation.alpha = 1.0f;CGRect imageFrame = imageViewForAnimation.frame;//Your image frame.origin from where the animation need to get startCGPoint viewOrigin = imageViewForAnimation.frame.origin;viewOrigin.y = viewOrigin.y + imageFrame.size.height / 2.0f;viewOrigin.x = viewOrigin.x + imageFrame.size.width / 2.0f;imageViewForAnimation.frame = imageFrame;imageViewForAnimation.layer.position = viewOrigin;[self.view addSubview:imageViewForAnimation];// Set up fade out effectCABasicAnimation *fadeOutAnimation = [CABasicAnimation animationWithKeyPath:@"opacity"];[fadeOutAnimation setToValue:[NSNumber numberWithFloat:0.3]];fadeOutAnimation.fillMode = kCAFillModeForwards;fadeOutAnimation.removedOnCompletion = NO;// Set up scalingCABasicAnimation *resizeAnimation = [CABasicAnimation animationWithKeyPath:@"bounds.size"];[resizeAnimation setToValue:[NSValue valueWithCGSize:CGSizeMake(40.0f, imageFrame.size.height * (40.0f / imageFrame.size.width))]];resizeAnimation.fillMode = kCAFillModeForwards;resizeAnimation.removedOnCompletion = NO;// Set up path movementCAKeyframeAnimation *pathAnimation = [CAKeyframeAnimation animationWithKeyPath:@"position"];pathAnimation.calculationMode = kCAAnimationPaced;pathAnimation.fillMode = kCAFillModeForwards;pathAnimation.removedOnCompletion = NO;//Setting Endpoint of the animationCGPoint endPoint = CGPointMake(480.0f - 30.0f, 40.0f);//to end animation in last tab use //CGPoint endPoint = CGPointMake( 320-40.0f, 480.0f);CGMutablePathRef curvedPath = CGPathCreateMutable();CGPathMoveToPoint(curvedPath, NULL, viewOrigin.x, viewOrigin.y);CGPathAddCurveToPoint(curvedPath, NULL, endPoint.x, viewOrigin.y, endPoint.x, viewOrigin.y, endPoint.x, endPoint.y);pathAnimation.path = curvedPath;CGPathRelease(curvedPath);CAAnimationGroup *group = [CAAnimationGroup animation]; group.fillMode = kCAFillModeForwards;group.removedOnCompletion = NO;[group setAnimations:[NSArray arrayWithObjects:fadeOutAnimation, pathAnimation, resizeAnimation, nil]];group.duration = 0.7f;group.delegate = self;[group setValue:imageViewForAnimation forKey:@"imageViewBeingAnimated"];[imageViewForAnimation.layer addAnimation:group forKey:@"savingAnimation"];[imageViewForAnimation release];
The way to animate along CGPath using UIView.animateKeyframes
(Swift 4)
private func animateNew() { let alphaFrom: CGFloat = 1 let alphaTo: CGFloat = 0.3 let sizeFrom = CGSize(width: 40, height: 20) let sizeTo = CGSize(width: 80, height: 60) let originFrom = CGPoint(x: 40, y: 40) let originTo = CGPoint(x: 240, y: 480) let deltaWidth = sizeTo.width - sizeFrom.width let deltaHeight = sizeTo.height - sizeFrom.height let deltaAlpha = alphaTo - alphaFrom // Setting default values imageViewNew.alpha = alphaFrom imageViewNew.frame = CGRect(origin: originFrom, size: sizeFrom) // CGPath setup for calculating points on curve. let curvedPath = CGMutablePath() curvedPath.move(to: originFrom) curvedPath.addQuadCurve(to: originTo, control: CGPoint(x: originFrom.x, y: originTo.y)) let path = Math.BezierPath(cgPath: curvedPath, approximationIterations: 10) // Calculating timing parameters let duration: TimeInterval = 0.7 let numberOfKeyFrames = 16 let curvePoints = Math.Easing.timing(numberOfSteps: numberOfKeyFrames, .easeOutQuad) UIView.animateKeyframes(withDuration: duration, delay: 0, options: [.calculationModeCubic], animations: { // Iterating curve points and adding key frames for point in curvePoints { let origin = path.point(atPercentOfLength: point.end) let size = CGSize(width: sizeFrom.width + deltaWidth * point.end, height: sizeFrom.height + deltaHeight * point.end) let alpha = alphaFrom + deltaAlpha * point.end UIView.addKeyframe(withRelativeStartTime: TimeInterval(point.start), relativeDuration: TimeInterval(point.duration)) { self.imageViewNew.frame = CGRect(origin: origin, size: size) self.imageViewNew.alpha = alpha } } }, completion: nil)}
File: Math.Easing.swift
// Inspired by: RBBAnimation/RBBEasingFunction.m: https://github.com/robb/RBBAnimation/blob/master/RBBAnimation/RBBEasingFunction.mextension Math { public struct Easing { } }extension Math.Easing { public enum Algorithm: Int { case linear, easeInQuad, easeOutQuad, easeInOutQuad } @inline(__always) public static func linear(_ t: CGFloat) -> CGFloat { return t } @inline(__always) public static func easeInQuad(_ t: CGFloat) -> CGFloat { return t * t } @inline(__always) public static func easeOutQuad(_ t: CGFloat) -> CGFloat { return t * (2 - t) } @inline(__always) public static func easeInOutQuad(_ t: CGFloat) -> CGFloat { if t < 0.5 { return 2 * t * t } else { return -1 + (4 - 2 * t) * t } }}extension Math.Easing { public struct Timing { public let start: CGFloat public let end: CGFloat public let duration: CGFloat init(start: CGFloat, end: CGFloat) { self.start = start self.end = end self.duration = end - start } public func multiplying(by: CGFloat) -> Timing { return Timing(start: start * by, end: end * by) } } public static func process(_ t: CGFloat, _ algorithm: Algorithm) -> CGFloat { switch algorithm { case .linear: return linear(t) case .easeInQuad: return easeInQuad(t) case .easeOutQuad: return easeOutQuad(t) case .easeInOutQuad: return easeInOutQuad(t) } } public static func timing(numberOfSteps: Int, _ algorithm: Algorithm) -> [Timing] { var result: [Timing] = [] let linearStepSize = 1 / CGFloat(numberOfSteps) for step in (0 ..< numberOfSteps).reversed() { let linearValue = CGFloat(step) * linearStepSize let processedValue = process(linearValue, algorithm) // Always in range 0 ... 1 let lastValue = result.last?.start ?? 1 result.append(Timing(start: processedValue, end: lastValue)) } result = result.reversed() return result }}
File: Math.BezierPath.swift
. Look on this SO answer: https://stackoverflow.com/a/50782971/1418981
You can animate a UIView's center property using a CAKeyframeAnimation. See the CoreAnimation programming guide.