How can I repeat chained animation in Swift? - swift

I would like to animate the whole chain for forever.
UIView.animate(withDuration: 2.0, delay: 0.2, options: .curveEaseOut, animations: {
self.circleImageView.transform = CGAffineTransform(scaleX: 9, y: 9)
}) { (_) in
UIView.animate(withDuration: 2.0, delay: 0.2, options: .curveEaseIn, animations: {
self.circleImageView.transform = CGAffineTransform(scaleX: 0.01, y: 0.01)
}, completion: nil)
}

You can wrap the two animations within a keyframe animation. Then you can .repeat that:
UIView.animateKeyframes(withDuration: 4.4, delay: 0, options: .repeat, animations: {
UIView.addKeyframe(withRelativeStartTime: 0.0, relativeDuration: 2.0/4.4) {
self.circleImageView.transform = .init(scaleX: 9, y: 9)
}
UIView.addKeyframe(withRelativeStartTime: 0.5, relativeDuration: 2.0/4.4) {
self.circleImageView.transform = .init(scaleX: 0.01, y: 0.01)
}
})

Wrap it up inside a method:
func repeatingAnimation() {
UIView.animate(withDuration: 2.0, delay: 0.2, options: .curveEaseOut, animations: {
self.circleImageView.transform = CGAffineTransform(scaleX: 9, y: 9)
}) { (_) in
UIView.animate(withDuration: 2.0, delay: 0.2, options: .curveEaseIn, animations: {
self.circleImageView.transform = CGAffineTransform(scaleX: 0.01, y: 0.01)
}, completion: { [weak self] _ in
self?.repeatingAnimation()
})
}
}

Related

Touch Labels That Keep Animating

I'm having an issue trying to touch a label that keeps animating because during the animation it becomes an image (set the animation on repeat). Is there a way to touch it during animation (I cant't use UITapGestureRecognizer) ?
func animatePro() {
UIView.animate(withDuration: 2, delay: 0, options: [.autoreverse, .repeat], animations: {
//1.5 times it's normal size
self.proLabel.transform = CGAffineTransform(scaleX: 1.5, y: 1.5)
}){ (finished) in
self.proLabel
.transform = CGAffineTransform.identity
}
}
func animateRev() {
UIView.animate(withDuration: 2, delay: 0, options: [.repeat], animations: {
//1.5 times it's normal size
self.revLabel.transform = CGAffineTransform(scaleX: 1.5, y: 1.5)
}){ (finished) in
UIView.animate(withDuration: 2, delay: 0, animations: {
self.revLabel.transform = CGAffineTransform.identity
})
}
}
Add allowUserInteraction
options: [.allowUserInteraction,.autoreverse, .repeat]

Swift move image from right to left than from left to right

