issues with UIView animation swift 4 Xcode9 - ios

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()
})
}

Related

Swift tvOS - pause UIImage animations when user presses MENU button

For my intro page I have multiple images animations in sync with music to make a cool intro page. Problem is when I tap MENU and return to the app, all of the animations have completed. I have implemented a function to pause the music, yet need to do the same to the UIImage animations. FYI, it is the Mortal Kombat theme song synced with the characters it says in the song. I have not worked with animations before so looking at other solutions on SO and trying them has not worked.
// Mortal Kombat Good Guys
#IBOutlet weak var luiKangImage: UIImageView!
#IBOutlet weak var johnnyCageImage: UIImageView!
#IBOutlet weak var raidenImage: UIImageView!
#IBOutlet weak var kitanaImage: UIImageView!
#IBOutlet weak var sonyaImage: UIImageView!
#IBOutlet weak var nightWolfImage: UIImageView!
#IBOutlet weak var cassieCageImage: UIImageView!
#IBOutlet weak var kenshiTakahashiImage: UIImageView!
#IBOutlet weak var kungLaoImage: UIImageView!
#IBOutlet weak var jaxBriggsImage: UIImageView!
// Mortal Kombat Bad Guys
#IBOutlet weak var scorpionImage: UIImageView!
#IBOutlet weak var kanoImage: UIImageView!
#IBOutlet weak var reptileImage: UIImageView!
#IBOutlet weak var subZeroImage: UIImageView!
#IBOutlet weak var shangTsungImage: UIImageView!
#IBOutlet weak var goroImage: UIImageView!
#IBOutlet weak var shaoKhanImage: UIImageView!
#IBOutlet weak var tanyaImage: UIImageView!
#IBOutlet weak var quanChiImage: UIImageView!
#IBOutlet weak var mileenaImage: UIImageView!
var player: AVAudioPlayer?
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
//Set to execute function playSound
playSound()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
setInMotionImagePaths()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
setInMotionImageTimePaths()
}
func setInMotionImagePaths() {
// Mortal Kombat Good Guys Image Animation Paths
luiKangImage.center.x -= view.bounds.width
johnnyCageImage.center.y += view.bounds.width
raidenImage.center.y -= view.bounds.width
kitanaImage.center.x -= view.bounds.width
sonyaImage.center.x -= view.bounds.width
nightWolfImage.center.y += view.bounds.width
cassieCageImage.center.y -= view.bounds.width
kenshiTakahashiImage.center.y += view.bounds.width
kungLaoImage.center.x -= view.bounds.width
jaxBriggsImage.center.y -= view.bounds.width
// Mortal Kombat Bad Guys Image Animation Paths
scorpionImage.center.x += view.bounds.width
kanoImage.center.y += view.bounds.width
reptileImage.center.x += view.bounds.width
subZeroImage.center.x += view.bounds.width
shangTsungImage.center.y += view.bounds.width
goroImage.center.x += view.bounds.width
shaoKhanImage.center.y += view.bounds.width
tanyaImage.center.x += view.bounds.width
quanChiImage.center.y += view.bounds.width
mileenaImage.center.x += view.bounds.width
}
func setInMotionImageTimePaths() {
playSound()
// Mortal Kombat Good Guys Animation Time and Path
UIView.animate(withDuration: 5.0, delay: 0.6, options: [], animations: {
self.luiKangImage.center.x += self.view.bounds.width
}, completion: nil)
//DONT TOUCH TIME
UIView.animate(withDuration: 5.0, delay: 2.3, options: [], animations: {
self.johnnyCageImage.center.y -= self.view.bounds.width
}, completion: nil)
//DONT TOUCH TIME
UIView.animate(withDuration: 5.0, delay: 1.2, options: [], animations: {
self.raidenImage.center.y += self.view.bounds.width
}, completion: nil)
//DONT TOUCH TIME
UIView.animate(withDuration: 5.0, delay: 10.0, options: [], animations: {
self.kitanaImage.center.x += self.view.bounds.width
}, completion: nil)
UIView.animate(withDuration: 5.0, delay: 8.5, options: [], animations: {
self.sonyaImage.center.x += self.view.bounds.width
}, completion: nil)
//DONT TOUCH TIME
UIView.animate(withDuration: 5.0, delay: 11.0, options: [], animations: {
self.nightWolfImage.center.y -= self.view.bounds.width
}, completion: nil)
UIView.animate(withDuration: 5.0, delay: 12.0, options: [], animations: {
self.cassieCageImage.center.y += self.view.bounds.width
}, completion: nil)
UIView.animate(withDuration: 5.0, delay: 13.0, options: [], animations: {
self.kenshiTakahashiImage.center.y -= self.view.bounds.width
}, completion: nil)
UIView.animate(withDuration: 5.0, delay: 14.0, options: [], animations: {
self.kungLaoImage.center.x += self.view.bounds.width
}, completion: nil)
UIView.animate(withDuration: 5.0, delay: 15.0, options: [], animations: {
self.jaxBriggsImage.center.y += self.view.bounds.width
}, completion: nil)
// Mortal Kombat Bad Guys Animation Time and Path
UIView.animate(withDuration: 4.0, delay: 5.5, options: [], animations: {
self.scorpionImage.center.x -= self.view.bounds.width
}, completion: nil)
//DONT TOUCH TIME
UIView.animate(withDuration: 2.0, delay: 0.0, options: [], animations: {
self.kanoImage.center.y -= self.view.bounds.width
}, completion: nil)
//DONT TOUCH TIME
UIView.animate(withDuration: 5.0, delay: 11.0, options: [], animations: {
self.reptileImage.center.x -= self.view.bounds.width
}, completion: nil)
UIView.animate(withDuration: 4.0, delay: 7.0, options: [], animations: {
self.subZeroImage.center.x -= self.view.bounds.width
}, completion: nil)
//DONT TOUCH TIME
UIView.animate(withDuration: 5.0, delay: 10.3, options: [], animations: {
self.shangTsungImage.center.y -= self.view.bounds.width
}, completion: nil)
UIView.animate(withDuration: 5.0, delay: 11.5, options: [], animations: {
self.goroImage.center.x -= self.view.bounds.width
}, completion: nil)
UIView.animate(withDuration: 5.0, delay: 12.5, options: [], animations: {
self.shaoKhanImage.center.y -= self.view.bounds.width
}, completion: nil)
UIView.animate(withDuration: 5.0, delay: 13.5, options: [], animations: {
self.tanyaImage.center.x -= self.view.bounds.width
}, completion: nil)
UIView.animate(withDuration: 5.0, delay: 15.0, options: [], animations: {
self.quanChiImage.center.y -= self.view.bounds.width
}, completion: nil)
UIView.animate(withDuration: 5.0, delay: 14.5, options: [], animations: {
self.mileenaImage.center.x -= self.view.bounds.width
}, completion: nil)
}
func playSound() {
let url = Bundle.main.url(forResource: "MortalKombatSong", withExtension: "mp3")!
do {
player = try AVAudioPlayer(contentsOf: url)
guard let player = player else { return }
player.prepareToPlay()
player.play()
player.numberOfLoops = 0
} catch let error {
print(error.localizedDescription)
}
}
func stopSound() {
if player?.isPlaying ?? true {
player?.stop()
}
}
How I pause the music in App Delegate
import AVFoundation
var player: AVAudioPlayer?
func applicationDidEnterBackground(_ application: UIApplication) {
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
player?.pause()
}
You could try out this answer. I don't believe it's possible to pause the actual view animations since they start as soon as they're passed to the animation block and you don't get any references to them

UILabel moving left or right Swift

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)

Swift - Animation Direction

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.

Proper way to transition to other ViewController

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

Incrementing a parameter in UIView.animateWithDuration in Swift

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
}

Resources