I've been learning swift for one week now and after creating my first app which uses Weather API I wanted to create simple animation: there is an image in LaunchScreen.storyboard with background and I wanted to animate the image to shrink to 0 so that my other ViewController would appear as normal. I've made something like this but there is problem, after the animation is finished Main ViewController appears and there is no animation inside of it. Moreover I wanted this image to increase its size slightly and then shrink - maybe there is another way to do it in one UIView.animate?
afterLaunchVC.swift:
class afterLaunchVC: UIViewController {
#IBOutlet weak var logoImg: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
//increasing the size
UIView.animate(withDuration: 0.1, animations: ({
self.logoImg.transform = CGAffineTransform(scaleX: 1.05, y: 1.05)
}), completion: nil)
//shrinking the image after increasing
UIView.animate(withDuration: 1.0, animations: ({
self.logoImg.transform = CGAffineTransform(scaleX: 0.00001, y: 0.00001)
}), completion:{ //performingSegue
finished in self.performSegue(withIdentifier: "MainVC", sender: self)
})
}
}
As you can see as a UIView.animate completion I've set performSegue and turned off the animation of it but can't get animation in MainVC.swift (IBOutlets appear normally but without animation)
MainVC.swift:
class MainVC: UIViewController {
#IBOutlet weak var topLabel: UILabel!
#IBOutlet weak var hourLabel: UILabel!
#IBOutlet weak var remainingTimeLabel: UILabel!
var time : TimeTrack!
override func viewDidLoad() {
super.viewDidLoad()
topLabel.center.y = self.view.frame.height + 100
hourLabel.center.y = self.view.frame.height + 100
remainingTimeLabel.center.y = self.view.frame.height + 100
UIView.animate(withDuration: 2.6, delay: 0.0, usingSpringWithDamping: 1.0, initialSpringVelocity: 3.0, options: [], animations: ({
self.topLabel.center.y = self.view.frame.height/2
}), completion: nil)
UIView.animate(withDuration: 2.5, delay: 0.1, usingSpringWithDamping: 1.0, initialSpringVelocity: 3.0, options: [], animations: ({
self.hourLabel.center.y = self.view.frame.height/2
}), completion: nil)
UIView.animate(withDuration: 2.4, delay: 0.2, usingSpringWithDamping: 1.0, initialSpringVelocity: 3.0, options: [], animations: ({
self.remainingTimeLabel.center.y = self.view.frame.height/2 - 30
}), completion: nil)
// further code...
}
I hope someone will explain what's wrong here because I'm new to this language
I suggest you familiarize yourself with the UIViewController lifecycle.
It would be best to put these animations in viewDidAppear because of this.
When the view APPEARS, you want these things to animate, there is a difference between when a view loads and when it appears.
View did Load: When a view loads into memory
View did appear: Called after view did load (and after viewWillAppear, Right when the view appears on screen
Related
My code is using a simple loop animation that is not stopping after the 5 second duration. All I want to do is have a second button that stops the animation. As you can see in my code, circle.stopAnimating() has no effect.
class VET: UIViewController {
#IBOutlet weak var circle: UIImageView!
var duration = 5
#IBAction func start(_ sender: Any) {
UIView.animate(withDuration: TimeInterval(duration), delay: 0.5, options: [.repeat, .autoreverse, .curveEaseIn, .curveEaseOut], animations: { () -> Void in
let scale = CGAffineTransform(scaleX: 0.25, y: 0.25)
self.circle.transform = scale
print("animation")
}, completion: { _ in
//if finished { or (finished: Bool)
// if isAnimating {
if self.circle.isAnimating {
self.circle.stopAnimating()
print("is animating -- stop animating")
} else {
self.circle.startAnimating()
print("start animating")
}
})
}
#IBAction func stop() {
circle.stopAnimating()
}
startAnimating and stopAnimating for change images of imageview in some interval like GIF. Here you used animation block with repeat and the completion block will only get called when the animation is interrupted. For example it gets called when the app goes in the background and comes back to the foreground again.
To stop animation after some interval you have to call forcefully removeAllAnimations after that interval. Please refer following code:
#IBOutlet weak var circle: UIImageView!
var duration = 10
func start() {
UIView.animate(withDuration: TimeInterval(duration), delay: 0.5, options: [.repeat, .autoreverse, .curveEaseIn, .curveEaseOut], animations: { () -> Void in
let scale = CGAffineTransform(scaleX: 0.25, y: 0.25)
self.circle.transform = scale
DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(self.duration * 1000)) {
// Code
self.circle.layer.removeAllAnimations()
}
print("animation")
}, completion: { _ in
let scale = CGAffineTransform(scaleX: 1.0, y: 1.0)
self.circle.transform = scale
})
}
You are using wrong approach to use animations if you need more control over it.
Instead use UIViewPropertyAnimator
The alternate property animator initializer gives you back an animator object that you need to start:
let animator = UIViewPropertyAnimator(duration: duration, curve: .linear) {
self.redSquare.backgroundColor = .green
}
animator.startAnimation()
Similarly you can use for stopping animations related to the animator
animator.stopAnimation()
I have set up 2 text by autolayouts and constraints. Now i would like to move text 1 to the right and text 2 to the left but it didnt work. Both the text should end on the center of the view (horizontally)
#IBOutlet weak var Text1: UILabel!
#IBOutlet weak var Text2: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
UIView.animate(withDuration: 5, delay: 0, options: [.beginFromCurrentState],
animations: {
self.Text1.frame.origin.x += 300
self.Text2.frame.origin.x -= 300
self.view.layoutIfNeeded()
}, completion: nil)
// Do any additional setup after loading the view.
}
Did i did anything wrong in the code?
You can use CGAffineTransform(translationX: y:) to achieve something like that:
UIView.animate(withDuration: 1.0, delay: 0.0, options: .curveEaseInOut, animations: {
self.breathingStatusLabel.transform = CGAffineTransform(translationX: self.breathingStatusLabel.bounds.origin.x + 300, y: self.breathingStatusLabel.bounds.origin.y)
}, completion: nil)
To move it back to it's originial place add this in another animation block:
self.breathingStatusLabel.transform = .identity
Note: CGAffineTransform(translationX: y:) translates the view to a provided location but it doesn't change the frame of that. So be careful about that while using this, to just animate labels you can use it.
But for example you want to move UITextFields when keyboard appears, you should change the constraints if its autolayout or change its frame. If you use CGAffineTransform(translationX: y:) you will notice that it will move the UITextField but when you tap on it nothing will work coz its frame didn't change, just the location changed.
You need to play with constraints of labels.
UIView.animate(withDuration: 5, delay: 0, options: [.beginFromCurrentState],
animations: {
//here modify constraints constant or may need to add some new constraints dynamically.
// self.Text1.frame.origin.x += 300
// self.Text2.frame.origin.x -= 300
// self.view.layoutIfNeeded()
}, completion: nil)
#IBOutlet weak var Text1: UILabel!
#IBOutlet var Text1LeftConstrin:NSLayoutConstraint!
// (Connect this with your Text1 label left constrain)
#IBOutlet weak var Text2: UILabel!
#IBOutlet var Text1LeftConstrin:NSLayoutConstraint!
//(Connect this with your Text1 label left constrain)
override func viewDidLoad() {
super.viewDidLoad()
UIView.animate(withDuration: 5, delay: 0, options: [.beginFromCurrentState],
animations: {
Text1.constant += 300
Text2.constant -= 300
self.view.layoutIfNeeded()
}, completion: nil)
// Do any additional setup after loading the view.
}
UIView.transition(with:self.yourLabel,
duration: 5.0,
options: [.autoreverse,.repeat],
animations: {
self.yourLabel.transform = CGAffineTransform(translationX: (-1 * (self.view.frame.size.width / 2) + 20), y:0)
self.yourLabel.transform = CGAffineTransform(translationX: ((self.view.frame.size.width / 2) - 20), y:0)
},
completion: nil)
I am trying to make an animation of a UILabel from off the screen to the center with a spring animation block. The first part of my code below in viewDidLoad() works perfectly, but when I add the animation block, it's like the code in the closure of the animation gets read first and it doesn't animate because the label is already in the place where I want the label to animate to. I have also tried to put this exact code in viewDidAppear() but the same thing happens.
#IBOutlet weak var follow: UILabel!
#IBOutlet weak var followX: NSLayoutConstraint!
override func viewDidLoad() {
super.viewDidLoad()
self.follow.translatesAutoresizingMaskIntoConstraints = false
self.followX.constant = self.view.frame.width/2 + follow.frame.width/2
self.view.layoutIfNeeded()
UIView.animate(withDuration: 2, delay: 2, usingSpringWithDamping: 5, initialSpringVelocity: 5, options: .curveEaseOut, animations: {
self.followX.constant = 0
self.view.layoutIfNeeded()
})
}
This is the correct way to do it (although I would try to move that animation to viewWillAppear or viewDidAppear):
#IBOutlet weak var follow: UILabel!
#IBOutlet weak var followX: NSLayoutConstraint!
override func viewDidLoad() {
super.viewDidLoad()
self.follow.translatesAutoresizingMaskIntoConstraints = false
self.followX.constant = self.view.frame.width/2 + follow.frame.width/2
self.view.layoutIfNeeded()
self.followX.constant = 0
self.view.setNeedsLayout()
UIView.animate(withDuration: 2, delay: 2, usingSpringWithDamping: 5, initialSpringVelocity: 5, options: .curveEaseOut, animations: {
self.view.layoutIfNeeded()
})
}
To work with autolayout:
You first make sure the to begin animation is ready:
self.followX.constant = self.view.frame.width/2 + follow.frame.width/2
self.view.layoutIfNeeded()
Then you change constraints to the final state of the animation:
self.followX.constant = 0
Then you tell autolayout that the constraints were changed:
self.view.setNeedsLayout()
And finally you call UIView.animate with layoutIfNeeded() to animate the change:
UIView.animate(withDuration: 2, delay: 2, usingSpringWithDamping: 5, initialSpringVelocity: 5, options: .curveEaseOut, animations: {
self.view.layoutIfNeeded()
})
override func viewDidLayoutSubviews() {
if(once)
{
once = false
self.follow.translatesAutoresizingMaskIntoConstraints = false
self.followX.constant = self.view.frame.width/2 + follow.frame.width/2
self.view.layoutIfNeeded()
}
}
and in viewDidAppear
override func viewDidAppear() {
self.followX.constant = 0
UIView.animate(withDuration: 2, delay: 0, usingSpringWithDamping: 5, initialSpringVelocity: 5, options: .curveEaseOut, animations: {
self.view.layoutIfNeeded()
})
}
I have having a nightmare with a simple problem. On my app I have clouds moving in the background (currently from left to right).
However with the background they need to be right to left.
Eddited for the more of the page code below. Hopefully this will be easier to work out where I have gone wrong.
#IBOutlet var cloud1: UIImageView!
#IBOutlet var cloud2: UIImageView!
#IBOutlet var cloud3: UIImageView!
#IBOutlet var cloud4: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func viewWillAppear(_ animated: Bool) {
cloud1.alpha = 0.0
cloud2.alpha = 0.0
cloud3.alpha = 0.0
cloud4.alpha = 0.0
}
override func viewDidAppear(_ animated: Bool) {
UIView.animate(withDuration: 2.0, delay: 0.1,
options: [],
animations: {
self.cloud1.alpha = 1.0
}, completion: nil)
UIView.animate(withDuration: 2.0, delay: 0.1,
options: [],
animations: {
self.cloud2.alpha = 1.0
}, completion: nil)
UIView.animate(withDuration: 2.0, delay: 0.1,
options: [],
animations: {
self.cloud3.alpha = 1.0
}, completion: nil)
UIView.animate(withDuration: 2.0, delay: 0.1,
options: [],
animations: {
self.cloud4.alpha = 1.0
}, completion: nil)
animateTheClouds(cloud: cloud1)
animateTheClouds(cloud: cloud2)
animateTheClouds(cloud: cloud3)
animateTheClouds(cloud: cloud4)
}
func animateTheClouds(cloud : UIImageView) {
let cloudMovingSpeed = 60.0/view.frame.size.width
let duration = (view.frame.size.width - cloud.frame.origin.x) * cloudMovingSpeed
UIView.animate(withDuration: TimeInterval(duration), delay: 0.0, options: .curveLinear, animations: {
// Animate the origin to be off the left side of the screen.
cloud.frame.origin.x = cloud.frame.size.width
}, completion: {_ in
// Reset back to the right edge of the screen
cloud.frame.origin.x = -self.view.frame.size.width
self.animateTheClouds(cloud: cloud)
})
If you want to move them from right to left then you simply need to change the starting and ending x origin.
func animateTheClouds(cloud : UIImageView) {
let cloudMovingSpeed = 60.0/view.frame.size.width
let duration = (cloud.frame.origin.x + cloud.frame.size.width) * cloudMovingSpeed
UIView.animate(withDuration: TimeInterval(duration), delay: 0.0, options: .curveLinear, animations: {
// Animate the origin to be off the left side of the screen.
cloud.frame.origin.x = -cloud.frame.size.width
}, completion: {_ in
// Reset back to the right edge of the screen
cloud.frame.origin.x = self.view.frame.size.width
self.animateTheClouds(cloud: cloud)
})
Also make sure the initial x origin is set to self.view.frame.size.width.
I have an IBOutlet Collection of buttons that I am trying to present on screen sequentially. They all start off screen fine, but as they animate in, I'd like each button to be presented on screen 0.05 seconds after the previous button. I can't figure out how to increment the delay in UIView.animateWithDuration. With the code below, they are all animating on screen at the same time.
//outlet collection
#IBOutlet var options: [UIButton]!
let increment = 0.25
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
for button in options {
button.center.y += view.bounds.height
}
}
override func viewDidLoad() {
super.viewDidLoad()
for button in options {
UIView.animateWithDuration(1.0, delay: increment, usingSpringWithDamping: 0.7, initialSpringVelocity: 0.0, options: nil, animations: {
button.center.y -= self.view.bounds.height
self.increment + 0.05
}, completion: nil)
}
}
for button in options {
UIView.animateWithDuration(1.0, delay: increment, usingSpringWithDamping: 0.7, initialSpringVelocity: 0.0, options: nil, animations: {
button.center.y -= self.view.bounds.height
}, completion: nil)
}
increment = increment + 0.05
}
Besides:
Change this
let increment = 0.25
To
var increment = 0.25
Increase the increment outside animation. Because animateWithDuration is an async method,it will return first. So,all your button have same delay.
#IBOutlet weak var button1: UIButton!
#IBOutlet weak var button2: UIButton!
#IBOutlet var options: [UIButton]!
let increment = 0.25
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
for button in options {
button.center.y += view.bounds.height
}
}
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
for button in options {
UIView.animateWithDuration(1.0, delay: increment, usingSpringWithDamping: 0.7, initialSpringVelocity: 0.0, options: nil, animations: {
button.center.y -= self.view.bounds.height
}, completion: nil)
self.increment + 0.05
}
}
Also getting error "Cannot invoke '+=' with an argument list of type '(Double, FloatLiteralConvertible)'" when using += but it will take just +
Here is the way to achieve the required delay between animations:
var i! as UInt64;
i = 0
for button in options {
// your animation with delay
UIView.animateWithDuration(1.0, delay: (i *0.05), usingSpringWithDamping: 0.7, initialSpringVelocity: 0.0, options: nil, animations: {
button.center.y -= self.view.bounds.height
}, completion: nil)
})
++i
}