UIView.animateWithDuration doesn't animate on separate view controller - ios

Setting animation to fade in and out repeatedly.
'viewSmall' and 'viewBig' are UIImageViews.
view storyboard shot
class TestViewController: UIViewController {
#IBOutlet weak var viewBig: UIImageView!
#IBOutlet weak var viewSmall: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
animate()
}
func animate() {
UIView.animateWithDuration(0.5,
delay: 0,
options: [.Repeat, .Autoreverse],
animations: {
self.viewSmall.alpha = 0
},
completion: nil)
UIView.animateWithDuration(0.5,
delay: 0,
options: [.Repeat, .Autoreverse],
animations: {
self.viewBig.alpha = 0
},
completion: nil)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
This code doesn't animate on a only few view controllers, and the view controllers seem independent, no segue to other view controllers, on my guess. Because this code works perfectly on view controllers connected to navigation view controller.
How can I make it on independent view controller?
Please help.

Found a brilliant solution.
Used dispatch_after to catch duration and delay.
func animate(){
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, Int64(0.0 * Double(NSEC_PER_SEC))), dispatch_get_main_queue(), {
// Animate the transition
UIView.animateWithDuration(0.5,
delay: 0,
options: [.Repeat, .Autoreverse],
animations: {
self.viewSmall.alpha = 0
},
completion: nil)
UIView.animateWithDuration(0.5,
delay: 0,
options: [.Repeat, .Autoreverse],
animations: {
self.viewBig.alpha = 0
},
completion: nil)
})
}
Source from:
Swift UIView animateWithDuration completion closure called immediately

Related

Sequential fade in Swift

