Want to make rotating button which I can press while animating - ios

I've made a rotating button. But while button rotates I can't press it. So The question is how I can make it "pressable"?
My animation code is:
var max: Bool = true
func startAnimation() {
max = !max
let duration: Double = 1
let fullCircle = 2 * M_PI
let upOrDown = (max ? CGFloat(-1 / 16 * fullCircle) : CGFloat(1 / 16 * fullCircle))
let scale: (CGFloat, CGFloat) = (max ? (1.0, 1.0) : (1.3, 1.3))
UIView.animateWithDuration(duration, animations: { () -> Void in
let rotationAnimation = CGAffineTransformMakeRotation(upOrDown)
let scaleAnimation = CGAffineTransformMakeScale(scale)
self.startButton.transform = CGAffineTransformConcat(rotationAnimation, scaleAnimation)
}) { (finished) -> Void in
self.startAnimation()
}
So if I press button there is no any effect that it has happened. No text inside button text animation - nothing! But button keep rotating and don't perform segue to other scene. But if it is no animation I can do segue.

Use the animation option UIViewAnimationOptionAllowUserInteraction.
UIView.animateWithDuration(duration, delay:, options: UIViewAnimationOptions.AllowUserInteraction, animations: , completion: )

The final code works fine and looks like:
var max: Bool = true
func startAnimation() {
max = !max
let duration: Double = 1
let fullCircle = 2 * M_PI
let upOrDown = (max ? CGFloat(-1 / 16 * fullCircle) : CGFloat(1 / 16 * fullCircle))
let scale: (CGFloat, CGFloat) = (max ? (1.0, 1.0) : (1.3, 1.3))
UIView.animateWithDuration(duration, delay: 0, options: UIViewAnimationOptions.AllowUserInteraction, animations: { () -> Void in
let rotationAnimation = CGAffineTransformMakeRotation(upOrDown)
let scaleAnimation = CGAffineTransformMakeScale(scale)
self.startButton.transform = CGAffineTransformConcat(rotationAnimation, scaleAnimation)
}) { (finished) -> Void in
self.startAnimation()
}

If you are using UIViewPropertyAnimator the following might also help you
let animator = UIViewPropertyAnimator(duration: 2, curve: .easeInOut) {
// animation code
}
animator.isUserInteractionEnabled = true
animator.startAnimation()

Related

UIView keyframe animation timing not as expected

I have some animations (testable in a playground):
import UIKit
import XCPlayground
import PlaygroundSupport
PlaygroundPage.current.needsIndefiniteExecution
let view = UIView()
view.frame = .init(x: 0, y: 0, width: 150, height: 150)
view.backgroundColor = .orange
let hand = UIView()
view.addSubview(hand)
hand.frame = .init(x: 0, y: 0, width: 10, height: 10)
hand.center = view.center
hand.backgroundColor = .green
PlaygroundPage.current.liveView = view
let fadeDuration: TimeInterval = 0.4
let translationDuration: TimeInterval = 1
let resetDuration: TimeInterval = 0.25
let duration: TimeInterval =
7 * fadeDuration +
4 * translationDuration +
3 * resetDuration
var currentTime: TimeInterval = 0.0
func addKey(_ animations: #escaping () -> Void, animationTime: TimeInterval) {
UIView.addKeyframe(withRelativeStartTime: currentTime / duration, relativeDuration: animationTime / duration, animations: animations)
currentTime += animationTime
}
func fadeIn() {
addKey({
hand.alpha = 1
}, animationTime: fadeDuration)
}
func fadeOut() {
addKey({
hand.alpha = 0
}, animationTime: fadeDuration)
}
func translate(_ direction: (CGFloat) -> CGFloat) {
let x = direction(50)
addKey({
hand.transform = .init(translationX: x, y: 0)
}, animationTime: translationDuration)
}
func reset() {
addKey({
hand.transform = .identity
}, animationTime: 0)
currentTime += resetDuration
}
UIView.animateKeyframes(withDuration: duration, delay: 0, options: .calculationModeLinear) {
translate(-)
fadeOut()
reset()
fadeIn()
translate(-)
fadeOut()
reset()
fadeIn()
translate(+)
fadeOut()
reset()
fadeIn()
translate(+)
fadeOut()
} completion: { _ in
PlaygroundPage.current.finishExecution()
}
I expect every single translate() animation to run at the same speed, but for some reason the first and last ones are especially slow, despite using .calculationModeLinear
How to make it so that translationDuration / duration is constant time ?
calculationModeLinear is different from curveLinear. It sounds like you want the latter. You are using curveEaseInOut by default so of course the movement slows near the overall start and finish.
Some hanky panky is needed to overcome Swift’s strict typing. Sample code:
let animationOptions: UIView.AnimationOptions = .curveLinear
let keyframeAnimationOptions = UIView.KeyframeAnimationOptions(rawValue: animationOptions.rawValue)
UIView.animateKeyframes(withDuration: duration, delay: 0.1, options: keyframeAnimationOptions) {

Double sided UIView not working

I have a subclass of UIView called TitleView, all this subclass does is override layerClass to return CATransformLayer.
My titleView property has some subviews; a titleBackgroundView and a titleLabel.
When I run my code the titleView’s top layer is visible (green background), but when I run my flip animation, there’s no animation. The code just jumps to the end state. Furthermore there’s no bottom layer visible (red background), just a reversed version of the titleView (a transformed titleLabel).
In the IBOutlet setter I have the following code:
#IBOutlet private weak var titleView: TitleView! {
didSet {
titleView.backgroundColor = UIColor.clearColor()
let topLayer = CALayer()
topLayer.backgroundColor = UIColor.greenColor().CGColor
topLayer.frame = titleView.bounds
topLayer.doubleSided = false
topLayer.zPosition = 3
titleView.layer.addSublayer(topLayer)
let bottomLayer = CALayer()
bottomLayer.backgroundColor = UIColor.redColor().CGColor
bottomLayer.frame = titleView.bounds
bottomLayer.doubleSided = false
bottomLayer.zPosition = 2
bottomLayer.transform = CATransform3DMakeRotation(CGFloat(M_PI), 1, 0, 0)
titleView.layer.addSublayer(bottomLayer)
}
}
titleView animation code:
private func setIsCategoriesShowing(showCategories: Bool, animated: Bool)
{
let alreadyInFinishState = (isShowingCategories == showCategories) ? true : false
if alreadyInFinishState
{
return
}
// Setup animations
isAnimatingCategories = true
headerView.superview?.bringSubviewToFront(headerView)
titleView.layer.setAnchorPointDynamically(CGPoint(x: 0.5, y: 1)) // Change position when updating anchor point
// Animate
let duration: NSTimeInterval = animated ? 0.8 : 0
let options: UIViewAnimationOptions = (showCategories == true) ? [.CurveEaseIn] : [.CurveEaseOut]
let newRotationValue: CGFloat = (showCategories == true) ? -179 : 0
let damping: CGFloat = (showCategories == true) ? 0.7 : 1
let initialSpringVelocity: CGFloat = (showCategories == true) ? 0.5 : 1
UIView.animateWithDuration(duration,
delay: 0,
usingSpringWithDamping: damping,
initialSpringVelocity: initialSpringVelocity,
options: options,
animations: { () -> Void in
var rotationAndPerspectiveTransform = CATransform3DIdentity
rotationAndPerspectiveTransform.m34 = 1 / -500
rotationAndPerspectiveTransform = CATransform3DRotate(rotationAndPerspectiveTransform, newRotationValue, 1, 0, 0);
self.titleView.layer.sublayerTransform = rotationAndPerspectiveTransform;
}) { (success) -> Void in
if showCategories == false
{
self.titleView.layer.sublayerTransform = CATransform3DIdentity
}
self.isAnimatingCategories = false
self.isShowingCategories = showCategories
}
}
Okay, so given a series of trial and error I’ve managed to (it seems) fix my problem. Feel free to take a look…
Here is the working code:
#IBOutlet private weak var titleView: TitleView! {
didSet {
let bottomLayer = CALayer()
bottomLayer.backgroundColor = UIColor.redColor().CGColor
bottomLayer.frame = titleView.bounds
bottomLayer.doubleSided = false
bottomLayer.zPosition = 2
bottomLayer.transform = CATransform3DMakeRotation(CGFloat(M_PI), 0, 1, 0) // Reverse bottom layer, so when flipped it's visible.
titleView.layer.addSublayer(bottomLayer)
// All subviews are one-sided
for subview in titleView.subviews
{
subview.layer.doubleSided = false
}
}
}
#IBOutlet private weak var titleViewBackgroundView: UIView!
Animation code:
private func setIsCategoriesShowing(showCategories: Bool, animated: Bool)
{
let alreadyInFinishState = (isShowingCategories == showCategories) ? true : false
if alreadyInFinishState
{
return
}
// Housekeeping
headerView.superview?.bringSubviewToFront(headerView)
titleView.layer.setAnchorPointDynamically(CGPoint(x: 0.5, y: 1))
// Animate
isAnimatingCategories = true
let isOpening = (showCategories == true)
let duration: NSTimeInterval = animated ? 3 : 0
let damping: CGFloat = isOpening ? 0.7 : 1
let initialSpringVelocity: CGFloat = isOpening ? 0.5 : 1
let options: UIViewAnimationOptions = isOpening ? [.CurveEaseIn] : [.CurveEaseOut]
let newRotationValue: CGFloat = isOpening ? -179 : 0
var rotationAndPerspectiveTransform = CATransform3DIdentity
rotationAndPerspectiveTransform.m34 = 1 / -500
rotationAndPerspectiveTransform = CATransform3DRotate(rotationAndPerspectiveTransform, newRotationValue, 1, 0, 0);
UIView.animateWithDuration(duration,
delay: 0,
usingSpringWithDamping: damping,
initialSpringVelocity: initialSpringVelocity,
options: options,
animations: {
self.titleView.layer.transform = rotationAndPerspectiveTransform;
}) { (success) -> Void in
if !isOpening
{
self.titleView.layer.transform = CATransform3DIdentity
}
self.isAnimatingCategories = !success
self.isShowingCategories = showCategories
}
}

Rotate a view for 360 degrees indefinitely in Swift?

I want to rotate an image view for 360 degrees indefinitely.
UIView.animate(withDuration: 2, delay: 0, options: [.repeat], animations: {
self.view.transform = CGAffineTransform(rotationAngle: 6.28318530717959)
}, completion: nil)
How can I do it?
UPDATE Swift 5.x
// duration will helps to control rotation speed
private func rotateView(targetView: UIView, duration: Double = 5) {
UIView.animate(withDuration: duration, delay: 0.0, options: .curveLinear, animations: {
targetView.transform = targetView.transform.rotated(by: .pi)
}) { finished in
self.rotateView(targetView: targetView, duration: duration)
}
}
Swift 2.x way to rotate UIView indefinitely, compiled from earlier answers:
// Rotate <targetView> indefinitely
private func rotateView(targetView: UIView, duration: Double = 1.0) {
UIView.animateWithDuration(duration, delay: 0.0, options: .CurveLinear, animations: {
targetView.transform = CGAffineTransformRotate(targetView.transform, CGFloat(M_PI))
}) { finished in
self.rotateView(targetView, duration: duration)
}
}
UPDATE Swift 3.x
// Rotate <targetView> indefinitely
private func rotateView(targetView: UIView, duration: Double = 1.0) {
UIView.animate(withDuration: duration, delay: 0.0, options: .curveLinear, animations: {
targetView.transform = targetView.transform.rotated(by: CGFloat(M_PI))
}) { finished in
self.rotateView(targetView: targetView, duration: duration)
}
}
Swift 3.0
let imgViewRing = UIImageView(image: UIImage(named: "apple"))
imgViewRing.frame = CGRect(x: 0, y: 0, width: UIImage(named: "apple")!.size.width, height: UIImage(named: "apple")!.size.height)
imgViewRing.center = CGPoint(x: self.view.frame.size.width/2.0, y: self.view.frame.size.height/2.0)
rotateAnimation(imageView: imgViewRing)
self.view.addSubview(imgViewRing)
This is the animation logic
func rotateAnimation(imageView:UIImageView,duration: CFTimeInterval = 2.0) {
let rotateAnimation = CABasicAnimation(keyPath: "transform.rotation")
rotateAnimation.fromValue = 0.0
rotateAnimation.toValue = CGFloat(.pi * 2.0)
rotateAnimation.duration = duration
rotateAnimation.repeatCount = .greatestFiniteMagnitude
imageView.layer.add(rotateAnimation, forKey: nil)
}
You can check output in this link
Use this extension to rotate UIImageView 360 degrees.
extension UIView {
func rotate360Degrees(duration: CFTimeInterval = 1.0, completionDelegate: AnyObject? = nil) {
let rotateAnimation = CABasicAnimation(keyPath: "transform.rotation")
rotateAnimation.fromValue = 0.0
rotateAnimation.toValue = CGFloat(M_PI)
rotateAnimation.duration = duration
if let delegate: CAAnimationDelegate = completionDelegate as! CAAnimationDelegate? {
rotateAnimation.delegate = delegate
}
self.layer.addAnimation(rotateAnimation, forKey: nil)
}
}
Than to rotate UIImageView simply use this method
self.YOUR_SUBVIEW.rotate360Degrees()
This one work for me in Swift 2.2:
let rotationAnimation = CABasicAnimation(keyPath: "transform.rotation.z")
rotationAnimation.fromValue = 0.0
rotationAnimation.toValue = 360 * CGFloat(M_PI/180)
let innerAnimationDuration : CGFloat = 1.0
rotationAnimation.duration = Double(innerAnimationDuration)
rotationAnimation.repeatCount = HUGE
self.imageView.addAnimation(rotationAnimation, forKey: "rotateInner")
I would stick it in a function like rotateImage() and in the completion code just call rotateImage() again. I think you should use M_PI (or the swift equivalent) for the rotation amount, though.
Updated for Swift 3:
I made an extension to UIView and included the following function within:
func rotate(fromValue: CGFloat, toValue: CGFloat, duration: CFTimeInterval = 1.0, completionDelegate: Any? = nil) {
let rotateAnimation = CABasicAnimation(keyPath: >"transform.rotation")
rotateAnimation.fromValue = fromValue
rotateAnimation.toValue = toValue
rotateAnimation.duration = duration
if let delegate: Any = completionDelegate {
rotateAnimation.delegate = delegate as? CAAnimationDelegate
}
self.layer.add(rotateAnimation, forKey: nil)
}
You can then call the function via (on a UIView I made into a button for example) :
monitorButton.rotate(fromValue: 0.0, toValue: CGFloat(M_PI * 2), completionDelegate: self)
Hope this helps!
I think what you really want here is to use a CADisplayLink. Reason being that this would be indefinitely smooth versus using completion blocks which may cause slight hiccups and are not as easily cancelable. See the following solution:
var displayLink : CADisplayLink?
var targetView = UIView()
func beginRotation () {
// Setup display link
self.displayLink = CADisplayLink(target: self, selector: #selector(onFrameInterval(displayLink:)))
self.displayLink?.preferredFramesPerSecond = 60
self.displayLink?.add(to: .current, forMode: RunLoop.Mode.default)
}
func stopRotation () {
// Invalidate display link
self.displayLink?.invalidate()
self.displayLink = nil
}
// Called everytime the display is refreshed
#objc func onFrameInterval (displayLink: CADisplayLink) {
// Get frames per second
let framesPerSecond = Double(displayLink.preferredFramesPerSecond)
// Based on fps, calculate how much target view should spin each interval
let rotationsPerSecond = Double(3)
let anglePerSecond = rotationsPerSecond * (2 * Double.pi)
let anglePerInterval = CGFloat(anglePerSecond / framesPerSecond)
// Rotate target view to match the current angle of the interval
self.targetView.layer.transform = CATransform3DRotate(self.targetView.layer.transform, anglePerInterval, 0, 0, 1)
}
Avoiding the completion closure with recursive calls!
Bit late to this party, but using UIView keyFrame animation & varying stages of rotation for each keyFrame, plus setting the animation curve works nicely. Here's an UIView class function -
class func rotate360(_ view: UIView, duration: TimeInterval, repeating: Bool = true) {
let transform1 = CGAffineTransform(rotationAngle: .pi * 0.75)
let transform2 = CGAffineTransform(rotationAngle: .pi * 1.5)
let animationOptions: UInt
if repeating {
animationOptions = UIView.AnimationOptions.curveLinear.rawValue | UIView.AnimationOptions.repeat.rawValue
} else {
animationOptions = UIView.AnimationOptions.curveLinear.rawValue
}
let keyFrameAnimationOptions = UIView.KeyframeAnimationOptions(rawValue: animationOptions)
UIView.animateKeyframes(withDuration: duration, delay: 0, options: [keyFrameAnimationOptions, .calculationModeLinear], animations: {
UIView.addKeyframe(withRelativeStartTime: 0, relativeDuration: 0.375) {
view.transform = transform1
}
UIView.addKeyframe(withRelativeStartTime: 0.375, relativeDuration: 0.375) {
view.transform = transform2
}
UIView.addKeyframe(withRelativeStartTime: 0.75, relativeDuration: 0.25) {
view.transform = .identity
}
}, completion: nil)
}
Looks pretty gnarly, with the weird rotation angles, but as the op & others have found, you can't just tell it to rotate 360
Try this one it works for me, i am rotating image for once
UIView.animateWithDuration(0.5, delay: 0.0, options: .CurveLinear, animations: {
self.imgViewReload.transform = CGAffineTransformRotate(self.imgViewReload.transform, CGFloat(M_PI))
}, completion: {
(value: Bool) in
UIView.animateWithDuration(0.5, delay: 0.0, options: .CurveLinear, animations: {
self.imgViewReload.transform = CGAffineTransformIdentity
}, completion: nil)
})
you can use this function ... just give it the view and the duration
it has two animations the the first rotates the view 180° and the other one rotates it to 360° then the function calls itself which allows it to continue the rotation animation infinitely
func infinite360Animation(targetView: UIView, duration: Double) {
UIView.animate(withDuration: duration/2, delay: 0, options: .curveLinear) {
targetView.transform = CGAffineTransform.identity.rotated(by: .pi )
} completion: { (_) in
UIView.animate(withDuration: duration/2, delay: 0, options: .curveLinear) {
targetView.transform = CGAffineTransform.identity.rotated(by: .pi * 2)
} completion: { (_) in
self.infinite360Animation(targetView: targetView, duration: duration)
}
}
}
You can then call the function like this
infinite360Animatio(targetView: yourView, duration: 3)
On your tabBarController, make sure to set your delegate and do the following didSelect method below:
func tabBarController(_ tabBarController: UITabBarController,
didSelect viewController: UIViewController) {
if let selectedItem:UITabBarItem = tabBarController.tabBar.selectedItem {
animated(tabBar: tabBarController.tabBar, selectedItem: selectedItem)
}
}
fileprivate func animated(tabBar: UITabBar, selectedItem:UITabBarItem){
if let view:UIView = selectedItem.value(forKey: "view") as? UIView {
if let currentImageView = view.subviews.first as? UIImageView {
rotate(imageView: currentImageView, completion: { (completed) in
self.restore(imageView: currentImageView, completion: nil)
})
}
}
}
fileprivate func rotate(imageView:UIImageView, completion:((Bool) ->Void)?){
UIView.animate(withDuration: animationSpeed, delay: 0.0, options: .curveLinear, animations: {
imageView.transform = CGAffineTransform.init(rotationAngle: CGFloat.pi)
}, completion: {
(value: Bool) in
completion?(value)
})
}
fileprivate func restore(imageView:UIImageView, completion:((Bool) ->Void)?){
UIView.animate(withDuration: animationSpeed, delay: 0.0, options: .curveLinear, animations: {
imageView.transform = CGAffineTransform.identity
}, completion: {
(value: Bool) in
completion?(value)
})
}

Create a continuously rotating square on the screen using animateKeyframesWithDuration

I tried to use the code below to create a continuously rotating square on the screen. But I don't know why the rotational speed is changing. How could I change the code to make the rotational speed invariable? I tried different UIViewKeyframeAnimationOptions, but seems none of them work.
override func viewDidLoad() {
super.viewDidLoad()
let square = UIView()
square.frame = CGRect(x: 55, y: 300, width: 40, height: 40)
square.backgroundColor = UIColor.redColor()
self.view.addSubview(square)
let duration = 1.0
let delay = 0.0
let options = UIViewKeyframeAnimationOptions.Repeat
UIView.animateKeyframesWithDuration(duration, delay: delay, options: options, animations: {
let fullRotation = CGFloat(M_PI * 2)
UIView.addKeyframeWithRelativeStartTime(0, relativeDuration: 1/3, animations: {
square.transform = CGAffineTransformMakeRotation(1/3 * fullRotation)
})
UIView.addKeyframeWithRelativeStartTime(1/3, relativeDuration: 1/3, animations: {
square.transform = CGAffineTransformMakeRotation(2/3 * fullRotation)
})
UIView.addKeyframeWithRelativeStartTime(2/3, relativeDuration: 1/3, animations: {
square.transform = CGAffineTransformMakeRotation(3/3 * fullRotation)
})
}, completion: {finished in
})
}
I faced same problem before, this is how I make it work:
Swift 2
let raw = UIViewKeyframeAnimationOptions.Repeat.rawValue | UIViewAnimationOptions.CurveLinear.rawValue
let options = UIViewKeyframeAnimationOptions(rawValue: raw)
Swift 3,4,5
let raw = UIView.KeyframeAnimationOptions.repeat.rawValue | UIView.AnimationOptions.curveLinear.rawValue
let options = UIView.KeyframeAnimationOptions(rawValue: raw)
I figured this problem out just before gave it up. I don't think there is doc about it, but it just work.
That's really odd... UIView.animateKeyframesWithDuration isn't working as I would expect it to with UIViewKeyframeAnimationOptions.CalculationModeLinear|UIViewKeyframeAnimationOpti‌​ons.Repeat passed in with options.
If you use the non-block method of creating a keyframe animation (see below) the rotation repeats as expected.
If I find out why the block-based option isn't working I'll try and remember to update answer here too!
override func viewDidLoad() {
super.viewDidLoad()
let square = UIView()
square.frame = CGRect(x: 55, y: 300, width: 40, height: 40)
square.backgroundColor = UIColor.redColor()
self.view.addSubview(square)
let fullRotation = CGFloat(M_PI * 2)
let animation = CAKeyframeAnimation()
animation.keyPath = "transform.rotation.z"
animation.duration = 2
animation.removedOnCompletion = false
animation.fillMode = kCAFillModeForwards
animation.repeatCount = Float.infinity
animation.values = [fullRotation/4, fullRotation/2, fullRotation*3/4, fullRotation]
square.layer.addAnimation(animation, forKey: "rotate")
}
Add UIViewKeyframeAnimationOptionCalculationModeLinear do your keyframe options. The default behavior for UIView animations is to "ease in/out" of the animation. i.e., start slow, go up to speed, then slow down again just near the end.
AFAIU, this is happening because the default animation curve is UIViewAnimationOption.CurveEaseInOut.
Unfortunately, UIViewKeyframeAnimationOptions doesn't have options for changing the curve, but you can add them manually!
Use this extension:
Swift 2
extension UIViewKeyframeAnimationOptions {
static var CurveEaseInOut: UIViewKeyframeAnimationOptions { return UIViewKeyframeAnimationOptions(rawValue: UIViewAnimationOptions.CurveEaseInOut.rawValue) }
static var CurveEaseIn: UIViewKeyframeAnimationOptions { return UIViewKeyframeAnimationOptions(rawValue: UIViewAnimationOptions.CurveEaseIn.rawValue) }
static var CurveEaseOut: UIViewKeyframeAnimationOptions { return UIViewKeyframeAnimationOptions(rawValue: UIViewAnimationOptions.CurveEaseOut.rawValue) }
static var CurveLinear: UIViewKeyframeAnimationOptions { return UIViewKeyframeAnimationOptions(rawValue: UIViewAnimationOptions.CurveLinear.rawValue) }
}
Swift 3, 4, 5
extension UIView.KeyframeAnimationOptions {
static var curveEaseInOut: UIView.KeyframeAnimationOptions { return UIView.KeyframeAnimationOptions(rawValue: UIView.AnimationOptions.curveEaseInOut.rawValue) }
static var curveEaseIn: UIView.KeyframeAnimationOptions { return UIView.KeyframeAnimationOptions(rawValue: UIView.AnimationOptions.curveEaseIn.rawValue) }
static var curveEaseOut: UIView.KeyframeAnimationOptions { return UIView.KeyframeAnimationOptions(rawValue: UIView.AnimationOptions.curveEaseOut.rawValue) }
static var curveLinear: UIView.KeyframeAnimationOptions { return UIView.KeyframeAnimationOptions(rawValue: UIView.AnimationOptions.curveLinear.rawValue) }
}
Now you can use curve options in UIView.animateKeyframesWithDuration method
Swift 2
let keyframeOptions: UIViewKeyframeAnimationOptions = [.Repeat, .CurveLinear]
UIView.animateKeyframesWithDuration(duration, delay: delay, options: keyframeOptions, animations: {
// add key frames here
}, completion: nil)
Swift 3, 4, 5
let keyframeOptions: UIView.KeyframeAnimationOptions = [.repeat, .curveLinear]
UIView.animateKeyframes(withDuration: duration, delay: delay, options: keyframeOptions, animations: {
// add key frames here
}, completion: nil)

How to make a custom transition behave like a fall under the gravity?

The effect could be implemented like the following code in animateTransition method:
UIView.animateWithDuration(duration,
delay: 0,
usingSpringWithDamping: 0.3,
initialSpringVelocity: 0.0,
options: .CurveLinear,
animations: {
fromVC.view.alpha = 0.5
toVC.view.frame = finalFrame
},
completion: {_ -> () in
fromVC.view.alpha = 1.0
transitionContext.completeTransition(true)
})
But how could I implement it using gravity and collision behaviors(UIGravityBehavior, UICollisionBehavior)?
And a more general question may be "How to use the UIDynamicAnimator to customize the transitions between UIViewControllers?"
You can find the solution under the post Custom view controller transitions with UIDynamic behaviors by dasdom.
And the Swift code:
func transitionDuration(transitionContext: UIViewControllerContextTransitioning!) -> NSTimeInterval {
return 1.0
}
func animateTransition(transitionContext: UIViewControllerContextTransitioning!) {
// 1. Prepare for the required components
let toVC = transitionContext.viewControllerForKey(UITransitionContextToViewControllerKey)
let finalFrame = transitionContext.finalFrameForViewController(toVC)
let containerView = transitionContext.containerView()
let screenBounds = UIScreen.mainScreen().bounds
// 2. Make toVC at the top of the screen
toVC.view.frame = CGRectOffset(finalFrame, 0, -1.0 * CGRectGetHeight(screenBounds))
containerView.addSubview(toVC.view)
// 3. Set the dynamic animators used by the view controller presentation
var animator: UIDynamicAnimator? = UIDynamicAnimator(referenceView: containerView)
let gravity = UIGravityBehavior(items: [toVC.view])
gravity.magnitude = 10
let collision = UICollisionBehavior(items: [toVC.view])
collision.addBoundaryWithIdentifier("GravityBoundary",
fromPoint: CGPoint(x: 0, y: screenBounds.height),
toPoint: CGPoint(x: screenBounds.width, y: screenBounds.height))
let animatorItem = UIDynamicItemBehavior(items: [toVC.view])
animatorItem.elasticity = 0.5
animator!.addBehavior(gravity)
animator!.addBehavior(collision)
animator!.addBehavior(animatorItem)
// 4. Complete the transition after the time of the duration
let nsecs = transitionDuration(transitionContext) * Double(NSEC_PER_SEC)
let delay = dispatch_time(DISPATCH_TIME_NOW, Int64(nsecs))
dispatch_after(delay, dispatch_get_main_queue()) {
animator = nil
transitionContext.completeTransition(true)
}
}
A little more complicated than using animateWithDuration:delay:usingSpringWithDamping:initialSpringVelocity:options:animations:completion: method.
EDIT: Fixed a bug when 'transitionDuration' is ≤1

Resources