I have a UIButton that I have placed in the center of the screen using Interface Builder.
I want to animate the UIButton to move up and down and repeat the animation forever.
So far I have this in my didMoveToView:
UIView.animateWithDuration(3, animations: { () -> Void in
self.playBtn.transform = CGAffineTransformMakeTranslation(0, 10)
self.playBtn.transform = CGAffineTransformMakeTranslation(0, -10)
self.playBtn.transform = CGAffineTransformMakeTranslation(0, -10)
self.playBtn.transform = CGAffineTransformMakeTranslation(0, 10)
UIView.setAnimationRepeatCount(-1)
})
However it only runs the first line and moves the button down 10.
You can't change the transform to multiple things simultaneously. I would expect your code to cause no animation at all, as changing the transform more than once would cancel the animation.
Another problem with your code is that what you're saying is not how you ask for a repeating animation.
Still another problem is that you won't ever bring the button back to its starting place; the transforms are not additive.
What you need to do is chain animations together. This will be easiest if you drop down to Core Animation and make a grouped animation.
Or can do it with view animation by doing a keyframe animation, perhaps; this is not identical to what you want, but it will get you started:
Swift 2
let opts = UIViewKeyframeAnimationOptions.Repeat
UIView.animateKeyframesWithDuration(3, delay: 0, options: opts, animations: {
UIView.addKeyframeWithRelativeStartTime(0, relativeDuration: 0.5, animations: {
self.playBtn.transform = CGAffineTransformMakeTranslation(0, 10)
})
UIView.addKeyframeWithRelativeStartTime(0.5, relativeDuration: 0.5, animations: {
self.playBtn.transform = CGAffineTransformIdentity
})
}, completion: nil)
Swift 3,4,5
let opts = UIView.KeyframeAnimationOptions.repeat
UIView.animateKeyframes(withDuration: 3, delay: 0, options: opts, animations: {
UIView.addKeyframe(withRelativeStartTime: 0, relativeDuration: 0.5, animations: {
self.playBtn.transform = CGAffineTransform(translationX: 0, y: 10)
})
UIView.addKeyframe(withRelativeStartTime: 0.5, relativeDuration: 0.5, animations: {
self.playBtn.transform = CGAffineTransform.identity
})
}, completion: nil)
But really, it would be better if you learned how animation actually works before you get into this kind of thing. From your code, it appears to me you are just thrashing.
Related
I'm either being stupid, or misunderstanding how keyframe animations work on iOS (or both!). The two animation blocks below produce different results but I would expect them to be the same:
let duration: TimeInterval = 2
UIView.animateKeyframes(withDuration: duration, delay: 0, animations: {
UIView.addKeyframe(withRelativeStartTime: 0.9, relativeDuration: 0.1, animations: {
self.someView.transform = CGAffineTransform(translationX: 0, y: 150)
})
})
UIView.animateKeyframes(withDuration: duration * 0.1, delay: duration * 0.9, animations: {
UIView.addKeyframe(withRelativeStartTime: 0, relativeDuration: 1, animations: {
self.someView.transform = CGAffineTransform(translationX: 0, y: 150)
})
})
Can anyone help me understand why these are different when executed? The first seems to do what I expect, but the second one seems to execute the animation earlier than expected.
You're seeing the effects of the default timing curve on your keyframe animations. The first animation is the last 0.2 seconds of a 2 second animation, and the second animation is the entirety of a 0.2 second animation. The default ease-in-ease-out means that the first animation will be done entirely in the ease-out portion of the curve.
To force a linear curve on both animations you can wrap them inside another animation and set a couple of options:
UIView.animate(withDuration: duration, delay: 0, options: [.curveLinear], animations: {
UIView.animateKeyframes(withDuration: duration, delay: 0, options: [.overrideInheritedDuration, .calculationModeLinear], animations: {
UIView.addKeyframe(withRelativeStartTime: 0.9, relativeDuration: 0.1, animations: {
view1.transform = CGAffineTransform(translationX: 0, y: 150)
})
})
UIView.animateKeyframes(withDuration: duration * 0.1, delay: duration * 0.9, options: [.overrideInheritedDuration, .calculationModeLinear], animations: {
UIView.addKeyframe(withRelativeStartTime: 0, relativeDuration: 1, animations: {
view2.transform = CGAffineTransform(translationX: 0, y: 150)
})
})
}, completion: nil)
Which I think you'll agree is horrible-looking code, but I'm assuming this is an intellectual exercise :)
Currently there are 2 CGAffineTransform key frames added to the UIView.animateKeyframes. For some strange reason the second keyframe's animation doesn't appear to happen. It just jumps right back to the starting position after the animation completes.
UIView.animateKeyframes(withDuration: 3, delay: 0, options: [], animations: {
UIView.addKeyframe(withRelativeStartTime: 0.0, relativeDuration: 1, animations: {
let scaledBy = CGAffineTransform(scaleX: 1.0, y: 1.0)
self.firstView.transform = scaledBy.translatedBy(x: 120, y: 20).rotated(by: CGFloat.pi/2)
})
UIView.addKeyframe(withRelativeStartTime: 1, relativeDuration: 1, animations: {
let scaledBy = CGAffineTransform(scaleX: 1.5, y: 1.5)
self.firstView.transform = scaledBy.translatedBy(x: 40, y: 60).rotated(by: -CGFloat.pi/2)
self.firstView.transform = CGAffineTransform(rotationAngle: -CGFloat.pi/2)
})
}, completion: {_ in})
It seems like this is very old question. I hope my answer will be helpful though. The issue here is because you didn't get the meaning of relativeStartTime and relativeDuration parameters. The key word here is relative. It means that all your inner animations, added via UIView.addKeyframe function should happen between 0 and 1.
Because this call will create a kind of timeline
UIView.animateKeyframes(withDuration: 3, delay: 0, options: [], animations: {
}, completion: {_ in})
This timeline runs from 0 to 1 (0% to 100%). Which means that relative duration of any animation can't be more than 1 and relative start time also can't be more than 1.
I'm trying to create an animation where an object moves from one keypoint to the next smoothly. As of right now the object does move to the different key points but instead of transitioning to each one as soon as it finishes one animation it automatically jumps to the next keypoint but with no transition animation in between. This is the function I'm using:
func keyframeAnimate () {
UIView.animateKeyframes(withDuration: 3, delay: 0, options: [], animations: {
UIView.addKeyframe(withRelativeStartTime: 0, relativeDuration: 1.0, animations: {
let translation = CGAffineTransform(translationX: 150, y: 150)
self.square.alpha = 0.5
self.square.transform = translation.rotated(by: CGFloat.pi).scaledBy(x: 2, y: 2)
})
UIView.addKeyframe(withRelativeStartTime: 1.01, relativeDuration: 1.0, animations: {
let translation = CGAffineTransform(translationX: 100, y: 400)
self.square.alpha = 1
self.square.transform = translation.rotated(by: -CGFloat.pi).scaledBy(x: 1, y: 1)
})
})
}
Any help is appreciated!
Your problem is in the relative start and relative duration. As the documentation explains, these parameters should be values between 0 and 1 because these are fractions of the entire animateKeyframes(withDuration: duration
just quick inquiry in regards to implementing an issue I'm having with animating a UIImageView. I successfully implemented animating an image to slide off screen; but i want it to reappear when it exists the view to simulate a side-scroller game animation.
Something like this:
I've tried implementing a completion handler but struggled understanding the logic of how to implement it, so I removed my attempts; so my code is left as follows:
let oldCenter = background.center
let newCenter = CGPoint(x: oldCenter.x - 400, y: oldCenter.y)
UIView.animate(withDuration: 2, delay: 0, options: .curveLinear, animations: {
self.background.center = newCenter
}) { (success: Bool) in
print("Done moving image")
}
An example or pointers on how to achieved my desired animation would be appreciated!
let oldCenter = view.center
let newCenter = CGPoint(x: oldCenter.x - 400, y: oldCenter.y)
UIView.animate(withDuration: 2.0, delay: 0.0, options:
[.curveLinear, .repeat], animations: {
view.center = newCenter
}, completion: nil)
If you want to repeat the animation,
there is no need to implement the completion.
animate(withDuration:delay:options:animations:completion:) has options parameter (UIViewAnimationOptions), which is:
A mask of options indicating how you want to perform the animations.
For a list of valid constants, see UIViewAnimationOptions.
One of the constants for the UIViewAnimationOptions is repeat:
Repeat the animation indefinitely.
So, what you should do:
UIView.animateKeyframes(withDuration: 2.0, delay: 0.0, options: [.curveLinear, .repeat], animations: {
self.background.center = newCenter
}, completion: nil)
Again, implementing completion is not required for repeating the animation, implementing it is up to your case.
You can try UIView UIView repeat option
let oldCenter = background.center
let newCenter = CGPoint(x: oldCenter.x - 400, y: oldCenter.y)
UIView.animate(withDuration: 2, delay: 0, options: [.curveLinear, repeat], animations: {
self.background.center = newCenter
}) { (success: Bool) in
print("Done moving image")
if(success)
{
self.background.center = oldCenter
}
}
I'm trying to add impact feedback to a custom view controller presentation. The animation is implemented in key frames, and has a bounce at the end so I can't perform the impact in presentationTransitionDidEnd. My key frames look something like:
UIView.animateKeyframes(withDuration: 0.6, delay: 0, options: [], animations: {
UIView.addKeyframe(withRelativeStartTime: 0, relativeDuration: 0.5, animations: {
// View controller hits its full size; I'd like feedback at the end of this
}
UIView.addKeyframe(withRelativeStartTime: 0.5, relativeDuration: 0.25, animations: {
// Expand for bounce
}
UIView.addKeyframe(withRelativeStartTime: 0.75, relativeDuration: 0.25, animations: {
// Back to normal size
}
}, completion: { ... })
Is there a way to run a block of code at a specific point in a key frame animation? Placing it right inside the block doesn't work.