I want to make an animation that move a background image from right to left than left to right.
I use this code but not works
UIView.animate(withDuration: 5, delay: 0, options: .repeat ,animations: { () -> Void in
imageView.transform = CGAffineTransform(translationX: -imageView.frame.width + self.view.bounds.width, y: 0)
UIView.animate(withDuration: 5, delay: 1, options: .repeat ,animations: { () -> Void in
imageView.transform = CGAffineTransform(translationX: +imageView.frame.width - self.view.bounds.width, y: 0)
})
})
I solved with this
UIView.animate(withDuration: 30.0, delay: 0, options: [.repeat, .autoreverse], animations: {
imageView.transform = CGAffineTransform(translationX: -imageView.frame.width + self.view.bounds.width, y: 0)
}, completion: nil)
Use the following code if you want to use the legacy animate(withDuration:animations:) API. However, you need to wait for the first animation to be finished - this means you have to use the completion block.
However, your value for translationX does not makes sense as well.
UIView.animate(withDuration: 1, animations: {
self.imageView.transform = CGAffineTransform(translationX: -self.imageView.frame.width, y: 0)
}) { _ in
UIView.animate(withDuration: 1) {
self.imageView.transform = CGAffineTransform.identity
}
}
I would strongly recommend having a look at the "new" way of doing Animations: UIViewPropertyAnimator(https://developer.apple.com/documentation/uikit/uiviewpropertyanimator)

UIButton animation ignores target selector in Swift

I've added this animation code to the end of a function that is called when the button is pushed, but while the animation is happening, it ignores the set target selector until the animation is finished. The animation is very fast, but I'd like users to be able to press it rapidly.
let transforms: CGAffineTransform = .identity
mirroredButton.transform = transforms
UIView.animate(withDuration: 0.05, animations: {
mirroredButton.transform = transforms.scaledBy(x: 0.75, y: 0.75)
},
completion: { _ in
UIView.animate(withDuration: 0.1) {
mirroredButton.transform = transforms.scaledBy(x: 1.0, y: 1.0)
}
})
Update:
Using the answer, I updated my animation code as shown below. Both animation calls need options. The 2nd has a nil completion handler.
let transforms: CGAffineTransform = .identity
mirroredButton.transform = transforms
UIView.animate(withDuration: 0.05, delay: 0.0, options: .allowUserInteraction, animations: {
mirroredButton.transform = transforms.scaledBy(x: 0.75, y: 0.75)
},
completion: { _ in
UIView.animate(withDuration: 0.1, delay: 0.0, options: .allowUserInteraction, animations: {
mirroredButton.transform = transforms.scaledBy(x: 1.0, y: 1.0)
}, completion:nil)
})
User interaction is disabled for the duration of a view's animation. If it's critical that the user should interact with a view during an animation, you can pass in the option .allowUserInteraction, like so:
UIView.animate(withDuration: 1.0, delay: 0.0, options: .allowUserInteraction, animations: {
//animate here
})

Swift only call recursive function X times

I have a function I am using to animate 4 UILabels. The animation is chained and I am trying to reduce repeated code by creating a recursive function that accepts the label as an argument, on completion calls itself with the next label.
fileprivate func handleAnimations(firstLabel: UILabel, secondLabel: UILabel) -> Void {
UIView.animate(withDuration: 1, delay: 2, usingSpringWithDamping: 1, initialSpringVelocity: 1, options: .curveEaseInOut, animations: {
firstLabel.alpha = 1
}) { (_) in
UIView.animate(withDuration: 1, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 1, options: .curveEaseInOut, animations: {
secondLabel.alpha = 1
}) { (_) in
UIView.animate(withDuration: 0.5, delay: 3, usingSpringWithDamping: 0.5, initialSpringVelocity: 0.5, options: .curveEaseInOut, animations: {
firstLabel.alpha = 0
secondLabel.alpha = 0
}, completion: { (_) in
self.handleAnimations(firstLabel: self.introTextLabelThree, secondLabel: self.introTextLabelFour)
})
}
}
}
Is this the best way to achieve this? If so, how I prevent the final complete block calling itself over and over with the final 2 labels?
The effect I am trying to achieve is:
labelOne - fades in
labelTwo - fades in
labelOne and labelTwo - fade out
labelThree - fades in
labelFour - fades in
labelThree and labelFour - fade out
After labelThree and labelFour I would be looking to implement another function call.
Do not do this. Please use animateKeyframes. This will allow you to properly chain animations without the dreaded pyramid of doom.
UIView.animateKeyframes(withDuration: 5.0, delay: 0, options: [.calculationModeCubic], animations: {
// Add animations
UIView.addKeyframe(withRelativeStartTime: 0, relativeDuration: 1.0/6.0, animations: {
self.introTextLabel.alpha = 1
})
UIView.addKeyframe(withRelativeStartTime: 1.0/6.0, relativeDuration: 1.0/5.0, animations: {
self.introTextLabelTwo.alpha = 1
})
UIView.addKeyframe(withRelativeStartTime: 2.0/6.0, relativeDuration: 1.0/5.0, animations: {
self.introTextLabel.alpha = 0
self.introTextLabelTwo.alpha = 0
})
UIView.addKeyframe(withRelativeStartTime: 3.0/6.0, relativeDuration: 1.0/5.0, animations: {
self.introTextLabelThree.alpha = 1
})
UIView.addKeyframe(withRelativeStartTime: 4.0/6.0, relativeDuration: 1.0/5.0, animations: {
self.introTextLabelFour.alpha = 1
})
UIView.addKeyframe(withRelativeStartTime: 5.0/6.0, relativeDuration: 1.0/5.0, animations: {
self.introTextLabelThree.alpha = 0
self.introTextLabelFour.alpha = 0
})
}, completion:{ _ in
// fire off whatever other method you want here
})

Whats the Swift animate WithDuration syntax?

I'm porting an older app over to Xcode 7 beta and I'm getting an error on my animations:
Cannot invoke 'animateWithDuration' with an argument list of type
'(Double, delay: Double, options: nil, animations: () -> _,
completion: nil)'
Here's the code:
UIView.animateWithDuration(0.5, delay: 0.3, options: nil, animations: {
self.username.center.x += self.view.bounds.width
}, completion: nil)
This works in Xcode 6 so I'm assuming this is an update in Swift. So my question is:
What's the Swift 3 syntax for animateWithDuration?
Swift 3/4 Syntax
Here's an update with the Swift 3 Syntax:
UIView.animate(withDuration: 0.5, delay: 0.3, options: [.repeat, .curveEaseOut, .autoreverse], animations: {
self.username.center.x += self.view.bounds.width
}, completion: nil)
If you need to add a completion handler just add a closure like so:
UIView.animate(withDuration: 0.5, delay: 0.3, options: [.repeat, .curveEaseOut, .autoreverse], animations: {
// animation stuff
}, completion: { _ in
// do stuff once animation is complete
})
Old Answer:
It turns out it's a very simple fix, just change options: nil to options: [].
Swift 2.2 Syntax:
UIView.animateWithDuration(0.5, delay: 0.3, options: [], animations: {
self.username.center.x += self.view.bounds.width
}, completion: nil)
What changed?
Swift 2 got rid of the C-Style comma-delimited list of options in favor of option sets (see: OptionSetType). In my original question, I passed in nil for my options, which was valid prior to Swift 2. With the updated syntax, we now see an empty option list as an empty set: [].
An example of animateWithDuration with some options would be this:
UIView.animateWithDuration(0.5, delay: 0.3, options: [.Repeat, .CurveEaseOut, .Autoreverse], animations: {
self.username.center.x += self.view.bounds.width
}, completion: nil)
Swift 3, 4, 5
UIView.animate(withDuration: 1.5, delay: 0.05 * Double(index), usingSpringWithDamping: 0.8, initialSpringVelocity: 0, options: [], animations: {
cell.transform = CGAffineTransform(translationX: 0, y: 0)
}, completion: nil)
Swift 3 Syntax with completion block
UIView.animate(withDuration: 3.0 , delay: 0.25, options: .curveEaseOut, animations: {
// animation
}, completion: { _ in
// completion
})
Swift 2
UIView.animateWithDuration(1.0, delay: 0.1, options: [.Repeat, .CurveEaseOut, .Autoreverse], animations: {
// animation
}, completion: { finished in
// completion
})
Swift 3, 4, 5
UIView.animate(withDuration: 1.0, delay: 0.1, options: [.repeat, .curveEaseOut, .autoreverse], animations: {
// animation
}, completion: { finished in
// completion
})