Circular UIView scale animation goes back to square sometimes - ios

I put a UIView on center of the screen with Autolayout. This UIView is a square sized to be 15% of screen width. Then on my controller I add the cornerRadius :
override func viewDidLayoutSubviews() {
circle.layer.cornerRadius = circle.frame.size.width / 2.0
}
Then when user click on a button, the circular view is scaling down with a first animation.
UIView.animateWithDuration(0.4, delay: 0.1, options: .CurveEaseIn, animations: { () -> Void in
self.circle.alpha = 0.0
self.circle.transform = CGAffineTransformMakeScale(0.01, 0.01)
}) { (finished) -> Void in
scaleUp()
}
private func scaleUp() {
UIView.animateWithDuration(0.5, delay: 0.0, options: .CurveEaseInOut, animations: { () -> Void in
self.circle.alpha = 1.0
self.circle.transform = CGAffineTransformIdentity
}) { (finished) -> Void in
}
}
Sometimes the scale down animation doesn't work properly. Before starting the cornerRadius is removed and my UIView become a square. But sometimes my animation works well and the circle scale down animation is OK.
Moreover the scale up animation seems to work well all the time.
I don't understand why the scale down animation isn't working all the time.
Any idea?
Thanks

When animating with Auto Layout enabled, it is usually a good idea to call layoutIfNeeded on your view before starting the animation and again in the loop.
override func viewDidLayoutSubviews() {
circle.layer.cornerRadius = circle.frame.size.width / 2.0
}
#IBAction func scaleDownUp(sender: AnyObject) {
self.circle.layoutIfNeeded()
UIView.animateWithDuration(0.4, delay: 0.1, options: .CurveEaseIn, animations: { () -> Void in
self.circle.alpha = 0.0
self.circle.transform = CGAffineTransformMakeScale(0.01, 0.01)
self.circle.layoutIfNeeded()
}) { (finished) -> Void in
self.scaleUp()
}
}
private func scaleUp() {
self.circle.layoutIfNeeded()
UIView.animateWithDuration(0.5, delay: 0.0, options: .CurveEaseInOut, animations: {
self.circle.alpha = 1.0
self.circle.transform = CGAffineTransformIdentity
self.circle.layoutIfNeeded()
}) { (finished) -> Void in
}
}

Try using bounds instead of frame in this method:
override func viewDidLayoutSubviews() {
circle.layer.cornerRadius = circle.frame.size.width / 2.0
}
It worked for me =)

Related

how to get infinite loop? [duplicate]

