Swift UIKit - Sequential animation function - ios

I have a func UIView.animate (2 imageViews, up/down, pulsating 4 and 2 times sequentially), which is forking fine.
Problem is that i need to run this func sequentially, all animations executed -> next lap of execution -> N lap of execution for my needs (it can be 5 or it can be 25 etc).
How can i do it?
func upDown() {
runButton.isEnabled = false
UIView.animate(
withDuration: 0.5,
delay: 0,
options: [.autoreverse, .repeat]) {
UIView.modifyAnimations(withRepeatCount: 4, autoreverses: true) {
self.arrowDownImageView.transform = CGAffineTransform(scaleX: 1.3, y: 1.3)
self.arrowDownImageView.tintColor = .green
self.downLabel.backgroundColor = .green
}
} completion: { _ in
self.arrowDownImageView.transform = .identity
self.downLabel.transform = .identity
self.downLabel.backgroundColor = .black
self.arrowDownImageView.tintColor = .black
UIView.animate(
withDuration: 0.5,
delay: 0,
options: [.autoreverse, .repeat]) {
UIView.modifyAnimations(withRepeatCount: 2, autoreverses: true) {
self.arrowUpImageView.transform = CGAffineTransform(scaleX: 1.3, y: 1.3)
self.arrowUpImageView.tintColor = .blue
self.upLabel.backgroundColor = .blue
}
} completion: { _ in
self.arrowUpImageView.transform = .identity
self.upLabel.transform = .identity
self.upLabel.backgroundColor = .black
self.arrowUpImageView.tintColor = .black
self.runButton.isEnabled = true
}
}
}
I tried to call my func upDown() through other UIView.animate (+ .modifyAnimations) func (executed 1 time only), call it with for-in-loop (also wrong, like i clicked X-times on a button). Should i rebuild it with some other method, not UIView.animate? In this case which one? Pods are forbidden in my case. I'm planning (need) to place my upDown() inside a new func to run it sequentially. Any other ideas? Any other methods? Thanks!

It sounds like you want your upDown function called multiple times. You can add a count parameter to upDown. Then in the final completion handler you can call upDown with the next smaller number. When the count gets to zero you can exit.
Here's your upDown method with only two lines added plus the new default parameter. Added the guard at the beginning and the recursive call at the very end in the last completion block.
func upDown(count: Int = 1) {
guard count > 0 else { return } // stop when we get to zero
runButton.isEnabled = false
UIView.animate(
withDuration: 0.5,
delay: 0,
options: [.autoreverse, .repeat]) {
UIView.modifyAnimations(withRepeatCount: 4, autoreverses: true) {
self.arrowDownImageView.transform = CGAffineTransform(scaleX: 1.3, y: 1.3)
self.arrowDownImageView.tintColor = .green
self.downLabel.backgroundColor = .green
}
} completion: { _ in
self.arrowDownImageView.transform = .identity
self.downLabel.transform = .identity
self.downLabel.backgroundColor = .black
self.arrowDownImageView.tintColor = .black
UIView.animate(
withDuration: 0.5,
delay: 0,
options: [.autoreverse, .repeat]) {
UIView.modifyAnimations(withRepeatCount: 2, autoreverses: true) {
self.arrowUpImageView.transform = CGAffineTransform(scaleX: 1.3, y: 1.3)
self.arrowUpImageView.tintColor = .blue
self.upLabel.backgroundColor = .blue
}
} completion: { _ in
self.arrowUpImageView.transform = .identity
self.upLabel.transform = .identity
self.upLabel.backgroundColor = .black
self.arrowUpImageView.tintColor = .black
self.runButton.isEnabled = true
upDown(count - 1) // Run it again
}
}
}
With those few changes you can call upDown() if you only want it to run once or you can call it as upDown(count: 5) if you want it to run 5 times (or whatever number you pass in).

Related

UITableView "jump up" during custom presentation animation