I'm using the excellent answer here to implement a fade in for a text label.
However, I want to introduce a delay so I can sequentially fade in several text labels.
So far (taken from the answer), i'm using :
extension UIView {
func fadeIn(duration: TimeInterval = 1.0, delay: TimeInterval = 0.0, completion: #escaping ((Bool) -> Void) = {(finished: Bool) -> Void in}) {
UIView.animate(withDuration: duration, delay: delay, options: UIViewAnimationOptions.curveEaseIn, animations: {
self.alpha = 1.0
}, completion: completion) }
}
and then implementing with :
override func viewDidLoad() {
self.line1Outlet.alpha = 0
self.line1Outlet.fadeIn(completion: {
(finished: Bool) -> Void in
})
}
I was thinking the best solution would be to implement the delay as a parameter in the extension so I could easily add a different delay to each label. (e.g.
override func viewDidLoad() {
self.line1Outlet.alpha = 0
//add a parameter here for the delay (here line 1 gets '1second' then line 2 could come in after 2seconds etc)
self.line1Outlet.delay = 1second
self.line1Outlet.fadeIn(completion: {
(finished: Bool) -> Void in
})
}
I've tried adding self.delay into the extension (underneath self.alpha) but that doesn't work and I'm not sure how to refactor that extension to allow what I'm after.
The answer to this would then be a reusable method of implementing sequential fades that hopefully would be useful to lots of other people!
In the extension you created, first add self.alpha = 0.0 at the top in fadeIn function, i.e.
extension UIView {
func fadeIn(duration: TimeInterval = 1.0, delay: TimeInterval = 0.0, completion: ((Bool)->())? = nil) {
self.alpha = 0.0
UIView.animate(withDuration: duration, delay: delay, options: .curveEaseIn, animations: {
self.alpha = 1.0
}, completion: completion)
}
}
Now lets assume you've 3 labels in your view, i.e.
#IBOutlet weak var label1: UILabel!
#IBOutlet weak var label2: UILabel!
#IBOutlet weak var label3: UILabel!
Add animation to the labels in sequence like,
self.label1.fadeIn(delay: 0.1) { _ in
self.label2.fadeIn(delay: 0.2, completion: { _ in
self.label3.fadeIn(delay: 0.3, completion: { _ in
print("Done All")
})
})
}
Since the duration parameter in fadeIn method is having a default value, we can avoid that.
The way you're calling fadeIn is one way of calling it. Since the method contains multiple default params, it can be called in other ways as well.
Read more about default parameters here.
Edit:
For hiding the labels initially, set the alpha of all labels as 0 in storyboard itself.

swift: fix buttons animation

When my app stat I want to show buttons animation. In storyboard my buttons have height = 50 and y = -50. I have this action in code.
#IBOutlet weak var imageView: UIImageView!
override func viewDidLoad()
{
super.viewDidLoad()
buttonAnimation()
}
func buttonAnimation()
{
self.buttonTopConstraint.constant += self.ButtonHeightConstraint.constant
UIView.animate(withDuration: 1.5, delay: 0.0, options: [], animations:
{
self.view.layoutIfNeeded()
}
)
}
Animation work well. But when app start my background imageView in storyboard and different imageView animated too. But I need to have only animation for button. How to fix it?
Don't call animation in viewDidLoad because at that time all views of your controller are going to set.
Use viewDidAppear to call animation method.
override func viewDidAppear()
{
buttonAnimation()
}
Use CGAffineTransform to animate your button. Remove y=-50 from storyboard and keep it at its desired place. Then call this function in viewWillAppear and viewDidAppear :
func animateButton() {
yourButton.transform = CGAffineTransform(translationX: yourButton.frame.origin.x, y: yourButton.frame.origin.y - 50)
UIView.animate(withDuration: 1.5, delay: 0.0, options: [], animations: {
self.yourButton.transform = .identity
}, completion: nil)
}

Keep animation when changing viewController

I'm trying to code an animation behind the profile picture, in order to encourage the user to click on it.
It's a circle which become bigger and smaller then.
override func layoutSubviews() {
super.layoutSubviews()
UIView.animate(withDuration: 1.0, delay: 0, options: [.repeat, .autoreverse], animations: {
self.bouncedView.transform = CGAffineTransform(scaleX:
1.3, y: 1.3)
}, completion: nil)
}
The problem is that, when I go to another viewController, the animation is stopped and the circle stays like you can the on the screen shot. Do you know how I could avoid this issue ?
Do a transform to .identity on ViewDidAppear. Something similar to below code:
class HomeController: UIViewController {
#IBOutlet weak var viewToAnimate: UIView!
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewWillAppear(_ animated: Bool) {
self.viewToAnimate.transform = .identity
animateView()
}
func animateView(){
UIView.animate(withDuration: 1.0, delay: 0, options: [.repeat, .autoreverse], animations: {
self.viewToAnimate.transform = CGAffineTransform(scaleX:
1.3, y: 1.3)
}, completion: nil)
}
}
The problem as you might have guessed is that the UIView.animate is only called on the ViewDidLoad method and since we don't have access that code while returning to this ViewController from another, it is better to start the animation in the ViewWillAppear method.
If the same issue occurs when you switch between tabs, then please make a separate UIView subclass for the view that you want to animate and proceed as follows:
class AnimateView: UIView {
override func awakeFromNib() {
super.awakeFromNib()
}
override func willMove(toWindow newWindow: UIWindow?) {
super.willMove(toWindow: newWindow)
self.transform = .identity
animateView()
}
func animateView(){
UIView.animate(withDuration: 1.0, delay: 0, options: [.repeat, .autoreverse], animations: {
self.transform = CGAffineTransform(scaleX:
1.3, y: 1.3)
}, completion: nil)
}
}
Here, you have taken the animate function to the UIView object and whenever the view appears, the animation will be reset.
Well, I don't yet have a satisfying answer yet (at least that would satisfy me), but I would like to add at least some insight to which I was able to get by a bit of experimenting.
First of all, it seems that the animation gets ended when the viewController is not currently the one presented. In your case that means that the animation stops and finishes with the state, in which the view is 1.3 times bigger, and stops repeating. layoutSubviews gets called only when you present it the first time. At least the viewController.viewDidLayoutSubviews gets called only at the beginning and not when you go back (so layoutSubviews will not get executed when the view reappears) - so the animation won't get restarted.
I tried to move the animation to the viewDidAppear of a UIViewController - that did not work either, because stopping animation resulted in state in which the view was already scaled 1.3 times. Resetting the state to CGAffineTransform.identity before creating the animation did work.
Now as I said, this can serve you as a workaround on how to get it working, however, I think what you really are looking for is some hook that would tell your view (not viewController) that it got presented again. But the following minimal example can at least help others to take a look at it and not start from scratch.
import UIKit
import PlaygroundSupport
class MyViewController : UIViewController {
let animator = UIViewPropertyAnimator(duration: 1, timingParameters: UICubicTimingParameters(animationCurve: .linear))
init(title: String) {
super.init(nibName: nil, bundle: nil)
self.title = title
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
let animatableView = UIView()
override func loadView() {
let view = UIView()
view.backgroundColor = .white
animatableView.frame = CGRect(x: 150, y: 400, width: 100, height: 100)
animatableView.backgroundColor = UIColor.magenta
view.addSubview(animatableView)
self.view = view
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
self.animatableView.transform = CGAffineTransform.identity
UIView.animate(withDuration: 1.0, delay: 0, options: [.repeat, .autoreverse], animations: {
self.animatableView.transform = CGAffineTransform(scaleX:
1.3, y: 1.3)
}, completion: nil)
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
print("\(self.title) >> Lay out")
}
}
let tabBar = UITabBarController()
tabBar.viewControllers = [MyViewController(title: "first"), MyViewController(title: "second"), MyViewController(title: "third")]
tabBar.selectedIndex = 0
// Present the view controller in the Live View window
PlaygroundPage.current.liveView = tabBar
EDIT
I added few lines to check if the self.animatableView.layer.animationKeys() contains any animations, it seems that switching tabs removes the animation from the view's layer - so you have to find a way to add the animation everytime the view reappears.
EDIT 2
So I would go with #SWAT's answer and use willMove(toWindow:) instead of layoutSubviews.

Why are the animated UI Labels in my code starting from within the view?

I'm a very new-to-code learner. I've been taking some online courses, and came across this project recently.
I basically copied the code as I saw it on the screen, as the project files weren't available to download.
The animation is supposed to bring the UILabels from outside the view and position them within the view.
What seems to happen however, is the labels are starting within the view screen and animate outward and beyond.
I've copied the project below. Any help is so very appreciated.
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var helloWorld: UILabel!
#IBOutlet weak var secondLabel: UILabel!
#IBOutlet weak var hiddenLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
helloWorld.center.y -= view.bounds.height
secondLabel.center.y += view.bounds.height
hiddenLabel.alpha = 0.0
}
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
// Animate Hello World label
UIView.animateWithDuration(1.5, animations: {
self.helloWorld.center.y += self.view.bounds.height
}, completion: { finished in
self.secondAnimation()
})
// Animate background color change
UIView.animateWithDuration(2.0, delay: 1.5, options: [], animations: {
self.view.backgroundColor = UIColor.yellowColor()
}, completion:nil)
}
// Animate second Label
func secondAnimation() {
UIView.animateWithDuration(1.5, delay: 0.0, options: [], animations: { () -> Void in
self.secondLabel.center.y -= self.view.bounds.height
}, completion:nil)
}
func backgroundColor() {
UIView.animateWithDuration(2.5, animations: {
self.view.backgroundColor = UIColor.blackColor()
}, completion: nil)
UIView.animateWithDuration(1.0, delay: 1.5, options: [], animations: {
self.hiddenLabel.alpha = 1.0
}, completion:nil)
}
}
I would check hello world.center.y value in viewWillAppear, before you subtract the view.bounds.height. If center.y value is large and positive, then the subtraction might be setting the center.y value to be inside of the view. If this is the case, then you would want to change the subtraction in viewWillAppear to addition and then the addition in the viewDidAppear animation to subtraction.

