Pass function to completion handler - ios

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
}

Related

Swift - CoreAnimation with duration

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

Cannot convert value of type and my problem

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

swift 4.2 Cannot convert value of type '(_) -> Void' to expected argument type '(() -> Void)?'

==> swift 3 version in perfect work but swift 4 and swift 4.2 in now working.
static func animate(_ duration: TimeInterval,
animations: (() -> Void)!,
delay: TimeInterval = 0,
options: UIViewAnimationOptions = [],
withComplection completion: (() -> Void)! = {}) {
UIView.animate(
withDuration: duration,
delay: delay,
options: options,
animations: {
animations()
}, completion: { finished in
completion()
})
}
static func animateWithRepeatition(_ duration: TimeInterval,
animations: (() -> Void)!,
delay: TimeInterval = 0,
options: UIViewAnimationOptions = [],
withComplection completion: (() -> Void)! = {}) {
var optionsWithRepeatition = options
optionsWithRepeatition.insert([.autoreverse, .repeat])
self.animate(
duration,
animations: {
animations()
},
delay: delay,
options: optionsWithRepeatition,
withComplection: { finished in
completion()
})
}
Error display on xcode =>
Cannot convert value of type '(_) -> Void' to expected argument type
'(() -> Void)?'
You declared the animate function such that its completion parameter takes no input arguments. However, you are trying to call an input argument, finished in your closure when you call that function in animateWithRepetition. Just remove finished and your code compiles fine.
static func animateWithRepetition(_ duration: TimeInterval, animations: (() -> Void)!, delay: TimeInterval = 0, options: UIView.AnimationOptions = [], withComplection completion: (() -> Void)! = {}) {
var optionsWithRepetition = options
optionsWithRepeatition.insert([.autoreverse, .repeat])
self.animate(duration, animations: {
animations()
}, delay: delay, options: optionsWithRepeatition, withCompletion: {
completion()
})
}
P.S.: I've corrected the typos in your input argument names. Passing in an input argument of implicitly unwrapped type also doesn't make much sense. Either make animations a normal Optional and safely unwrap it or rather make it non-Optional if it should never be nil.
In your function declaratation:
static func animate(_ duration: TimeInterval,
animations: (() -> Void)!,
delay: TimeInterval = 0,
options: UIViewAnimationOptions = [],
withComplection completion: (() -> Void)! = {})
you have defined completion handler as (() -> Void)!, that is, it does not have any parameter.
But when you call this function:
self.animate(
duration,
animations: {
animations()
},
delay: delay,
options: optionsWithRepeatition,
withComplection: { finished in
completion()
})
you are giving the parameter finished in completion block. That's why its giving error.
Under Swift4 compiler assumed () -> Void as (Void) -> Void but since that its matters if there is an argument (even if it's Void)

How to set same speed to UIView animation

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

Swift cant resolve CurveEaseIn in UIView.animateWithDuration when using parameter from function inside animation block

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.

Resources