I am trying to create custom transition but I have a problem with "jumping" tableView. As you can see, just before transition, tableView hide under navigationBar which is wrong. How can I fix it?
Animation code:
UIView.animate(withDuration: transitionDuration, delay: 0, options: animationOptions, animations: { [weak self] in
guard let self = self else { return }
fromVC.view.transform = CGAffineTransform(scaleX: 0.9, y: 0.855)
fromVC.view.layer.cornerRadius = self.cornerRadius
fromVC.view.clipsToBounds = true
self.containerView.frame.origin.y -= self.containerHeight
self.topView.frame.origin.y -= self.containerHeight
self.backdropView.alpha = self.backdropAlpha
}, completion: { _ in
transitionContext.completeTransition(true)
})
and how it looks right now:

How to Animate a UIView n number of times within a loop and track each animation event

UIView.animate(withDuration: 3.0, animations: {
// UIView.setAnimationRepeatCount(3.0)
self.leadng.constant = self.view.frame.size.width + self.vwObj.frame.size.width
self.view.layoutIfNeeded()
}, completion: { finished in
UIView.animate(withDuration: 3.0, animations: {
self.leadng.constant = -(self.vwObj.frame.size.width)
})
})
This is the code i am using for animating a UIView. What i am trying to do is to animate the UIView n number of times. That can be done using UIView.setAnimationRepeatCount(3.0), but the problem is i am having an array of different images which i want to show within the UiView for each animation. Anyone having any idea about how to do that
Use the animatedImage property of UIImage to animate multiple images inside a imageview.
ImageView.image = UIImage.animatedImage(with: myImages, duration: 1.0)
where myImages is array of images you want to animate.
Follow this link for details.
Edit: Definitely a very crude way, but it works.
Declared this at top of class.
let arrayColors = [UIColor.red , UIColor.green, UIColor.blue]
var animationCount = 0
I called animateView() in viewdidload
func animateView() {
self.vwObj.backgroundColor = arrayColors[animationCount]
UIView.animate(withDuration: 3.0, animations: {
self.leadng.constant = self.view.frame.size.width + self.vwObj.frame.size.width
self.view.layoutIfNeeded()
}, completion: { finished in
UIView.animate(withDuration: 3.0, animations: {
self.leadng.constant = 0
self.view.layoutIfNeeded()
}, completion: { finished in
self.animationCount = self.animationCount + 1
if self.animationCount < 3 {
self.animateView()
}
})
})
}
Insted of array of colors, you can use array of images to set imageview.image = arrayImage[animationCount]
Seems solved by the below code:
UIView.animate(withDuration: 3.0, animations: {
print ("Animation Started")
self.leadng.constant = self.view.frame.size.width + self.vwObj.frame.size.width
self.view.layoutIfNeeded()
print ("BeginCounter --- (self.counter)")
}, completion: { finished in
UIView.animate(withDuration: 3.5, animations: {
self.leadng.constant = -(self.vwObj.frame.size.width)
print ("AnimationCompleted")
self.counter = self.counter + 1
print ("CompleteCounter --- \(self.counter)")
//self.displayAnimation()
if (self.counter == 3) {
self.timer?.invalidate()
self.counter = 0
}
})
})
& calling---- timer = Timer.scheduledTimer(timeInterval: 4.0, target: self, selector: #selector(ViewController.displayAnimation), userInfo: nil, repeats: true)

Frames changing suddenly instead of animating [iOS]

I have a view with a scroll view in my app (a percentage calculator). I am trying to animate the view to fly out and then fly in again from the left albeit with different contents, on the press of a button.
Here's my code:
func next()
{
UIView.animateWithDuration(0.5, animations: {
self.mainView.frame = CGRectMake(-500, self.view.bounds.height/2-self.mainHeight/2, self.mainWidth, self.mainHeight)
self.mainView.center.x -= 600
}) { (success) in
self.mainView.center.x += 1200
}
UIView.animateWithDuration(0.5, animations: {
self.mainView.frame = CGRectMake(self.view.bounds.width/2-self.mainWidth/2, self.view.bounds.height/2-self.mainHeight/2, self.mainWidth, self.mainHeight)
}) { (success) in
print("Animation Successful!")
self.drawView()
}
}
I don't understand why, but the animation doesn't occur, the view disappears completely and I see "Animation Successful" in the logs.
To be clear, I have created the view programmatically.
Here's the code in viewDidLoad(); the view displays correctly when it's called:
mainHeight = self.view.bounds.height * 0.85
mainWidth = self.view.bounds.width * 0.85
let viewHeight = self.view.bounds.height
let viewWidth = self.view.bounds.width
mainView.frame = CGRectMake(viewWidth/2-mainWidth/2, viewHeight/2-mainHeight/2, mainWidth, mainHeight)
mainView.layer.cornerRadius = 8
mainView.layer.masksToBounds = true
mainView.backgroundColor = getColor(0, green: 179, blue: 164)
self.view.addSubview(mainView)
scrollView.frame = CGRectMake(0, 0, mainWidth, mainHeight)
scrollView.contentSize = CGSizeMake(mainWidth, 800)
self.mainView.addSubview(scrollView)
contentView.frame = CGRectMake(0, 0, mainWidth, 800);
contentView.backgroundColor = getColor(0, green: 179, blue: 164)
contentView.layer.masksToBounds = true
contentView.clipsToBounds = true
self.scrollView.addSubview(contentView)
Place self.layoutViewIfNeeded() before frame/center changes.
Additionally you may need to put your next animation within a completion handler of the next animation.
func next() {
UIView.animateWithDuration(0.5, animations: {
self.view.layoutIfNeeded() // add this
self.mainView.frame = CGRectMake(-500, self.view.bounds.height/2-self.mainHeight/2, self.mainWidth, self.mainHeight)
self.mainView.center.x -= 600
}) { (success) in
self.view.layoutIfNeeded() // add this
self.mainView.center.x += 1200
// your next animation within a completion handler of previous animation
UIView.animateWithDuration(0.5, animations: {
self.mainView.frame = CGRectMake(self.view.bounds.width/2-self.mainWidth/2, self.view.bounds.height/2-self.mainHeight/2, self.mainWidth, self.mainHeight)
}) { (success) in
print("Animation Successful!")
self.drawView()
}
}
}
I haven't tested it yet, perhaps some changes or lines ordering is needed.
In Swift 3, I had to set the frame and then call layoutIfNeeded().
For example, an animation with spring:
UIView.animate(withDuration: 0.3, delay: 0.1, usingSpringWithDamping: 0.7, initialSpringVelocity: 0.5, options: .curveEaseInOut, animations: {
self.subView.frame = CGRect(x: 0, y: 0, width: 500, height: 200)
self.view.layoutIfNeeded()
}) { (_) in
// Follow up animations...
}

How to make a button flash or blink?

I am trying to change a button's color (just a flash/blink) to green when a scan is correct and red when there's a problem. I am able to do this with a view like so
func flashBG(){
UIView.animateWithDuration(0.7, animations: {
self.view.backgroundColor = UIColor.greenColor()
})
}
But with a button it stays green
func flashBtn(){
UIButton.animateWithDuration(0.5, animations: {
self.buttonScan.backgroundColor = UIColor.greenColor()
})
}
I have created the button by code
func setupScanButton() {
let X_Co = (self.view.frame.size.width - 100)/2
let Y_Co = (self.viewForLayer.frame.size.height + 36/2)
buttonScan.frame = CGRectMake(X_Co,Y_Co,100,100)
buttonScan.layer.borderColor = UIColor.whiteColor().CGColor
buttonScan.layer.borderWidth = 2
buttonScan.layer.cornerRadius = 50
buttonScan.setTitle("Scan", forState: .Normal)
buttonScan.backgroundColor = UIColor.blueColor()
buttonScan.addTarget(self, action: "buttonScanAction", forControlEvents: .TouchUpInside)
buttonScan.setTitleColor(UIColor(red:255/255, green: 255/255, blue:255/255, alpha: 1), forState: UIControlState.Normal)
self.view.addSubview(buttonScan)
}
Should i call setupScanButton() again?
This should work in Swift 4
extension UIView{
func blink() {
self.alpha = 0.2
UIView.animate(withDuration: 1, delay: 0.0, options: [.curveLinear, .repeat, .autoreverse], animations: {self.alpha = 1.0}, completion: nil)
}
}
This will start and stop a flashing button onClick, if you only want to flash the button immediately just use the first statement.
var flashing = false
#IBAction func btnFlash_Clicked(sender: AnyObject) {
if !flashing{
self.buttonScan.alpha = 1.0
UIView.animateWithDuration(0.5, delay: 0.0, options: [.CurveEaseInOut, .Repeat, .Autoreverse, .AllowUserInteraction], animations: {() -> Void in
self.buttonScan.alpha = 0.0
}, completion: {(finished: Bool) -> Void in
})
flashing = true
}
else{
UIView.animateWithDuration(0.1, delay: 0.0, options: [.CurveEaseInOut, .BeginFromCurrentState], animations: {() -> Void in
self.buttonScan.alpha = 1.0
}, completion: {(finished: Bool) -> Void in
})
}
}
Swift 5.x version
An updated version with extension.
extension UIView {
func blink(duration: TimeInterval = 0.5, delay: TimeInterval = 0.0, alpha: CGFloat = 0.0) {
UIView.animate(withDuration: duration, delay: delay, options: [.curveEaseInOut, .repeat, .autoreverse], animations: {
self.alpha = alpha
})
}
}
To call the function:
button.blink() // without parameters
button.blink(duration: 1, delay: 0.1, alpha: 0.2) // with parameters
I hope that will solve your problem.
buttonScan.alpha = 1.0
UIView.animate(withDuration: 1.0, delay: 1.0, options: UIView.AnimationOptions.curveEaseOut, animations: {
buttonScan.alpha = 0.0
}, completion: nil)
Swift 4:
I've maked an extension with some useful options:
extension UIButton {
open override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
return self.bounds.contains(point) ? self : nil
}
func blink(enabled: Bool = true, duration: CFTimeInterval = 1.0, stopAfter: CFTimeInterval = 0.0 ) {
enabled ? (UIView.animate(withDuration: duration, //Time duration you want,
delay: 0.0,
options: [.curveEaseInOut, .autoreverse, .repeat],
animations: { [weak self] in self?.alpha = 0.0 },
completion: { [weak self] _ in self?.alpha = 1.0 })) : self.layer.removeAllAnimations()
if !stopAfter.isEqual(to: 0.0) && enabled {
DispatchQueue.main.asyncAfter(deadline: .now() + stopAfter) { [weak self] in
self?.layer.removeAllAnimations()
}
}
}
}
First of all, I've overrided the hittest function to enabling the touch also when the button have the alpha equals to 0.0 (transparent) during the animation.
Then , all input vars have a default value so you can launch the blink() method without parameters
I've introduced also the enabled parameter to start or stop the animations on your button.
Finally, if you want you can stop animation after a specific time with the stopAfter parameter.
Usage:
yourButton.blink() // infinite blink effect with the default duration of 1 second
yourButton.blink(enabled:false) // stop the animation
yourButton.blink(duration: 2.0) // slowly the animation to 2 seconds
yourButton.blink(stopAfter:5.0) // the animation stops after 5 seconds.
Typical uses:
yourButton.blink(duration: 1.5, stopAfter:10.0)
// your code..
yourButton.blink()
// other code..
yourButton.blink(enabled:false)
You can try something like this:
extension UIView {
func blink() {
UIView.animateWithDuration(0.5, //Time duration you want,
delay: 0.0,
options: [.CurveEaseInOut, .Autoreverse, .Repeat],
animations: { [weak self] in self?.alpha = 0.0 },
completion: { [weak self] _ in self?.alpha = 1.0 })
dispatch_after(dispatch_time(DISPATCH_TIME_NOW,Int64(2 * NSEC_PER_SEC)),dispatch_get_main_queue()){
[weak self] in
self?.layer.removeAllAnimations()
}
}
}
//MARK : Usage
yourButton.flash()
extension UIButton {
func flash() {
let flash = CABasicAnimation(keyPath: "opacity")
flash.duration = 0.5
flash.fromValue = 1
flash.toValue = 0.1
flash.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeInEaseOut)
flash.autoreverses = true
flash.repeatCount = 3
layer.add(flash, forKey: nil)
}
}
Swift 3.0
func btnFlash_Clicked(sender: AnyObject) {
if !flashing{
callButton.alpha = 1.0
UIView.animate(withDuration: 0.5, delay: 0.0, options: [.allowUserInteraction], animations: {() -> Void in
callButton.alpha = 0.5
}, completion: {(finished: Bool) -> Void in
})
flashing = true
}
else{
flashing = false
callButton.alpha = 0.5
UIView.animate(withDuration: 0.5, delay: 0.0, options: [.allowUserInteraction], animations: {() -> Void in
callButton.alpha = 1.0
}, completion: {(finished: Bool) -> Void in
})
}
}
with UIViewPropertyAnimator and Swift 5
UIViewPropertyAnimator.runningPropertyAnimator(withDuration: 1, delay: 0, options: [.curveLinear,.repeat], animations: {
UIView.setAnimationRepeatCount(3000)
self.buttonScan.alpha = 0.0
}, completion: {_ in })
Swift 3.0
func animateFlash() {
flashView.alpha = 0
flashView.isHidden = false
UIView.animate(withDuration: 0.3, animations: { flashView.alpha = 1.0 }) { finished in flashView.isHidden = true }
}
This UIView extension "blinks" a view and changes the background colour:
/**
Blinks a view with a given duration and optional color.
- Parameter duration: The duration of the blink.
- Parameter color: The color of the blink.
*/
public func blink(withDuration duration: Double = 0.25, color: UIColor? = nil) {
alpha = 0.2
UIView.animate(withDuration: duration, delay: 0.0, options: [.curveEaseInOut], animations: {
self.alpha = 1.0
})
guard let newBackgroundColor = color else { return }
let oldBackgroundColor = backgroundColor
UIView.animate(withDuration: duration, delay: 0.0, options: [.curveEaseInOut], animations: {
self.backgroundColor = newBackgroundColor
self.backgroundColor = oldBackgroundColor
})
}
You would then use as follows:
buttonScan.blink(color: .green)
myButton.alpha = 0.7
UIView.animate(withDuration: 0.3,
delay: 1.0,
options: [UIView.AnimationOptions.curveLinear, UIView.AnimationOptions.repeat, UIView.AnimationOptions.autoreverse],
animations: { myButton.alpha = 1.0 },
completion: nil)
Another smoothly animating version for Swift 5:
public extension UIView {
func blink(duration: TimeInterval) {
let initialAlpha: CGFloat = 1
let finalAlpha: CGFloat = 0.2
alpha = initialAlpha
UIView.animateKeyframes(withDuration: duration, delay: 0, options: .beginFromCurrentState) {
UIView.addKeyframe(withRelativeStartTime: 0, relativeDuration: 0.5) {
self.alpha = finalAlpha
}
UIView.addKeyframe(withRelativeStartTime: 0.5, relativeDuration: 0.5) {
self.alpha = initialAlpha
}
}
}
}

