UIView animation options using Swift - uiviewanimation

How do I set the UIViewAnimationOptions to .Repeat in an UIView animation block:
UIView.animateWithDuration(0.2, delay:0.2 , options: UIViewAnimationOptions, animations: (() -> Void), completion: (Bool) -> Void)?)

Swift 3
Pretty much the same as before:
UIView.animate(withDuration: 0.2, delay: 0.2, options: UIViewAnimationOptions.repeat, animations: {}, completion: nil)
except that you can leave out the full type:
UIView.animate(withDuration: 0.2, delay: 0.2, options: .repeat, animations: {}, completion: nil)
and you can still combine options:
UIView.animate(withDuration: 0.2, delay: 0.2, options: [.repeat, .curveEaseInOut], animations: {}, completion: nil)
Swift 2
UIView.animateWithDuration(0.2, delay: 0.2, options: UIViewAnimationOptions.Repeat, animations: {}, completion: nil)
UIView.animateWithDuration(0.2, delay: 0.2, options: .Repeat, animations: {}, completion: nil)
UIView.animateWithDuration(0.2, delay: 0.2, options: [.Repeat, .CurveEaseInOut], animations: {}, completion: nil)

Most of the Cocoa Touch 'option' sets that were enums prior to Swift 2.0 have now been changed to structs, UIViewAnimationOptions being one of them.
Whereas UIViewAnimationOptions.Repeat would previously have been defined as:
(semi-pseudo code)
enum UIViewAnimationOptions {
case Repeat
}
It is now defined as:
struct UIViewAnimationOption {
static var Repeat: UIViewAnimationOption
}
Point being, in order to achieve what was achieved prior using bitmasks (.Reverse | .CurveEaseInOut) you'll now need to place the options in an array, either directly after the options parameter, or defined in a variable prior to utilising it:
UIView.animateWithDuration(0.2, delay: 0.2, options: [.Repeat, .CurveEaseInOut], animations: {}, completion: nil)
or
let options: UIViewAnimationOptions = [.Repeat, .CurveEaseInOut]
UIView.animateWithDuration(0.2, delay: 0.2, options: options, animations: {}, completion: nil)
Please refer to the following answer from user #0x7fffffff for more information: Swift 2.0 - Binary Operator “|” cannot be applied to two UIUserNotificationType operands

Swift 5
UIView.animate(withDuration: 0.2, delay: 0, options: UIView.AnimationOptions.curveEaseInOut, animations: {}, completion: nil)

Related

How to hide the tab bar with animation in iOS 13?

In my tabBarController, I use these two methods to hide/show the tabBar:
func showTabBar() {
tabBar.isHidden = false
UIView.animate(withDuration: 0.3) {
self.tabBar.transform = .identity
}
}
func hideTabBar() {
UIView.animate(withDuration: 0.3, animations: {
self.tabBar.transform = CGAffineTransform(translationX: 0, y: self.tabBar.frame.height)
}, completion: { _ in
self.tabBar.isHidden = true
})
}
This has worked well in iOS 12 and below, but in iOS 13, when the transforms are applied, the whole tabBar is broken (see picture). I know that you can hide the tabBar with frame animations, but applying transforms is much easier and I don't understand why this is broken and what changed in iOS 13 that prevents this from working.
'transform' property of tabbar is not working in iOS 13 for now, maybe it'll work later.
while hiding tabbar you just need to hold the last frame of your tabbar.
To show tabbar: -
func showTabbar(_ tabBarController: UITabBarController?, _ lastTabBarFrame: CGRect?) {
if #available(iOS 13, *) {
UIView.animate(withDuration: 0.3, animations: {
tabBarController?.tabBar.frame = lastTabBarFrame ?? .zero
}, completion: { (_) in
})
} else {
UIView.animate(withDuration: 0.3, animations: {
tabBarController?.tabBar.transform = .identity
}, completion: { (_) in
})
}
}
To hide tabbar: -
func hideTabbar(_ tabBarController: UITabBarController?, _ lastTabBarFrame: CGRect?, _ view: UIView) {
if #available(iOS 13, *) {
UIView.animate(withDuration: 0.3, animations: {
tabBarController?.tabBar.frame = CGRect(x: 0, y: view.bounds.height + (lastTabBarFrame?.height ?? 0.0) + 20, width: lastTabBarFrame?.width ?? 0.0, height: lastTabBarFrame?.height ?? 0.0)
}) { (_) in
}
} else {
UIView.animate(withDuration: 0.3, animations: {
tabBarController?.tabBar.transform = CGAffineTransform(translationX: 0, y: tabBarController?.tabBar.frame.height ?? 0.0)
}) { (_) in
}
}
}
How to use: -
var lastTabBarFrame: CGRect?
self.lastTabBarFrame = self.tabBarController?.tabBar.frame
hideTabbar(self.tabBarController, self.lastTabBarFrame, self.view)
showTabbar(self.tabBarController, self.lastTabBarFrame)