I have a UIImageView which i want to rotate 180 degrees, taking up 1 second, then i want to wait 1 second at this position, then rotate 180 degrees back to the original position taking up 1 second.
How do i accomplish this? I've tried a 100 approaches and it keeps snapping back instead of rotating back
EDIT: I forgot to add i need this to repeat indefinitely
All you need to do is to create a keyFrame animations. It is designed to chain multiple animations together, and in the first keyframe rotate your UIImageView subclass by PI, and in the second one transform it back to identity.
let rotateForwardAnimationDuration: TimeInterval = 1
let rotateBackAnimationDuration: TimeInterval = 1
let animationDuration: TimeInterval = rotateForwardAnimationDuration + rotateBackAnimationDuration
UIView.animateKeyframes(withDuration: animationDuration, delay: 0, options: [], animations: {
UIView.addKeyframe(withRelativeStartTime: 0, relativeDuration: rotateForwardAnimationDuration) {
self.imageView.transform = CGAffineTransform(rotationAngle: .pi)
}
UIView.addKeyframe(withRelativeStartTime: 0.5, relativeDuration: rotateBackAnimationDuration) {
self.imageView.transform = .identity
}
})
Outcome:
EDIT:
Here is an example how to make it run indefinitely. I suppose your image is in a viewController, and you hold some reference to your imageView.
So for example, on viewDidAppear, call a function what triggers the animation, and than in the completion block of the animation, just call the same function again.
class ViewController: UIViewController {
#IBOutlet weak var imageView: UIImageView!
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
self.runRotateAnimation()
}
func runRotateAnimation() {
let rotateForwardAnimationDuration: TimeInterval = 1
let rotateBackAnimationDuration: TimeInterval = 1
let animationDuration: TimeInterval = rotateForwardAnimationDuration + rotateBackAnimationDuration
UIView.animateKeyframes(withDuration: animationDuration, delay: 0, options: [], animations: {
UIView.addKeyframe(withRelativeStartTime: 0, relativeDuration: rotateForwardAnimationDuration) {
self.imageView.transform = CGAffineTransform(rotationAngle: .pi)
}
UIView.addKeyframe(withRelativeStartTime: 0.5, relativeDuration: rotateBackAnimationDuration) {
self.imageView.transform = .identity
}
}) { (isFinished) in
// To loop it continuosly, just call the same function from the completion block of the keyframe animation
self.runRotateAnimation()
}
}
}
You can perform the second animation in the completionHandler presented on UIView.animate
let duration = self.transitionDuration(using: transitionContext)
let firstAnimDuration = 0.5
UIView.animate(withDuration: firstAnimDuration, animations: {
/* Do here the first animation */
}) { (completed) in
let secondAnimDuration = 0.5
UIView.animate(withDuration: secondAnimDuration, animations: {
/* Do here the second animation */
})
}
Now you could have another problem.
If you rotate your view with the CGAffineTransform and for every animation you assign a new object of this type to your view.transform, you will lose the previous transform operation
So, according to this post: How to apply multiple transforms in Swift, you need to concat the transform operation
Example with 2 animation block
This is an example to made a rotation of 180 and returning back to origin after 1 sec:
let view = UIView.init(frame: CGRect.init(origin: self.view.center, size: CGSize.init(width: 100, height: 100)))
view.backgroundColor = UIColor.red
self.view.addSubview(view)
var transform = view.transform
transform = transform.rotated(by: 180)
UIView.animate(withDuration: 2, animations: {
view.transform = transform
}) { (completed) in
transform = CGAffineTransform.identity
UIView.animate(withDuration: 2, delay: 1, options: [], animations: {
view.transform = transform
}, completion: nil)
}
Example of .repeat animation and .autoreverse
The .animate method give you the ability to set some animation options. In particular the structure UIViewAnimationOptions contains:
.repeat, which repeat indefinitely your animation block
.autoreverse, which restore your view to the original status
With this in mind you could do this:
var transform = view.transform.rotated(by: 180)
UIView.animate(withDuration: 2, delay: 0, options: [.repeat, .autoreverse], animations: {
self.myView.transform = transform
})
But you need a delay between the two animations, so you need to do this trick:
Example of recursive animation and a delay of 1 sec
Just create a method inside your ViewController which animate your view. In the last completionHandler, just call the method to create a infinite loop.
Last you need to call the method on viewDidAppear to start the animation.
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
self.animation()
}
func animation() {
var transform = view.transform
transform = transform.rotated(by: 180)
UIView.animate(withDuration: 2, delay: 0, options: [], animations: {
self.myView.transform = transform
}) { bool in
transform = CGAffineTransform.identity
UIView.animate(withDuration: 2, delay: 1, options: [], animations: {
self.myView.transform = transform
}, completion: { bool in
self.animation()
})
}
}

Animation not stoping using UIView.animate

