Custom UIViewController transition doesn't next animations perform - ios

When I use the custom UIViewControllerAnimatedTransitioning to do a custom transition from AViewController to BViewController it performs as expected, using a Cubic Animation, but then when I try to use a default iOS presentation, like a modal to transition from BViewController to CViewController, it doesn't animate at all.
Here's the code of the first transition:
class CubeTransitionAnimator: NSObject, UIViewControllerAnimatedTransitioning {
private var direction: Direction!
convenience init(direction: Direction) {
self.init()
self.direction = direction
}
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return 1
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
let containerView = transitionContext.containerView
guard let fromView = transitionContext.view(forKey: .from),
let toView = transitionContext.view(forKey: .to) else {
return
}
toView.frame = fromView.frame
containerView.backgroundColor = UIColor.white
let fromViewIntialTransform = fromView.layer.transform
fromView.layer.anchorPointZ = -((fromView.frame.size.width) / 2)
var transform: CATransform3D = CATransform3DIdentity
transform.m34 = -1.0 / 1000
transform = CATransform3DTranslate(transform, 0, 0, (fromView.layer.anchorPointZ))
fromView.layer.transform = transform
containerView.addSubview(fromView)
toView.alpha = 1
fromView.alpha = 1
let toViewIntialTransform = toView.layer.transform
toView.layer.anchorPointZ = -((toView.frame.size.width) / 2)
transform = CATransform3DIdentity
transform.m34 = -1.0 / 1000
transform = CATransform3DTranslate(transform, 0, 0, (toView.layer.anchorPointZ))
toView.layer.transform = transform
containerView.insertSubview(toView, belowSubview:fromView)
// transform toView to it's begining position
switch direction {
case .left: // t angle x y z
toView.layer.transform = CATransform3DRotate((toView.layer.transform), CGFloat(-Double.pi/2), 0, 1, 0)
case .right:
toView.layer.transform = CATransform3DRotate((toView.layer.transform), CGFloat(Double.pi / 2), 0, 1, 0)
case .up:
toView.layer.transform = CATransform3DRotate((toView.layer.transform), CGFloat(Double.pi/2), 1, 0, 0)
case .down:
toView.layer.transform = CATransform3DRotate((toView.layer.transform), CGFloat(-Double.pi/2), 1, 0, 0)
default:
break
}
UIView.animate(withDuration: transitionDuration(using: transitionContext), delay: 0.0, options: UIViewAnimationOptions(), animations: {
// Animate toView to go to fromView initial position,
// And the fromView to go to a new position
switch self.direction {
case .left: // t angle x y z
toView.layer.transform = CATransform3DRotate((toView.layer.transform), CGFloat(Double.pi / 2), 0, 1, 0)
fromView.layer.transform = CATransform3DRotate((fromView.layer.transform), CGFloat(Double.pi / 2), 0, 1, 0)
case .right:
toView.layer.transform = CATransform3DRotate((toView.layer.transform), CGFloat(-Double.pi / 2), 0, 1, 0)
fromView.layer.transform = CATransform3DRotate((fromView.layer.transform), CGFloat(-Double.pi / 2), 0, 1, 0)
case .up:
toView.layer.transform = CATransform3DRotate((toView.layer.transform), CGFloat(Double.pi/2), 1, 0, 0)
fromView.layer.transform = CATransform3DRotate((fromView.layer.transform), CGFloat(Double.pi / 2), 1, 0, 0)
case .down:
toView.layer.transform = CATransform3DRotate((toView.layer.transform), CGFloat(-Double.pi/2), 1, 0, 0)
fromView.layer.transform = CATransform3DRotate((fromView.layer.transform), CGFloat(-Double.pi / 2), 1, 0, 0)
default:
break
}
}, completion: {(value: Bool) in
fromView.layer.transform = fromViewIntialTransform
toView.layer.transform = toViewIntialTransform
fromView.layoutSubviews()
toView.layoutSubviews()
// This undo the transition if it's cancelled
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
})
}
}
The thing gets even stranger when I use another transition to perform the segue from A to B, the transition from B to C starts to working again.
Second transition:
class PopTransition: NSObject, UIViewControllerAnimatedTransitioning {
let duration = 0.3
var originFrame = CGRect.zero
var presenting = true
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return duration
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
let containerView = transitionContext.containerView
let toView = transitionContext.view(forKey: .to)!
let detailView = presenting ? toView : transitionContext.view(forKey: .from)!
let initialFrame = presenting ? originFrame : detailView.frame
let finalFrame = presenting ? detailView.frame : originFrame
let xScaleFactor = presenting
? initialFrame.width / finalFrame.width
: finalFrame.width / initialFrame.width
let yScaleFactor = presenting
? initialFrame.height / finalFrame.height
: finalFrame.height / initialFrame.height
let scaleTransform = CGAffineTransform(scaleX: xScaleFactor, y: yScaleFactor)
if presenting {
detailView.transform = scaleTransform
detailView.center = CGPoint(
x: initialFrame.midX,
y: initialFrame.midY)
}
containerView.addSubview(toView)
containerView.bringSubview(toFront: detailView)
UIView.animate(
withDuration: duration,
delay: 0.0,
animations: {
detailView.transform = self.presenting ? .identity : scaleTransform
detailView.center = CGPoint(x: finalFrame.midX, y: finalFrame.midY)
},
completion:{_ in
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
}
)
}
}

