There are so many posts similar to this that I have seen and none of them works.
Here is my code so far.
func startRotating(view : UIImageView, rotating : Bool) {
//Rotating the rotatingClockBarImage
UIView.animate(withDuration: 1.0, delay: 0.0, options: [.curveLinear], animations: {
view.transform = CGAffineTransform(rotationAngle: CGFloat.pi)
}, completion: {finished in
UIView.animate(withDuration: 1.0, delay: 0.0, options: [.curveLinear], animations: {
view.transform = CGAffineTransform(rotationAngle: 0)
}, completion: nil)
})//End of rotation
continueRotating(view: view)
}
The original problem would be that I couldn't rotate a full 360 degrees. I figured that out by rotating half way and the other half in the completion.
The problem now is once this animation finishes, that's it. I have tried putting it in a while loop, for loop, calling two similar functions back and forth. Nothing works it just keeps freezing my app.
In a for loop, for example, that would run 3 times, I put a print(). The print writes to the console three times but the animation only happens once. Because of this I am thinking the animation is just cutting itself off before it even starts, and the final rotation is the only one that plays through. So I need to find a way to allow it to play each rotation through.
This shouldn't be that hard seeing that Apple had their planes rotate so easily in a former version of Xcode in a game app. I'm trying to avoid deleting and reinstalling the old version just so I can look at that code.
Actually it would be more easy:
extension UIView {
func rotate360Degrees(duration: CFTimeInterval = 1.0) {
let rotateAnimation = CABasicAnimation(keyPath: "transform.rotation")
rotateAnimation.fromValue = 0.0
rotateAnimation.toValue = CGFloat.pi * 2
rotateAnimation.duration = duration
rotateAnimation.repeatCount = Float.infinity
self.layer.add(rotateAnimation, forKey: nil)
}
func stopRotating(){
self.layer.sublayers?.removeAll()
//or
self.layer.removeAllAnimations()
}
}
Then for rotating:
yourView.rotate360Degrees()
for stopping:
yourView. stopRotating()
Did you try calling startRotating again in the completion block of your second half turn ?
Note that you should do that conditionally with a "stop" flag of your own if you ver want it to stop.
Related
I've taken a look at the answers in the following StackOverflow question but none seem to work for me: on the execution of the completion block, instead of performing the animation again, the program spews out "complete" ad infinitum without animating the view at all.
How can I repeat animation (using UIViewPropertyAnimator) certain number of times?
This is my AnimatorFactory class:
class AnimatorFactory {
#discardableResult
static func rotateRepeat(view: UIView) -> UIViewPropertyAnimator {
let rotate = UIViewPropertyAnimator.runningPropertyAnimator(withDuration: 1.0, delay: 0.0, options: [.curveLinear], animations: {
view.transform = CGAffineTransform(rotationAngle: .pi)
}, completion: { _ in
print("complete")
self.rotateRepeat(view: view)
})
return rotate
}
}
It is called as you'd expect with AnimatorFactory.rotateRepeat(view: <someView>)
However, the problem as mentioned above occurs. What I'd expect is that the view would rotate repeatedly until some time that I decide to change or stop it; this is exactly the reason that I have chosen to use UIViewPropertyAnimator instead of UIView.animate(withDuration:animations).
What's the best way then to create interactive, repeatable UIView animations? Much appreciated.
Your code is working fine. The trouble is that your animation does nothing after the first time. You say:
view.transform = CGAffineTransform(rotationAngle: .pi)
The first time, we change the rotation from 0 to pi. That is a change, so there is animation. But after that we just keep saying “stay at pi” over and over. We are at pi and you say to stay there, so there is no change to animate.
What you want each animation to do is add pi, not be pi.
As #matt suggested, I was merely setting the rotation of the view to .pi over and over. So in the completion block I have now set the transform to .identity before kicking the animation off again.
class AnimatorFactory {
#discardableResult
static func rotateRepeat(view: UIView) -> UIViewPropertyAnimator {
let rotate = UIViewPropertyAnimator(duration: 1.0, curve: .linear)
rotate.addAnimations {
view.transform = CGAffineTransform(rotationAngle: .pi)
}
rotate.addCompletion{ _ in
view.transform = .identity
self.rotateRepeat(view: view)
}
rotate.startAnimation()
return rotate
}
}
I am trying to have my animation ease the screen from black to white to black again and repeat that a certain amount of times. Currently with the code I have the animation eases from black to white then jumps back to black. Is there anyway to run an animation in reverse or add an animation that runs after the first animation is completed?
override func viewDidAppear(_ animated: Bool) {
let viewColorAnimator: UIViewPropertyAnimator = UIViewPropertyAnimator.runningPropertyAnimator(
withDuration: 4.0,
delay: 0.0,
options: [.curveEaseInOut],
animations: {
UIView.setAnimationRepeatCount(3);
self.lightView.backgroundColor = .white
})
viewColorAnimator.startAnimation()
}
I tried adding this block of code to the project but the outcome was the same:
viewColorAnimator.addCompletion {_ in
let secondAnimator = UIViewPropertyAnimator(duration: 4.0, curve: .linear) {
self.lightView.backgroundColor = .black
}
secondAnimator.startAnimation()
}
EDIT: I've found out it is because of the setAnimationRepeatCount because the last of the iterations works properly. How do I run the animation multiple times without the repeat count?
Documentation says that the options related to direction are ignored. you can check the image attached here.
To achieve reverse animation:
Create an animator property.
private var animator: UIViewPropertyAnimator = {
let propertyAnimator = UIViewPropertyAnimator.runningPropertyAnimator(
withDuration: 4.0,
delay: 0.0,
options: [.curveEaseInOut, .autoreverse],
animations: {
UIView.setAnimationRepeatCount(3)
UIView.setAnimationRepeatAutoreverses(true)
self.lightView.backgroundColor = .white
})
return propertyAnimator
}()
P.S. we need to mention the .autoreverse in the options. Otherwise UIView.setAnimationRepeatAutoreverses(true) won't work.
There's an easy way to run animations. And for this method: if you want the animation to repeat after completing, you can add the .autoreverse, and the .repeat option. Like this:
UIView.animate(withDuration: TimeInterval(randomTime), delay: 0, options: [.repeat,.autoreverse], animations: {
//Animation code here
}, completion: {(bool)
//Do something after completion
})
You can put the codes you want to execute after the animation in the completion block.
And, you can use a Timer as a way to run the animation for certain times.
Xcode 9.4.1 (Swift 4.1.2) and Xcode 10 beta 3 (Swift 4.2):
Here's the way to do it using UIViewPropertyAnimator -- basically, we just add .repeat and .autoreverse to the options. You were 99% of the way there:
override func viewDidAppear(_ animated: Bool) {
let viewColorAnimator: UIViewPropertyAnimator = UIViewPropertyAnimator.runningPropertyAnimator(
withDuration: 4.0,
delay: 0.0,
options: [.curveEaseInOut, .repeat, .autoreverse],
animations: {
UIView.setAnimationRepeatCount(3)
self.lightView.backgroundColor = .white
})
viewColorAnimator.startAnimation()
}
When doing UIView.animateWithDuration, I would like to define a custom curve for the ease, as opposed to the default: .CurveEaseInOut, .CurveEaseIn, .CurveEaseOut, .CurveLinear.
This is an example ease that I want applied to UIView.animateWithDuration:
let ease = CAMediaTimingFunction(controlPoints: Float(0.8), Float(0.0), Float(0.2), Float(1.0))
I tried making my own UIViewAnimationCurve, but it seems it accepts only one Int.
I can apply the custom ease to a Core Animation, but I would like to have custom easing for UIView.animateWithDuration for simpler and optimized code. UIView.animateWithDuration is better for me as I won't have to define animations for each animated property and easier completion handlers, and to have all animation code in one function.
Here's my non-working code so far:
let customOptions = UIViewAnimationOptions(UInt((0 as NSNumber).integerValue << 50))
UIView.setAnimationCurve(UIViewAnimationCurve(rawValue: 5)!)
UIView.animateWithDuration(2, delay: 0, options: customOptions, animations: {
view.layer.position = toPosition
view.layer.bounds.size = toSize
}, completion: { finished in
println("completion")
})
That's because UIViewAnimationCurve is an enumeration - its basically human-readable representations of integer values used to determine what curve to use.
If you want to define your own curve, you need to use CA animations.
You can still do completion blocks and groups of animations. You can group multiple CA Animations into a CAAnimationGroup
let theAnimations = CAAnimationGroup()
theAnimations.animations = [positionAnimation, someOtherAnimation]
For completion, use a CATransaction.
CATransaction.begin()
CATransaction.setCompletionBlock { () -> Void in
// something?
}
// your animations go here
CATransaction.commit()
After a bunch of researches, the following code works for me.
Create an explicit core animation transaction, set your desired timing function.
Use eigher or both UIKit and CAAnimation to perform the changes.
let duration = 2.0
CATransaction.begin()
CATransaction.setAnimationDuration(duration)
CATransaction.setAnimationTimingFunction(CAMediaTimingFunction(controlPoints: 0.8, 0.0, 0.2, 1.0))
CATransaction.setCompletionBlock {
print("animation finished")
}
// View animation
UIView.animate(withDuration: duration) {
// E.g. view.center = toPosition
}
// Layer animation
view.layer.position = toPosition
view.layer.bounds.size = toSize
CATransaction.commit()
With iOS 10.0 you can use UIViewPropertyAnimator.
https://developer.apple.com/documentation/uikit/uiviewpropertyanimator
See https://developer.apple.com/videos/play/wwdc2016/216/
I'm spending my turkey day trying to construct a flip animation class that I have been struggling with for weeks.
The goal is this:
Flip two images repeatedly, quickly at first, then slow down and stop, landing on one of the two images that was chosen before the animation began.
Right now both images are in a container view, in a storyboard. Ive tried using transitionFromView transitionFlipFromTop and I think that could work, but at the time I was unable to get it to repeat.
I am in the process of reading the View Programming Guide, but its tough to connect the dots between it, and swift. Being new to programming in general does not help.
Here is where I'm at: I'm now using a CATransform to scale an image from zero height, to full height. Im thinking that if I can somehow chain two animations, the first one showing the first image scaling up, then back down to zero, then animate the second image doing the same thing, that should give me the first part. Then if I could somehow get those two animations to repeat, quickly at first, then slow down and stop.
It seems I need to know how to nest multiple animations, and then be able to apply an animation curve to the nested animation.
I planned on solving the part about landing on a particular image by having two of these nested animations, one of them would have an odd number of flips, the other an even number of flips. Depending on the desired final image, the appropriate animation gets called.
Right now, my current code is able to repeatedly scale an image from zero to full, a set number of times:
import UIKit
import QuartzCore
let FlipAnimatorStartTransform:CATransform3D = {
let rotationDegrees: CGFloat = -15.0
let rotationRadians: CGFloat = rotationDegrees * (CGFloat(M_PI)/180.0)
let offset = CGPointMake(-20, -20)
var startTransform = CATransform3DIdentity
startTransform = CATransform3DScale(CATransform3DMakeRotation(0, 0, 0, 0),
1, 0, 1);
return startTransform
}()
class FlipAnimator {
class func animate(view: UIView) {
let viewOne = view
let viewTwo = view
viewOne.layer.transform = FlipAnimatorStartTransform
viewTwo.layer.transform = FlipAnimatorStartTransform
UIView.animateWithDuration(0.5, delay: 0, options: .Repeat, {
UIView.setAnimationRepeatCount(7)
viewOne.layer.transform = CATransform3DIdentity
},nil)
}
}
Im going to keep chipping away at it. Any help, or ideas, or hints about a better way to go about it would be amazing.
Thanks.
I'm using this function to rotate image horizontally and to change the image during the transition.
private func flipImageView(imageView: UIImageView, toImage: UIImage, duration: NSTimeInterval, delay: NSTimeInterval = 0)
{
let t = duration / 2
UIView.animateWithDuration(t, delay: delay, options: .CurveEaseIn, animations: { () -> Void in
// Rotate view by 90 degrees
let p = CATransform3DMakeRotation(CGFloat(GLKMathDegreesToRadians(90)), 0.0, 1.0, 0.0)
imageView.layer.transform = p
}, completion: { (Bool) -> Void in
// New image
imageView.image = toImage
// Rotate view to initial position
// We have to start from 270 degrees otherwise the image will be flipped (mirrored) around Y axis
let p = CATransform3DMakeRotation(CGFloat(GLKMathDegreesToRadians(270)), 0.0, 1.0, 0.0)
imageView.layer.transform = p
UIView.animateWithDuration(t, delay: 0, options: .CurveEaseOut, animations: { () -> Void in
// Back to initial position
let p = CATransform3DMakeRotation(CGFloat(GLKMathDegreesToRadians(0)), 0.0, 1.0, 0.0)
imageView.layer.transform = p
}, completion: { (Bool) -> Void in
})
})
}
Don't forget to import GLKit.
This flip animation swift code appears little better:
let transition = CATransition()
transition.startProgress = 0;
transition.endProgress = 1.0;
transition.type = "flip";
transition.subtype = "fromRight";
transition.duration = 0.9;
transition.repeatCount = 2;
self.cardView.layer.addAnimation(transition, forKey: " ")
Other option is
#IBOutlet weak var cardView: UIView!
var back: UIImageView!
var front: UIImageView!
self.front = UIImageView(image: UIImage(named: "heads.png"))
self.back = UIImageView(image: UIImage(named: "tails.png"))
self.cardView.addSubview(self.back)
UIView.transitionFromView(self.back, toView: self.front, duration: 1, options: UIViewAnimationOptions.TransitionFlipFromRight , completion: nil)
Please check link
It seems I need to know how to nest multiple animations, and then be able to apply an animation curve to the nested animation.
I don't think so. I don't think you want/need to nest anything. You are not repeating an animation in this story; you are doing different animations each time (because the durations are to differ). This is a sequence of animations. So I think what you need to know is how to do either a keyframe animation or a grouped animation. That way you can predefine a series of animations, each one lasting a certain predefined duration, each one starting after the preceding durations are over.
And for that, I think you'll be happiest at the Core Animation level. (You can't make a grouped animation at the UIView animation level anyway.)
I am trying clear the data in a UITableView with some quick animation, I am using the below code, which works fine in iOS8.
However when I run it on iOS7 it only runs the block after completion , but not the first animation code (fade out), so the animation looks very bad, the tableview disappears suddenly and returns back with animation.
any idea what is wrong here? what is the problem with it in iOS7?
UIView.animateWithDuration (0.5, animations: {
self.tableView.transform = CGAffineTransformMakeScale(0.2, 0.2)
self.tableView.alpha = 0
}, completion: { (value: Bool) in
UIView.animateWithDuration (0.5, animations: {
self.tableView.reloadData()
self.tableView.transform = CGAffineTransformMakeScale(1.0, 1.0)
self.tableView.alpha = 1
println ("animation done")
})
})
Try removing UIView.animateWithDuration (0.5, animations: { block inside completion block and check