Trying to understand CoreAnimation and completion handlers.
Why does this code happen all at once, not over a 5.0 second period?
I am trying to get it to run 6 times with the 5 second duration each time for a total of 30 seconds and get "complete" to print every 5 seconds.
func animateBox() {
UIView.animate(withDuration: 5.0, delay: 0.0, options: [], animations: {
self.myBox.layer.borderColor = self.getRandom()
}, completion: {finished in
print("complete")
})
}
#objc func buttonTapped() {
for _ in 1...6 {
animateBox()
print("animate")
}
}
for _ in 1...6 { is executed instantly, so animateBox is called 6 times, right after another, in a split second.
What you want to do is call each animation block inside the completion handler (which is called when the animation completes) of the previous one. Something like this:
UIView.animate(withDuration: 5.0, delay: 0.0, options: [], animations: {
self.myBox.layer.borderColor = self.getRandom()
}, completion: { finished in
print("complete")
UIView.animate(withDuration: 5.0, delay: 0.0, options: [], animations: {
self.myBox.layer.borderColor = self.getRandom()
}, completion: { finished in
print("complete")
UIView.animate(withDuration: 5.0, delay: 0.0, options: [], animations: {
self.myBox.layer.borderColor = self.getRandom()
}, completion: { finished in
print("complete")
...
})
})
})
But this will result in a huge completion pyramid... instead, try using animateKeyframes:
let totalDuration = CGFloat(30)
let relativeIndividualDuration = CGFloat(1) / CGFloat(6)
UIView.animateKeyframes(withDuration: totalDuration, delay: 0, options: .calculationModeCubic, animations: {
UIView.addKeyframe(withRelativeStartTime: 0.0, relativeDuration: relativeIndividualDuration) {
self.myBox.layer.borderColor = self.getRandom()
}
UIView.addKeyframe(withRelativeStartTime: relativeIndividualDuration, relativeDuration: relativeIndividualDuration) {
self.myBox.layer.borderColor = self.getRandom()
}
UIView.addKeyframe(withRelativeStartTime: relativeIndividualDuration * 2, relativeDuration: relativeIndividualDuration) {
self.myBox.layer.borderColor = self.getRandom()
}
...
})
Another way you could sequence the animations is to use the delay parameter, and add a delay for each step after the first:
func animateBox(step: Int) {
let duration = 5.0
UIView.animate(withDuration: duration,
delay: duration * Double(step),
options: [],
animations: {
self.myBox.layer.borderColor = self.getRandom()
}, completion: {finished in
print("complete")
})
}
#objc func buttonTapped() {
for index in 0...5 {
animateBox(step: index)
print("animating step \(index)")
}
}
(Note that I changed your loop to run from 0...5 so delay =step * duration would start at 0.)
Related
I have code:
typealias animationBlock = () -> Void
func fade(withAnimation animations: animationBlock ) {
UIView.transition(with: blankView!, duration: TimeInterval(kAnimationDuration), options: .transitionCrossDissolve, animations: animations)
}
I tried a lot and can not what is wrong
When a closure parameter is optional, it becomes #escaping, so mark your animations parameter with #escaping:
typealias animationBlock = () -> Void
func fade(withAnimation animations: #escaping animationBlock) {
UIView.transition(with: blankView!,
duration: TimeInterval(kAnimationDuration),
options: .transitionCrossDissolve,
animations: animations)
}
I have a view that im moving using this
func r() {
movel.constant = self.view.bounds.width - self.move.bounds.width
move.setNeedsLayout()
UIView.animate(withDuration: time, delay: 0, options:[], animations: {
self.move.superview?.layoutIfNeeded()
}, completion: {(finished: Bool) -> Void in
if (finished == true){
self.timerand()
self.l()
}else{
self.view.layoutIfNeeded()
}
})
}
And I'm using these functions to pause and resume it
func movestop() {
pausedmovetime = self.move.layer.convertTime(CACurrentMediaTime(), from: nil)
self.move.layer.speed = 0.0
self.move.layer.timeOffset = pausedmovetime
}
func movestart() {
self.move.layer.speed = 1.0
self.move.layer.timeOffset = 0.0
self.move.layer.beginTime = 0.0
let timeSincePause = self.move.layer.convertTime(CACurrentMediaTime(), from: nil) - pausedmovetime
self.move.layer.beginTime = timeSincePause
}
The app works fine but when I go out of the app I call the movestop() but when I come back it is at its final position and the completion block doesnt get called
I have a function that performs an animation on a view. I would like to implement a completion handler for this function that would be called after the animation is complete.
In ViewController...
hudView.hide(animated: true, myCompletionHandler: {
// Animation is complete
})
In HudView class...
func hide(animated: Bool, myCompletionHandler: () -> Void) {
if animated {
transform = CGAffineTransform(scaleX: 0.7, y: 0.7)
UIView.animate(withDuration: 0.3, delay: 0, usingSpringWithDamping: 0.7, initialSpringVelocity: 0.5, options: [], animations: {
self.alpha = 0
self.transform = CGAffineTransform.identity
}, completion: nil) // I want to run 'myCompletionHandler' in this completion handler
}
}
I've tried a number of things but can't find the correct syntax:
}, completion: myCompletionHandler)
Passing non-escaping parameter 'myCompletionHandler' to function expecting an
#escaping closure
}, completion: myCompletionHandler())
Cannot convert value of type 'Void' to expected argument type '((Bool)
-> Void)?'
}, completion: { myCompletionHandler() })
Closure use of non-escaping parameter 'myCompletionHandler' may allow it to
escape
As a swift novice these error messages don't mean very much to me and I cant seem to find any examples of the correct way to do this.
What is the correct way to pass myCompletionHandler to the .animate completion handler?
If you want to pass your own closure as an input argument to UIView.animate, you need to match the types of the closures, so myCompletionHandler has to have a type of ((Bool) -> ())?, just like completion.
func hide(animated: Bool, myCompletionHandler: ((Bool) -> ())?) {
if animated {
transform = CGAffineTransform(scaleX: 0.7, y: 0.7)
UIView.animate(withDuration: 0.3, delay: 0, usingSpringWithDamping: 0.7, initialSpringVelocity: 0.5, options: [], animations: {
self.alpha = 0
self.transform = CGAffineTransform.identity
}, completion: myCompletionHandler) // I want to run 'myCompletionHandler' in this completion handler
}
}
This is how you can call it:
hudView.hide(animated: true, myCompletionHandler: { success in
//animation is complete
})
This is how to use completion in UIView.animate :
func hide(animated: Bool, myCompletionHandler: () -> Void) {
if animated {
transform = CGAffineTransform(scaleX: 0.7, y: 0.7)
UIView.animate(withDuration: 0.3, delay: 0, usingSpringWithDamping: 0.7, initialSpringVelocity: 0.5, options: [], animations: {
self.alpha = 0
self.transform = CGAffineTransform.identity
}, completion: { (success) in
myCompletionHandler()
})
}
}
You can create your function as,
func hide(_ animated:Bool, completionBlock:((Bool) -> Void)?){
}
And you can call it as,
self.hide(true) { (success) in
// callback here
}
I want to set speed to my UISlider, to make it move more smoothly.
Here is how I am trying to animate it :
UIView.animateWithDuration(6, delay: 0.0, options: UIViewAnimationOptions.TransitionCurlUp, animations: { () -> Void in
self.videoSlider.setValue(time, animated: true)
if self.videoSlider.value == self.videoSlider.maximumValue {
self.playerLayer.stop()
self.playBigButton.hidden = false
self.pauseButton.hidden = true
self.playButton.hidden = false
}
}, completion: { (finished: Bool) -> Void in
})
But it moves with a different speed on the beginning, middle and end.
Try giving your animation the option of .CurveLinear, like this:
UIView.animateWithDuration(6,
delay: 0.0,
options: [UIViewAnimationOptions.TransitionCurlUp, .CurveLinear],
animations: { () -> Void in
self.videoSlider.setValue(time, animated: true)
if self.videoSlider.value == self.videoSlider.maximumValue {
self.playerLayer.stop()
self.playBigButton.hidden = false
self.pauseButton.hidden = true
self.playButton.hidden = false
}
}, completion: { (finished: Bool) -> Void in
})
I am trying to pass parameter to function to change layout constraint in animation block dynamically.
This works:
func moveKeyboard (up: Bool, newMargin: Int)
{
UIView.animateWithDuration(0.2, delay: 0, options: .CurveEaseIn, animations: {
self.topMarginConstraint.constant=10;
}, completion: { finished in
println("Animation end!")
})
}
and this doesn't (i get error "Could not find member CurveEaseIn"):
func moveKeyboard (up: Bool, newMargin: Int)
{
UIView.animateWithDuration(0.2, delay: 0, options: .CurveEaseIn, animations: {
self.topMarginConstraint.constant=newMargin;
}, completion: { finished in
println("Animation end!")
})
}
How should i define my function to be able to use newMargin parameter inside animation block?
It's because, the "constant" is of type "CGFLoat" and you are passsing "Int":
func moveKeyboard (up: Bool, newMargin: CGFloat)
{
UIView.animateWithDuration(0.2, delay: 0, options: UIViewAnimationOptions.CurveEaseIn, animations: {
self.topMarginConstraint.constant = newMargin;
}, completion: { finished in
println("Animation end!")
})
}
Check this out it's working fine.