You are modifying toView.layer.anchorPointZ and fromView.layer.anchorPointZ so when the transition from B to C is about to happen, the anchorPointZ is messed up.
Try saving and reseting it like you do with fromViewIntialTransform and toViewIntialTransform.

Related

iOS 13 breaks UIViewPropertyAnimator transition

We have a broken transition in WeatherKit only reproducible on iOS 13 beta. We're unsure if this is an UIKit bug or we're doing something awfully wrong.
With an array of UIViewPropertyAnimator working before iOS 13, ever since iOS 13 (through all of the betas) the animation frame is not updating correctly. For example, I have an UIViewPropertyAnimator called labelAnimator which animates a label to some specific CGRect, that CGRect is not respected and the label animates somewhere else as shown in the video.
Curious enough, if I mess around with the order of the transitions in the array, the bottom sheet works fine and the only one that animates wrong is the temperature label.
Here's the code that animates that whole view:
class MainView: UIViewController {
var panGesture = UIPanGestureRecognizer()
var tapGesture = UITapGestureRecognizer()
let animationDuration: TimeInterval = 0.75
var diff: CGFloat = 150
#IBOutlet weak var gradientView: GradientView!
#IBOutlet weak var detailedViewContainer: UIView!
#IBOutlet weak var blurView: UIVisualEffectView!
override func viewDidLoad() {
self.panGesture.addTarget(self, action: #selector(MainView.handlePanGesture(gesture:)))
self.detailedViewContainer.addGestureRecognizer(self.panGesture)
self.tapGesture.addTarget(self, action: #selector(MainView.handleTapGesture(gesture:)))
self.detailedViewContainer.addGestureRecognizer(self.tapGesture)
}
enum PanelState {
case expanded
case collapsed
}
var nextState: PanelState {
return panelIsVisible ? .collapsed : .expanded
}
var panelIsVisible: Bool = false
var runningAnimations = [UIViewPropertyAnimator]()
var animationProgressWhenInterrupted: CGFloat = 0.0
#objc func handleTapGesture(gesture: UITapGestureRecognizer) {
switch gesture.state {
case .ended:
tapAnimation()
default: break
}
}
#objc func tapAnimation(){
self.panGesture.isEnabled = false
self.tapGesture.isEnabled = false
startInteractiveTransition(state: nextState, duration: animationDuration)
updateInteractiveTransition(fractionComplete: 0)
let linearTiming = UICubicTimingParameters(controlPoint1: CGPoint(x: 0.8, y: -0.16), controlPoint2: CGPoint(x: 0.22, y: 1.18))
continueInteractiveTransition(timingParameters: linearTiming){
self.panGesture.isEnabled = true
self.tapGesture.isEnabled = true
}
}
#objc func handlePanGesture(gesture: UIPanGestureRecognizer) {
switch gesture.state {
case .began:
if !panelIsVisible ? gesture.velocity(in: nil).y < 0 : gesture.velocity(in: nil).y > 0 {
startInteractiveTransition(state: nextState, duration: animationDuration)
}
case .changed:
let translation = gesture.translation(in: self.detailedViewContainer)
var fractionComplete = (translation.y / view.bounds.height * 2)
fractionComplete = !panelIsVisible ? -fractionComplete : fractionComplete
updateInteractiveTransition(fractionComplete: fractionComplete)
case .ended:
let linearTiming = UICubicTimingParameters(controlPoint1: CGPoint(x: 0.8, y: -0.16), controlPoint2: CGPoint(x: 0.22, y: 1.18))
continueInteractiveTransition(timingParameters: linearTiming) {
self.panGesture.isEnabled = true
self.tapGesture.isEnabled = true
}
NotificationCenter.default.post(name: .resetHeaders, object: nil)
NotificationCenter.default.post(name: .disableScrolling, object: nil, userInfo: ["isDisabled": nextState == .collapsed])
default:
break
}
}
// MARK: - Animations
func animateTransitionIfNeeded(state: PanelState, duration: TimeInterval) {
if runningAnimations.isEmpty {
// MARK: Frame
var linearTiming = UICubicTimingParameters(animationCurve: .easeOut)
linearTiming = UICubicTimingParameters(controlPoint1: CGPoint(x: 0.1, y: 0.1), controlPoint2: CGPoint(x: 0.1, y: 0.1))
let frameAnimator = UIViewPropertyAnimator(duration: duration, timingParameters: linearTiming)
frameAnimator.addAnimations {
switch state {
case .expanded:
self.detailedViewContainer.frame = CGRect(x: 0, y: self.diff, width: self.view.bounds.width, height: self.view.bounds.height - self.diff)
case .collapsed:
self.detailedViewContainer.frame = CGRect(x: 0, y: self.view.bounds.height - self.view.safeAreaInsets.bottom - 165, width: self.view.bounds.width, height: 200)
}
}
// MARK: Arrow
let arrowAnimator = UIViewPropertyAnimator(duration: duration, timingParameters: linearTiming)
arrowAnimator.addAnimations {
switch state {
case .expanded:
self.leftArrowPath.transform = CGAffineTransform(rotationAngle: 15 * CGFloat.pi / 180)
self.rightArrowPath.transform = CGAffineTransform(rotationAngle: 15 * -CGFloat.pi / 180)
case .collapsed:
self.leftArrowPath.transform = CGAffineTransform(rotationAngle: 15 * -CGFloat.pi / 180)
self.rightArrowPath.transform = CGAffineTransform(rotationAngle: 15 * CGFloat.pi / 180)
}
self.leftArrowPath.center.y = self.detailedViewContainer.frame.origin.y + 15
self.rightArrowPath.center.y = self.detailedViewContainer.frame.origin.y + 15
}
// MARK: Scale
let radiusAnimator = UIViewPropertyAnimator(duration: duration, timingParameters: linearTiming)
radiusAnimator.addAnimations{
switch state {
case .expanded:
self.gradientView.transform = CGAffineTransform(scaleX: 0.9, y: 0.9)
self.gradientView.layer.maskedCorners = [.layerMaxXMinYCorner,.layerMinXMinYCorner]
self.gradientView.layer.cornerRadius = dataS.hasTopNotch ? 20 : 14
case .collapsed:
self.gradientView.transform = CGAffineTransform.identity
self.gradientView.layer.maskedCorners = [.layerMaxXMinYCorner,.layerMinXMinYCorner]
self.gradientView.layer.cornerRadius = 0
}
}
// MARK: Blur
let blurTiming = UICubicTimingParameters(controlPoint1: CGPoint(x: 0.5, y: 0.25), controlPoint2: CGPoint(x: 0.5, y: 0.75))
let blurAnimator = UIViewPropertyAnimator(duration: duration, timingParameters: blurTiming)
blurAnimator.addAnimations {
switch state {
case .expanded:
self.blurView.effect = UIBlurEffect(style: .dark)
case .collapsed:
self.blurView.effect = nil
}
}
// MARK: Text
let textAnimator = UIViewPropertyAnimator(duration: duration, timingParameters: linearTiming)
textAnimator.addAnimations({
switch state{
case .expanded:
self.tempLabel.transform = CGAffineTransform(scaleX: 0.6, y: 0.6)
self.tempLabel.frame = CGRect(origin: CGPoint(x: 15, y: self.diff / 2 - self.tempLabel.frame.height / 2), size: self.tempLabel.frame.size)
self.descriptionLabel.transform = CGAffineTransform(scaleX: 0.8, y: 0.8)
self.descriptionLabel.alpha = 0
self.descriptionLabel.transform = CGAffineTransform(translationX: 0, y: -100)
self.summaryLabel.frame = CGRect(origin: CGPoint(x: self.blurView.contentView.center.x, y: 10), size: self.summaryLabel.frame.size)
case .collapsed:
self.descriptionLabel.transform = CGAffineTransform.identity
self.descriptionLabel.alpha = 1
self.tempLabel.transform = CGAffineTransform.identity
self.tempLabel.frame = CGRect(origin: CGPoint(x: 15, y: self.view.frame.height / 2 - self.tempLabel.frame.height / 2 - 30), size: self.tempLabel.frame.size)
self.summaryLabel.frame = CGRect(origin: CGPoint(x: self.blurView.contentView.center.x, y: self.tempLabel.center.y - self.summaryLabel.frame.height / 2), size: self.summaryLabel.frame.size)
}
}, delayFactor: 0.0)
let summaryLabelTiming = UICubicTimingParameters(controlPoint1: CGPoint(x: 0.05, y: 0.95), controlPoint2: CGPoint(x: 0.15, y: 0.95))
let summaryLabelTimingReverse = UICubicTimingParameters(controlPoint1: CGPoint(x: 0.95, y: 0.5), controlPoint2: CGPoint(x: 0.85, y: 0.05))
// MARK: Summary Label
let summaryLabelAnimator = UIViewPropertyAnimator(duration: duration, timingParameters: state == .collapsed ? summaryLabelTiming : summaryLabelTimingReverse)
summaryLabelAnimator.addAnimations {
switch state{
case .expanded:
self.summaryLabel.alpha = 1
case .collapsed:
self.summaryLabel.alpha = 0
}
}
radiusAnimator.startAnimation()
runningAnimations.append(radiusAnimator)
blurAnimator.scrubsLinearly = false
blurAnimator.startAnimation()
runningAnimations.append(blurAnimator)
summaryLabelAnimator.scrubsLinearly = false
summaryLabelAnimator.startAnimation()
runningAnimations.append(summaryLabelAnimator)
frameAnimator.startAnimation()
runningAnimations.append(frameAnimator)
textAnimator.startAnimation()
textAnimator.pauseAnimation()
runningAnimations.append(textAnimator)
arrowAnimator.startAnimation()
runningAnimations.append(arrowAnimator)
// Clear animations when completed
runningAnimations.last?.addCompletion { _ in
self.runningAnimations.removeAll()
self.panelIsVisible = !self.panelIsVisible
textAnimator.startAnimation()
}
}
}
/// Called on pan .began
func startInteractiveTransition(state: PanelState, duration: TimeInterval) {
if runningAnimations.isEmpty {
animateTransitionIfNeeded(state: state, duration: duration)
for animator in runningAnimations {
animator.pauseAnimation()
animationProgressWhenInterrupted = animator.fractionComplete
}
}
let hapticSelection = SelectionFeedbackGenerator()
hapticSelection.prepare()
hapticSelection.selectionChanged()
}
/// Called on pan .changed
func updateInteractiveTransition(fractionComplete: CGFloat) {
for animator in runningAnimations {
animator.fractionComplete = fractionComplete + animationProgressWhenInterrupted
}
}
/// Called on pan .ended
func continueInteractiveTransition(timingParameters: UICubicTimingParameters? = nil, durationFactor: CGFloat = 0, completion: #escaping ()->()) {
for animator in runningAnimations {
animator.continueAnimation(withTimingParameters: timingParameters, durationFactor: durationFactor)
}
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + animationDuration) {
completion()
}
}
}
And here's a video of the issue in iOS 13 and how it currently works in iOS 12.
I have same issue, for me UIViewPropertyAnimator continueAnimation durationFactor parameter is the issue, whenever it is not 0, after few animation the table view goes crazy.