UIButton touchUp get swallowed

touching a UIButton rapidly does not deliver all touches up
to the receiver.
Is there any recourse?
iOS 12 in case that matters. both real device and simulator
I was animating touches in the handler for that touch up:
// https://stackoverflow.com/questions/46021640/how-to-sequence-two-animations-with-delay-in-between
let scaleForwardAnimationDuration: TimeInterval = 0.15
let transformBackAnimationDuration: TimeInterval = 0.1
let animationDuration: TimeInterval = scaleForwardAnimationDuration + transformBackAnimationDuration
UIView.animateKeyframes(withDuration: animationDuration, delay: 0, options: [], animations: {
UIView.addKeyframe(withRelativeStartTime: 0, relativeDuration: scaleForwardAnimationDuration) {
sender.transform = CGAffineTransform(scaleX: 0.75, y: 0.75)
}
UIView.addKeyframe(withRelativeStartTime: scaleForwardAnimationDuration, relativeDuration: transformBackAnimationDuration) {
sender.transform = .identity
}
})
and that caused input dropped on the floor
due to .allowUserInteraction
absent from options of the outer animation block

Swift Infinite Fade in and Out Loop

How can I make a effect in swift similar to this:
I want the animation to loop forever.
For iOS
UIViewAnimationOptions set provides different handy options to achieve a combination of beautiful and complex animations. For your particular scenario you will require two of the options.
UIViewAnimationOptions.Repeat
UIViewAnimationOptions.AutoReverse
Check out the code below for implementation.
Code:
class MyViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let view = UIView(frame: CGRect(x: 0, y: 0, width: 200, height: 200))
view.backgroundColor = UIColor.blueColor()
self.view.addSubview(view)
UIView.animateWithDuration(1,
delay: 0,
options: [UIViewAnimationOptions.Autoreverse, UIViewAnimationOptions.Repeat],
animations: {
view.backgroundColor = UIColor.clearColor()
},
completion: nil)
}
}
Explanation:
I have created a view with a specific frame for demo purpose.
The part you are interested in is the UIView.animateWithDuration method. Notice that I have provided an array [UIViewAnimationOptions.AutoReverse, UIViewAnimationOptions.Repeat] in the options parameter.
These two options are enough to achieve a smooth and forever looping animation like below.
https://s3.amazonaws.com/uploads.hipchat.com/37040/1764070/6Iow7n7WiWf6Naz/autoReverse.gif
If you don't want to reverse the animation, just remove UIViewAnimationOptions.AutoReverse from the array in the options parameter. You will get an animation like this.
https://s3.amazonaws.com/uploads.hipchat.com/37040/1764070/8fyRUlzqNHSQI47/noreverse.gif
For iOS
let viewSquare be the name of the blue square in your question.
UIView.animate(withDuration: 0.5, delay: 0, options: [.repeat,.autoreverse], animations: {
viewSquare.alpha = 0.0
}, completion: nil)
Swift 5.1
let duration = 0.5
func fadeIn(finished: Bool) {
UIView.animate(withDuration: self.duration, delay: 0,
options: [.curveEaseInOut],
animations: { self.tftMap.alpha = 1 }, completion: self.fadeOut)
}
func fadeOut(finished: Bool) {
UIView.animate(withDuration: self.duration, delay: 0,
options: [.curveEaseInOut],
animations: { self.tftMap.alpha = 0 }, completion: self.fadeIn)
}
I assume you are programming for iOS.
Play around with the duration to see what suits you best:
class ViewController: UIViewController {
#IBOutlet weak var myView: UIView!
let duration = 0.5
override func viewDidLoad() {
super.viewDidLoad()
self.fadeOut(true)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
func fadeIn(finished: Bool) {
UIView.animateWithDuration(self.duration, delay: 0, options: [.CurveEaseInOut], animations: { self.myView.alpha = 1 } , completion: self.fadeOut)
}
func fadeOut(finished: Bool) {
UIView.animateWithDuration(self.duration, delay: 0, options: [.CurveEaseInOut], animations: { self.myView.alpha = 0 } , completion: self.fadeIn)
}
}
Swift 5
This worked well for me. I wanted to animate a continuous fade in and out of a label, which I placed inside the "cardHeaderView" UIView.
#IBOutlet weak var cardHeaderView: UIView!
Place this inside the "viewDidAppear". I went with a delay of zero so the animation would start right away.
fadeViewInThenOut(view: cardHeaderView, delay: 0)
Here is the function.
func fadeViewInThenOut(view : UIView, delay: TimeInterval) {
let animationDuration = 1.5
UIView.animate(withDuration: animationDuration, delay: delay, options: [UIView.AnimationOptions.autoreverse, UIView.AnimationOptions.repeat], animations: {
view.alpha = 0
}, completion: nil)
}
Swift 5
Fade in 2 seconds
pause 2 seconds,
fade out 2 seconds
Repeat
func startAnimation() {
UIView.animateKeyframes(withDuration: 6.0,
delay: 0,
options: [.repeat, .autoreverse, .calculationModeLinear]) {
UIView.addKeyframe(withRelativeStartTime: 0.0, relativeDuration: 0.165) { [weak self] in
self?.view1.alpha = 0.0
}
UIView.addKeyframe(withRelativeStartTime: 0.165, relativeDuration: 0.165) { [weak self] in
self?.view2.alpha = 1.0
}
UIView.addKeyframe(withRelativeStartTime: 0.66, relativeDuration: 0.165) { [weak self] in
self?.view2.alpha = 0.0
}
ctartTime: 0.825, relativeDuration: 0.165) { [weak self] in
self?.view1.alpha = 1.0
}
}
}
Please note that when your animation is at Alpha == 0.0 the item is not interactable! You will have to add to .allowUserInteraction as an option
If you want repeatable fade animation you can do that by using CABasicAnimation like below :
###First create handy UIView extension :
extension UIView {
enum AnimationKeyPath: String {
case opacity = "opacity"
}
func flash(animation: AnimationKeyPath ,withDuration duration: TimeInterval = 0.5, repeatCount: Float = 5){
let flash = CABasicAnimation(keyPath: AnimationKeyPath.opacity.rawValue)
flash.duration = duration
flash.fromValue = 1 // alpha
flash.toValue = 0 // alpha
flash.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeInEaseOut)
flash.autoreverses = true
flash.repeatCount = repeatCount
layer.add(flash, forKey: nil)
}
}
###How to use it:
// You can use it with all kind of UIViews e.g. UIButton, UILabel, UIImage, UIImageView, ...
imageView.flash(animation: .opacity, withDuration: 1, repeatCount: 5)
titleLabel.flash(animation: .opacity, withDuration: 1, repeatCount: 5)