Unable to Get UIButton to Fade In on ViewDidLoad

As stated in the title, I am unable to get the UIButton to fade in on the ViewDidLoad method. Here is my code thus far:
ViewController.swift
import UIKit
import QuartzCore
class ViewController: UIViewController {
#IBOutlet weak var nextButton: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
self.nextButton.fadeIn(duration: 10.0, delay: 10.0)
}
}
UIViewExtensions.swift
import Foundation
import UIKit
extension UIView {
func fadeIn(duration: NSTimeInterval = 1.0, delay: NSTimeInterval = 0.0, completion: ((Bool) -> Void) = {(finished: Bool) -> Void in}) {
UIView.animateWithDuration(duration, delay: delay, options: UIViewAnimationOptions.CurveEaseIn, animations: {
self.alpha = 1.0
}, completion: completion) }
func fadeOut(duration: NSTimeInterval = 1.0, delay: NSTimeInterval = 0.0, completion: (Bool) -> Void = {(finished: Bool) -> Void in}) {
UIView.animateWithDuration(duration, delay: delay, options: UIViewAnimationOptions.CurveEaseIn, animations: {
self.alpha = 0.0
}, completion: completion)
}
}
You do not want to start any animations like a fade in, on the viewDidLoad. This method is called when the class is finished initializing (right after the init). It happens before the view is visible. You want to start animations in the viewDidAppear. This is called once the view is visible on the screen. When you start it in the viewDidLoad, it's already done the animation by the time it gets to the viewDidAppear, assuming of course you did the fade in over about .5 second.
Set you button alpha to 0 on storyboard,so that you can fade in.
Because,if your button alpha is 1,then you want to animate to 1,IOS do nothing
override func viewDidAppear(animated: Bool) {
self.button.fadeIn(duration: 3, delay: 0)
}

Resources