Circular Transition, change initial circle size

I have a circular transition you can find in the code below. This transition creates an extending circle with the next view controller inside.
It works fine, but I've been trying to increase the circle size on start for a while now.
To be exact, currently on execution the circle will be drawn from zero, but I want it to have an initial size which appears immediately, from where it starts extending.
How can I change the initial size of the circle?
import UIKit
class CircularTransition: NSObject {
var circle = UIView()
var startingPoint = CGPoint.zero {
didSet {
circle.center = startingPoint
}
}
var circleColor = UIColor.white
var duration = 0.2
enum CircularTransitionMode:Int {
case present, dismiss, pop
}
var transitionMode:CircularTransitionMode = .present
}
extension CircularTransition:UIViewControllerAnimatedTransitioning {
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return duration
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
let containerView = transitionContext.containerView
if transitionMode == .present {
if let presentedView = transitionContext.view(forKey: UITransitionContextViewKey.to) {
let viewCenter = presentedView.center
let viewSize = presentedView.frame.size
circle = UIView()
circle.frame = frameForCircle(withViewCenter: viewCenter, size: viewSize, startPoint: startingPoint)
circle.layer.cornerRadius = circle.frame.size.height / 2
circle.center = startingPoint
circle.backgroundColor = circleColor
circle.transform = CGAffineTransform(scaleX: 0.001, y: 0.001)
containerView.addSubview(circle)
presentedView.center = startingPoint
presentedView.transform = CGAffineTransform(scaleX: 0.001, y: 0.001)
presentedView.alpha = 0
containerView.addSubview(presentedView)
UIView.animate(withDuration: duration, animations: {
self.circle.transform = CGAffineTransform.identity
presentedView.transform = CGAffineTransform.identity
presentedView.alpha = 1
presentedView.center = viewCenter
}, completion: { (success:Bool) in
transitionContext.completeTransition(success)
})
}
}else{
let transitionModeKey = (transitionMode == .pop) ? UITransitionContextViewKey.to : UITransitionContextViewKey.from
if let returningView = transitionContext.view(forKey: transitionModeKey) {
let viewCenter = returningView.center
let viewSize = returningView.frame.size
circle.frame = frameForCircle(withViewCenter: viewCenter, size: viewSize, startPoint: startingPoint)
circle.layer.cornerRadius = circle.frame.size.height / 2
circle.center = startingPoint
UIView.animate(withDuration: duration, animations: {
self.circle.transform = CGAffineTransform(scaleX: 0.001, y: 0.001)
returningView.transform = CGAffineTransform(scaleX: 0.001, y: 0.001)
returningView.center = self.startingPoint
returningView.alpha = 0
if self.transitionMode == .pop {
containerView.insertSubview(returningView, belowSubview: returningView)
containerView.insertSubview(self.circle, belowSubview: returningView)
}
}, completion: { (success:Bool) in
returningView.center = viewCenter
returningView.removeFromSuperview()
self.circle.removeFromSuperview()
transitionContext.completeTransition(success)
})
}
}
}
func frameForCircle (withViewCenter viewCenter:CGPoint, size viewSize:CGSize, startPoint:CGPoint) -> CGRect {
let xLength = fmax(startPoint.x, viewSize.width - startPoint.x)
let yLength = fmax(startPoint.y, viewSize.height - startPoint.y)
let offestVector = sqrt(xLength * xLength + yLength * yLength) * 2
let size = CGSize(width: offestVector, height: offestVector)
return CGRect(origin: CGPoint.zero, size: size)
}
}