CABasicAnimation too verbose

In my application I have several fades. I create them with Core Animation, using syntax similar to what is below. Is there a less verbose syntax I could use?
CATransaction.begin()
let fade = CABasicAnimation(keyPath: "opacity")
fade.fromValue = 0
fade.toValue = 1
fade.duration = 0.35
fade.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut)
fade.fillMode = kCAFillModeForwards
fade.removedOnCompletion = false
CATransaction.setCompletionBlock({
...
})
self.addAnimation(fade, forKey: nil)
CATransaction.commit()
Sure. You can make your own:
extension CABasicAnimation
{
convenience init(_ keyPath: String, from: AnyObject?, to: AnyObject?, duration: CFTimeInterval = 0.3)
{
self.init(keyPath: keyPath)
fromValue = from
toValue = to
self.duration = duration
}
}
extension CALayer
{
func addAnimation(animation: CAAnimation, forKey key: String?, completion: Void -> Void)
{
CATransaction.begin()
CATransaction.setCompletionBlock(completion)
addAnimation(animation, forKey: key)
CATransaction.commit()
}
}
// later...
let fade = CABasicAnimation("opacity", from: 0, to: 1, duration: 0.35)
self.addAnimation(fade, forKey: nil) {
// ...
}
As an alternative to #jtbandes' excellent answer (voted), in many cases you can use a much simpler UIView animation:
UIView.animateWidhDuration(.35
animations:
{
myView.alpha = 0
}
completion:
{
(finshed) in
//your completion code here
})
EDIT:
I see from your comment that you're developing for OS X. That requires a different technique, using the animation proxy for your view:
myView.animator.setAlphaValue(0.0)
And if you want it to use a different duration than the default 0.25 second duration, you need to set that in an animation context group:
NSAnimationContext.currentContext().beginGrouping()
NSAnimationContext.currentContext().setDuration(0.35)
myView.animator.setAlphaValue(0.0)
NSAnimationContext.currentContext().endGrouping()
I believe that unlike iOS, NSViews don't have a native alpha setting, and setAlphaValue actually manipulates the alpha value of the view's layer. In that case you will probably need to set the view as layer-backed.