My code is using a simple loop animation that is not stopping after the 5 second duration. All I want to do is have a second button that stops the animation. As you can see in my code, circle.stopAnimating() has no effect.
class VET: UIViewController {
#IBOutlet weak var circle: UIImageView!
var duration = 5
#IBAction func start(_ sender: Any) {
UIView.animate(withDuration: TimeInterval(duration), delay: 0.5, options: [.repeat, .autoreverse, .curveEaseIn, .curveEaseOut], animations: { () -> Void in
let scale = CGAffineTransform(scaleX: 0.25, y: 0.25)
self.circle.transform = scale
print("animation")
}, completion: { _ in
//if finished { or (finished: Bool)
// if isAnimating {
if self.circle.isAnimating {
self.circle.stopAnimating()
print("is animating -- stop animating")
} else {
self.circle.startAnimating()
print("start animating")
}
})
}
#IBAction func stop() {
circle.stopAnimating()
}
startAnimating and stopAnimating for change images of imageview in some interval like GIF. Here you used animation block with repeat and the completion block will only get called when the animation is interrupted. For example it gets called when the app goes in the background and comes back to the foreground again.
To stop animation after some interval you have to call forcefully removeAllAnimations after that interval. Please refer following code:
#IBOutlet weak var circle: UIImageView!
var duration = 10
func start() {
UIView.animate(withDuration: TimeInterval(duration), delay: 0.5, options: [.repeat, .autoreverse, .curveEaseIn, .curveEaseOut], animations: { () -> Void in
let scale = CGAffineTransform(scaleX: 0.25, y: 0.25)
self.circle.transform = scale
DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(self.duration * 1000)) {
// Code
self.circle.layer.removeAllAnimations()
}
print("animation")
}, completion: { _ in
let scale = CGAffineTransform(scaleX: 1.0, y: 1.0)
self.circle.transform = scale
})
}
You are using wrong approach to use animations if you need more control over it.
Instead use UIViewPropertyAnimator
The alternate property animator initializer gives you back an animator object that you need to start:
let animator = UIViewPropertyAnimator(duration: duration, curve: .linear) {
self.redSquare.backgroundColor = .green
}
animator.startAnimation()
Similarly you can use for stopping animations related to the animator
animator.stopAnimation()

Swift skips one animation (UIView.animate)

I have a logoImage on a view, animating the constraints.
The constraints animated are width and height ; going from original state of 220.0 to 240.0 (works fine) and THEN I would like to make it go back to 220.0 but the animation is skipped and the image goes directly back in 0.1second to 220.0.
Here is the code of the animation
func animate() {
self.imageHeightConstraint.constant = 240.0
self.imageWidthConstraint.constant = 240.0
UIView.animate(withDuration: 1.0,
delay:0.0,
options: .curveEaseIn,
animations:{
self.logoImage.layoutIfNeeded()
}, completion: { (finished: Bool) -> Void in
self.imageHeightConstraint.constant = 220.0
self.imageWidthConstraint.constant = 220.0
UIView.animate(withDuration: 2.0,
delay:0.0,
options: .curveEaseIn,
animations: {
self.logoImage.layoutIfNeeded()
})
})
}
Thanks in advance for any help I could get! ;-)
You need to call self.logoImage.setNeedsLayout() within your completion block, before you re-set the constants. This since you need to invalidate the current layout of the receiver and trigger a layout update during the next update cycle.
Try this code instead and it will work for you:
func animate() {
self.imageHeightConstraint.constant = 240.0
self.imageWidthConstraint.constant = 240.0
UIView.animate(withDuration: 1.0,
delay:0.0,
options: .curveEaseIn,
animations:{
self.logoImage.layoutIfNeeded()
}, completion: { (finished: Bool) -> Void in
self.logoImage.setNeedsLayout()
self.imageHeightConstraint.constant = 220.0
self.imageWidthConstraint.constant = 220.0
UIView.animate(withDuration: 2.0,
delay:0.0,
options: .curveEaseIn,
animations: {
self.logoImage.layoutIfNeeded()
})
})
}

hide/show uiimageview with animation when clicking button

how can I implement that when I click on button, my Image moves out of the screen with animation and when I click again on the button , comes back to its place?
On the button click event, simply animate the x coordinate of origin of the imageView according to your requirement.
Example:
#IBAction func onTapButton(_ sender: UIButton)
{
if sender.isSelected
{
UIView.animate(withDuration: 2.0, animations: {
self.imageView.frame.origin.x = 0.0
})
}
else
{
UIView.animate(withDuration: 2.0, animations: {
self.imageView.frame.origin.x = UIScreen.main.bounds.width
})
}
sender.isSelected = !sender.isSelected
}
The above code will work as follows:
when selecting button, imageView's x coordinate of origin is moved to extreme right of the screen (UIScreen.main.bounds.width)
when de-selecting button, imageView's x coordinate of origin is moved to extreme left of the screen (0.0)
Use this code to move left-
func moveToLeft()
{
var frame = imageView.frame
frame.origin.x = -imageView.frame.size.width //Adjust this value according to your need
UIView.animate(withDuration: 0.3, delay: 0.0, options: .curveEaseOut, animations: {() -> Void in
self.imageView.frame = frame
}, completion: {(_ finished: Bool) -> Void in
/*done*/
})
}
Use this code to move right-
func moveToRight()
{
var frame = imageView.frame
frame.origin.x = 0 //Adjust this value according to your need
UIView.animate(withDuration: 0.3, delay: 0.0, options: .curveEaseOut, animations: {() -> Void in
self.imageView.frame = frame
}, completion: {(_ finished: Bool) -> Void in
/*done*/
})
}