Swift: change UIImageView image midway during animation

I have this code to create animation imageView:
imageView.layer.anchorPoint = CGPoint(x: 0, y: 0.5)
var transform = CATransform3DIdentity
transform.m34 = -0.001
imageView.layer.transform = transform
UIView.animate(withDuration: 3) {
self.imageView.layer.transform = CATransform3DRotate(transform, .pi, 0, 1, 0)
self.imageViewCenterConstraintX.constant = 0
self.view.layoutIfNeeded()
}
In this code imageView rotate with 180 degrees. I want to change image after imageView rotate with 90 degrees.
You should chain the two animations, each rotating with pi/2.
imageView.layer.anchorPoint = CGPoint(x: 0, y: 0.5)
var transform = CATransform3DIdentity
transform.m34 = -0.001
imageView.layer.transform = transform
UIView.animate(withDuration: 1.0, animations:
{
self.imageView.layer.transform = CATransform3DRotate(transform, .pi/2, 0, 1, 0)
self.imageViewCenterConstraintX.constant = 0
})
{
(bCompleted) in
if (bCompleted)
{
self.imageView?.layer.transform = CATransform3DRotate(transform, .pi/2, 0, 1, 0)
}
UIView.animate(withDuration: 1.0, animations:
{
self.imageView.layer.transform = CATransform3DRotate(transform, .pi*0.999, 0, 1, 0)
self.imageView.image = UIImage.init(named:"anotherImage")
},
completion:
{
(bFinished) in
//Whatever
})
}
Use the one below. Use code to change image in the completion handler field.
UIView Animation with completion
You could use DispatchQueue.main.asyncAfter.
UIView.animate(withDuration: 3) {
DispatchQueue.main.asyncAfter(deadline: .now() + 1.5) {
// Change your image here
}
self.imageView.layer.transform = CATransform3DRotate(transform, .pi, 0, 1, 0)
self.imageViewCenterConstraintX.constant = 0
self.view.layoutIfNeeded()
}