Create a continuously rotating square on the screen using animateKeyframesWithDuration

I tried to use the code below to create a continuously rotating square on the screen. But I don't know why the rotational speed is changing. How could I change the code to make the rotational speed invariable? I tried different UIViewKeyframeAnimationOptions, but seems none of them work.
override func viewDidLoad() {
super.viewDidLoad()
let square = UIView()
square.frame = CGRect(x: 55, y: 300, width: 40, height: 40)
square.backgroundColor = UIColor.redColor()
self.view.addSubview(square)
let duration = 1.0
let delay = 0.0
let options = UIViewKeyframeAnimationOptions.Repeat
UIView.animateKeyframesWithDuration(duration, delay: delay, options: options, animations: {
let fullRotation = CGFloat(M_PI * 2)
UIView.addKeyframeWithRelativeStartTime(0, relativeDuration: 1/3, animations: {
square.transform = CGAffineTransformMakeRotation(1/3 * fullRotation)
})
UIView.addKeyframeWithRelativeStartTime(1/3, relativeDuration: 1/3, animations: {
square.transform = CGAffineTransformMakeRotation(2/3 * fullRotation)
})
UIView.addKeyframeWithRelativeStartTime(2/3, relativeDuration: 1/3, animations: {
square.transform = CGAffineTransformMakeRotation(3/3 * fullRotation)
})
}, completion: {finished in
})
}
I faced same problem before, this is how I make it work:
Swift 2
let raw = UIViewKeyframeAnimationOptions.Repeat.rawValue | UIViewAnimationOptions.CurveLinear.rawValue
let options = UIViewKeyframeAnimationOptions(rawValue: raw)
Swift 3,4,5
let raw = UIView.KeyframeAnimationOptions.repeat.rawValue | UIView.AnimationOptions.curveLinear.rawValue
let options = UIView.KeyframeAnimationOptions(rawValue: raw)
I figured this problem out just before gave it up. I don't think there is doc about it, but it just work.
That's really odd... UIView.animateKeyframesWithDuration isn't working as I would expect it to with UIViewKeyframeAnimationOptions.CalculationModeLinear|UIViewKeyframeAnimationOpti‌​ons.Repeat passed in with options.
If you use the non-block method of creating a keyframe animation (see below) the rotation repeats as expected.
If I find out why the block-based option isn't working I'll try and remember to update answer here too!
override func viewDidLoad() {
super.viewDidLoad()
let square = UIView()
square.frame = CGRect(x: 55, y: 300, width: 40, height: 40)
square.backgroundColor = UIColor.redColor()
self.view.addSubview(square)
let fullRotation = CGFloat(M_PI * 2)
let animation = CAKeyframeAnimation()
animation.keyPath = "transform.rotation.z"
animation.duration = 2
animation.removedOnCompletion = false
animation.fillMode = kCAFillModeForwards
animation.repeatCount = Float.infinity
animation.values = [fullRotation/4, fullRotation/2, fullRotation*3/4, fullRotation]
square.layer.addAnimation(animation, forKey: "rotate")
}
Add UIViewKeyframeAnimationOptionCalculationModeLinear do your keyframe options. The default behavior for UIView animations is to "ease in/out" of the animation. i.e., start slow, go up to speed, then slow down again just near the end.
AFAIU, this is happening because the default animation curve is UIViewAnimationOption.CurveEaseInOut.
Unfortunately, UIViewKeyframeAnimationOptions doesn't have options for changing the curve, but you can add them manually!
Use this extension:
Swift 2
extension UIViewKeyframeAnimationOptions {
static var CurveEaseInOut: UIViewKeyframeAnimationOptions { return UIViewKeyframeAnimationOptions(rawValue: UIViewAnimationOptions.CurveEaseInOut.rawValue) }
static var CurveEaseIn: UIViewKeyframeAnimationOptions { return UIViewKeyframeAnimationOptions(rawValue: UIViewAnimationOptions.CurveEaseIn.rawValue) }
static var CurveEaseOut: UIViewKeyframeAnimationOptions { return UIViewKeyframeAnimationOptions(rawValue: UIViewAnimationOptions.CurveEaseOut.rawValue) }
static var CurveLinear: UIViewKeyframeAnimationOptions { return UIViewKeyframeAnimationOptions(rawValue: UIViewAnimationOptions.CurveLinear.rawValue) }
}
Swift 3, 4, 5
extension UIView.KeyframeAnimationOptions {
static var curveEaseInOut: UIView.KeyframeAnimationOptions { return UIView.KeyframeAnimationOptions(rawValue: UIView.AnimationOptions.curveEaseInOut.rawValue) }
static var curveEaseIn: UIView.KeyframeAnimationOptions { return UIView.KeyframeAnimationOptions(rawValue: UIView.AnimationOptions.curveEaseIn.rawValue) }
static var curveEaseOut: UIView.KeyframeAnimationOptions { return UIView.KeyframeAnimationOptions(rawValue: UIView.AnimationOptions.curveEaseOut.rawValue) }
static var curveLinear: UIView.KeyframeAnimationOptions { return UIView.KeyframeAnimationOptions(rawValue: UIView.AnimationOptions.curveLinear.rawValue) }
}
Now you can use curve options in UIView.animateKeyframesWithDuration method
Swift 2
let keyframeOptions: UIViewKeyframeAnimationOptions = [.Repeat, .CurveLinear]
UIView.animateKeyframesWithDuration(duration, delay: delay, options: keyframeOptions, animations: {
// add key frames here
}, completion: nil)
Swift 3, 4, 5
let keyframeOptions: UIView.KeyframeAnimationOptions = [.repeat, .curveLinear]
UIView.animateKeyframes(withDuration: duration, delay: delay, options: keyframeOptions, animations: {
// add key frames here
}, completion: nil)

Resources