Swift Continuous Rotation Animation not so continuous

Here is my code. Intent is to continuously rotate the UIImageView named swirls[l]. However, there is a small pause between every rotation start/end. I have gone through every single animation tutorial but cant figure out what the mistake is?
let fullRotation = CGFloat(M_PI * 2)
let duration = 2.0
let delay = 0.0
let options = UIViewKeyframeAnimationOptions.Repeat | UIViewKeyframeAnimationOptions.CalculationModeLinear
UIView.animateKeyframesWithDuration(duration, delay: delay, options: options, animations: {
UIView.addKeyframeWithRelativeStartTime(0, relativeDuration: 1/3, animations: {
swirls[l].transform = CGAffineTransformMakeRotation(1/3 * fullRotation)
})
UIView.addKeyframeWithRelativeStartTime(1/3, relativeDuration: 1/3, animations: {
swirls[l].transform = CGAffineTransformMakeRotation(2/3 * fullRotation)
})
UIView.addKeyframeWithRelativeStartTime(2/3, relativeDuration: 1/3, animations: {
swirls[l].transform = CGAffineTransformMakeRotation(3/3 * fullRotation)
})
}, completion: {finished in
})
EDIT: I see that it has been suggested that a previous solution is available, but it simply does not work for continuous uninterrupted rotation. The only trick that worked for me is the answer that I chose below. Thanks
Try below extension for swift 4.
extension UIView {
func rotate360Degrees(duration: CFTimeInterval = 3) {
let rotateAnimation = CABasicAnimation(keyPath: "transform.rotation")
rotateAnimation.fromValue = 0.0
rotateAnimation.toValue = CGFloat(Double.pi * 2)
rotateAnimation.isRemovedOnCompletion = false
rotateAnimation.duration = duration
rotateAnimation.repeatCount=Float.infinity
self.layer.add(rotateAnimation, forKey: nil)
}
}
For start rotation.
MyView.rotate360Degrees()
And for Stop.
MyView.layer.removeAllAnimations()
You can use UIButton, UILabel and many more.
I'm not sure what's wrong with your code, but I've implemented continuous rotation using this method,
#IBAction func rotateView(sender: UIButton) {
UIView.animate(withDuration: 0.5, delay: 0, options: .curveLinear, animations: { () -> Void in
self.spinningView.transform = self.spinningView.transform.rotated(by: .pi / 2)
}) { (finished) -> Void in
self.rotateView(sender: sender)
}
}
If you want to continuous rotate Button or View and stop that rotation whenever you want then you can try this:
For start rotation:
#IBOutlet weak var RotateBtn: UIButton!
var timeTimer: Timer?
viewDidLoad()
{
self.rotateView()
timeTimer = Timer.scheduledTimer(timeInterval: 0.5, target: self, selector: #selector(self.rotateView), userInfo: nil, repeats: true)
}
func rotateView()
{
UIView.animate(withDuration: 0.5, delay: 0, options: .curveLinear, animations: { () -> Void in
self.RotateBtn.transform = self.RotateBtn.transform.rotated(by: CGFloat(M_PI_4))
})
}
If you want to stop rotation then use:
#IBAction func StopBtn_Pressed(_ sender: AnyObject)
{
timeTimer?.invalidate()
self.RecordBtn.layer.removeAllAnimations()
}

Resources