Custom horizontal pop transition not working as it should

I'm trying to create a custom horizontal slide UINavigationController transition. And the push animation seems to work fine. But when I'm trying to pop (and have that horizontal slide back) where's only a blank screen and after animation time the view appears where it should
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
let container = transitionContext.containerView
let fromView = transitionContext.view(forKey: UITransitionContextViewKey.from)!
let toView = transitionContext.view(forKey: UITransitionContextViewKey.to)!
let offScreenRight = CGAffineTransform(translationX: container.frame.width, y: 0)
let offScreenLeft = CGAffineTransform(translationX: -container.frame.width, y: 0)
fromView.frame = transitionContext.initialFrame(for: transitionContext.viewController(forKey: .from)!)
toView.frame = transitionContext.finalFrame(for: transitionContext.viewController(forKey: .to)!)
toView.transform = isPresenting == true ? offScreenRight : offScreenLeft
container.addSubview(toView)
container.addSubview(fromView)
let duration = self.transitionDuration(using: transitionContext)
UIView.animate(withDuration: duration, delay: 0.0, usingSpringWithDamping: 0.5, initialSpringVelocity: 0.8, options: [], animations: {
fromView.transform = self.isPresenting == true ? offScreenLeft : offScreenRight
toView.transform = .identity
}, completion: { finished in
toView.frame = transitionContext.initialFrame(for: transitionContext.viewController(forKey: .from)!)
transitionContext.completeTransition(true)
})
}
Looks like you need to reset any existing transforms on your views.
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
let container = transitionContext.containerView
let fromView = transitionContext.view(forKey: UITransitionContextViewKey.from)!
let toView = transitionContext.view(forKey: UITransitionContextViewKey.to)!
// add these two lines
fromView.transform = .identity
toView.transform = .identity
// ... the rest of your existing code

