UIViewPropertyAnimator repeat - ios

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
}
}

Related

I need to infinitely rotate a view until told to stop

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.

old swift code is not working (animating a UIView)

i have found this code which is responsible for animating a UIView but unfortunately the code does not work and i can not figure the reason (maybe an older version of swift)
this is the code :
(this is helper function according to the creator)
func moveView(#view:UIView, toPoint destination:CGPoint, completion☹()->())?) {
//Always animate on main thread
dispatch_async(dispatch_get_main_queue(), { () Void in
//Use UIView animation API
UIView.animateWithDuration(1.0, delay: 0.0, usingSpringWithDamping:
0.6, initialSpringVelocity: 0.3, options:
UIViewAnimationOptions.AllowAnimatedContent, animations: { () ->
Void in
//do actual move
view.center = destination
}, completion: { (complete) -> Void in
//when animation completes, activate block if not nil
if complete {
if let c = completion {
c()
}
}
})
})
}
and this is the animation
//Create your face object (Just a UIImageView with a face as the image
var face = Face();
face.frame = CGRect(x: 0, y: 0, width: 50, height: 50)
//find our trajectory points
var center = CGPointMake(self.view.frame.size.width/2, self.view.frame.size.height/2);
var left = CGPointMake(center.x *-0.3, center.y)
var right = CGPointMake(center.x *2.2, center.y)
//place our view off screen
face.center = right
self.view.addSubview(face)
//move to center
moveView(view: face, toPoint: center) { () -> () in
//Do your Pop
face.pop()
// Move to left
moveView(view: face, toPoint: left, completion: { () -> () in
}
}
and i quote from the creator of the code
General Steps: Create a new face on the right edge of the screen. Make
the face visible. Move the face to the middle of the screen. Pop the
face Start the process with the next face. Move the first face to the
left as soon as the new face gets to the middle.
Actual slide animation Once again, we will do the following here: Move
view off screen on the right Move to center Pop Move to left
To get the repeating effect, just call this method on a timer
and a summary :
UIView’s animation API is very powerful. Both the pop and movement
animations use depend on this API. If you’re stuck with trying to
create an animation, UIView animation block is usually a good place to
start.
NOTE : im a beginner in IOS development if anyone can please explain the code for me
Indeed this moveView method had a few issues, one being it was written for Swift 1 (but there were also some typos, faulty characters and useless operations).
Here's the fixed version:
func moveView(view view:UIView, toPoint destination: CGPoint, afterAnim: ()->()) {
//Always animate on main thread
dispatch_async(dispatch_get_main_queue(), { () -> Void in
//Use UIView animation API
UIView.animateWithDuration(1.0, delay: 0.0, usingSpringWithDamping:
0.6, initialSpringVelocity: 0.3, options:
UIViewAnimationOptions.AllowAnimatedContent, animations: { () -> Void in
//do actual move
view.center = destination
}, completion: { (complete) -> Void in
//if and when animation completes, callback
if complete {
afterAnim()
}
})
})
}
You can use it like this now:
moveView(view: face, toPoint: center) {
//Do your Pop
face.pop()
// Move to left
moveView(view: face, toPoint: left) {
// Do stuff when the move is finished
}
}
Observe the differences between your version and mine to understand what was obsolete/wrong and how I fixed it. I'll help if you're stuck.

How to access radius dynamically during animation? (possible during CGAffineTransform)

I'm using two simple CGAffineTransforms to scale a circular UIView up and back down again, but I'd like to be able to dynamically access the radius in touchesBegan() while the animation is happening and check if it equals a certain value at that point.
The difficulty is that I need to check the radius while the animation is still happening. I'm not completely sold on the CGAffineTransforms approach, so I'm more than happy to animate the circle in another way if it would mean being able to access the radius. Thank you!
I'm at a loss on how to do that. Here is my code below (tips welcome!):
func grow() {
UIView.animateWithDuration(2.3, delay: 0.0,
options: UIViewAnimationOptions.CurveEaseIn,
animations: {
self.circle2.transform = CGAffineTransformMakeScale(1.5, 1.5)
},
completion: ({ finished in
if (finished) {
self.shrink()
}
}))
}
func shrink() {
UIView.animateWithDuration(2.3, delay: 0.0,
options: UIViewAnimationOptions.CurveEaseInOut,
animations: {
self.circle2.transform = CGAffineTransformMakeScale(0.5, 0.5)
},
completion: ({ finished in
if (finished) {
self.grow()
}
}))
}
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
/*
if radius == certain value
{
//do something
}
*/
}
First, I would recommend doing this animation using a plain CABasicAnimation or CAKeyframeAnimation, because you can use anim.repeatCount = .infinity rather than completion handlers that call each other back and forth.
The current state of the animation can be accessed by circle2.layer.presentationLayer. As mentioned in this question, you can use its frame which will take the transform into account.

Custom ease in animateWithDuration?

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/

How do you create a Swift animation class that flips two images

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.)

Resources