UIView Animations seem to speed up after first time, Xcode 6.1 swift

For some reason after my first call of a serious of animation blocks, the animation seems to be faster, not sure if this is a bug or something i've done wrong but i'm sure someone call tell me.. i've made a UIView subclass to handle this.
import UIKit
import QuartzCore
class GBPopupController: UIView {
var originalContainerCenterY = CGFloat()
#IBOutlet var continerConstraintCenterY: NSLayoutConstraint!
#IBOutlet var containerConstraintCenterX: NSLayoutConstraint!
var startingCenter = CGPoint()
#IBOutlet var contentView: UIView!
#IBOutlet var button: UIButton!
override func layoutSubviews() {
super.layoutSubviews()
contentView.layer.cornerRadius = 10
button.layer.cornerRadius = 10
}
override func didMoveToSuperview() {
self.beginViewAnimations()
}
func animatePopupIn() {
UIView.animateWithDuration(1.0, delay: 0.0, usingSpringWithDamping: 0.6, initialSpringVelocity: 0.7, options: UIViewAnimationOptions.CurveEaseIn, animations: {
self.contentView.layer.transform = CATransform3DIdentity
}, completion: {finished in
})
}
func beginViewAnimations() {
var transform = CATransform3DIdentity;
transform = CATransform3DMakeTranslation(0, -self.frame.size.height, 0)
self.contentView.layer.transform = transform
UIView.animateWithDuration(0.4, delay: 0.0, options: UIViewAnimationOptions.TransitionCrossDissolve, animations: {
self.backgroundColor = UIColor(white: 0.0, alpha: 0.5)
}, completion: {finished in
self.animatePopupIn()
})
}
func removeViewFromSuperView() {
UIView.animateWithDuration(0.5, delay: 0.0, options: UIViewAnimationOptions.TransitionCrossDissolve, animations: {
var transform = CATransform3DIdentity;
transform = CATransform3DMakeTranslation(0, -self.frame.size.height, 0)
self.contentView.layer.transform = transform
}, completion: {finished in
UIView.animateWithDuration(0.4, delay: 0.0, options: UIViewAnimationOptions.TransitionCrossDissolve, animations: {
self.backgroundColor = UIColor.clearColor()
}, completion: {finished in
self.removeViewAnimation()
})
})
}
func removeViewAnimation() {
self.removeFromSuperview()
}
My comment is too long so I answer here. I don't know if it can help you but here's what I did.
I use autolayout and so in the viewdidload(), dimensions are not yet initialized. They are based on a 600x600 size screen (meaning any width, any height). Screen size is picked up once the first action is done (like pushing a button in my case).
This is why I call my animation for the first time at this first action, just to put my frame at the good place. Then the rest is the same. But it kind of initialize my view with the right frame (size and position). After that, I don't have this problem and my animation is also good.
It's like I was calling it twice the first time, one to initialize and one just to play the animation.
I made some research and I found that it could be a problem with the layer which is different from the frame and which is initialized with a different animation from the one you choose. That's why I didn't have the same animation the first time.
Hope it will help you.

Resources