Rotate UIButton 360 degrees

I've been trying to run an animation that rotates my UIButton 360 degrees using this code:
UIView.animateWithDuration(3.0, animations: {
self.vineTimeCapButton.transform = CGAffineTransformMakeRotation(CGFloat(M_PI*2))
self.instagramTimeCapButton.transform = CGAffineTransformMakeRotation(CGFloat(M_PI*2))
})
However, it doesn't rotate 360 degrees because the UIButton is already at that location.
How can I rotate my UIButton 360 degrees?
You can use a trick: start rotating with 180 degrees first and then rotate with 360 degrees. Use 2 animations with delay.
Try this.
UIView.animate(withDuration: 0.5) {
self.view.transform = CGAffineTransform(rotationAngle: .pi)
}
UIView.animate(
withDuration: 0.5,
delay: 0.45,
options: UIView.AnimationOptions.curveEaseIn
) {
self.view.transform = CGAffineTransform(rotationAngle: 2 * .pi)
}
As discussed here, you can also use a CAAnimation. This code applies one full 360 rotation:
Swift 3
let fullRotation = CABasicAnimation(keyPath: "transform.rotation")
fullRotation.delegate = self
fullRotation.fromValue = NSNumber(floatLiteral: 0)
fullRotation.toValue = NSNumber(floatLiteral: Double(CGFloat.pi * 2))
fullRotation.duration = 0.5
fullRotation.repeatCount = 1
button.layer.add(fullRotation, forKey: "360")
You will need to import QuartzCore:
import QuartzCore
And your ViewController needs to conform to CAAnimationDelegate:
class ViewController: UIViewController, CAAnimationDelegate {
}
Swift 4: Animation nested closure is better than animation delay block.
UIView.animate(withDuration: 0.5, animations: {
button.transform = CGAffineTransform(rotationAngle: (CGFloat(Double.pi)))
}) { (isAnimationComplete) in
// Nested Block
UIView.animate(withDuration: 0.5) {
button.transform = CGAffineTransform(rotationAngle: (CGFloat(Double.pi * 2)))
}
}
Swift 4 version that allows you to rotate the same view infinite times:
UIView.animate(withDuration: 0.5) {
self.yourButton.transform = self.yourButton.transform.rotated(by: CGFloat.pi)
}
if you want to rotate 360°:
UIView.animate(withDuration: 0.5) {
self.yourButton.transform = self.yourButton.transform.rotated(by: CGFloat.pi)
self.yourButton.transform = self.yourButton.transform.rotated(by: CGFloat.pi)
}
in Swift 3 :
UIView.animate(withDuration:0.5, animations: { () -> Void in
button.transform = CGAffineTransform(rotationAngle: CGFloat(M_PI))
})
UIView.animate(withDuration: 0.5, delay: 0.45, options: .curveEaseIn, animations: { () -> Void in
button.transform = CGAffineTransform(rotationAngle: CGFloat(M_PI * 2))
}, completion: nil)
You can use a little extension based on CABasicAnimation (Swift 4.x):
extension UIButton {
func rotate360Degrees(duration: CFTimeInterval = 1.0, completionDelegate: AnyObject? = nil) {
let rotateAnimation = CABasicAnimation(keyPath: "transform.rotation")
rotateAnimation.fromValue = 0.0
rotateAnimation.toValue = CGFloat(.pi * 2.0)
rotateAnimation.duration = duration
if let delegate: AnyObject = completionDelegate {
rotateAnimation.delegate = delegate as? CAAnimationDelegate
}
self.layer.add(rotateAnimation, forKey: nil)
}
}
Usage:
For example we can start to make a simple button:
let button = UIButton()
button.frame = CGRect(x: self.view.frame.size.width/2, y: 150, width: 50, height: 50)
button.backgroundColor = UIColor.red
button.setTitle("Name your Button ", for: .normal)
button.addTarget(self, action: #selector(buttonAction), for: .touchUpInside)
self.view.addSubview(button)
Then we can build its selector:
#objc func buttonAction(sender: UIButton!) {
print("Button tapped")
sender.rotate360Degrees()
}
You can also try to implement an extension which will simplify things if you need to do it multiple time
extension UIView {
func rotate360Degrees(duration: CFTimeInterval = 1, repeatCount: Float = .infinity) {
let rotateAnimation = CABasicAnimation(keyPath: "transform.rotation")
rotateAnimation.fromValue = 0.0
rotateAnimation.toValue = CGFloat(Double.pi * 2)
rotateAnimation.isRemovedOnCompletion = false
rotateAnimation.duration = duration
rotateAnimation.repeatCount = repeatCount
layer.add(rotateAnimation, forKey: nil)
}
// Call this if using infinity animation
func stopRotation () {
layer.removeAllAnimations()
}
}
Swift 5. Try this, it worked for me
UIView.animate(withDuration: 3) {
self.yourButton.transform = CGAffineTransform(rotationAngle: .pi)
self.yourButton.transform = CGAffineTransform(rotationAngle: .pi * 2)
}

Resources