Xcode ViewDidLoad animation Swift - ios

I'm having some trouble with the animations in a tabbed app in Xcode.
I have the viewDidLoad and the viewDidAppear parts, the problem is that I have two labels label1 and label2. I would like label1 appearing only once when the application loads, and label2 appearing every time I go back to the FirstView.
So the logical thing to do would be:
override func viewDidLoad(animated: Bool) {
super.viewDidLoad()
self.label1.alpha = 0
self.label2.alpha = 0
//this is the animation
UIView.animateWithDuration(2.0, animations: { () -> Void in
self.label1.alpha = 1.0
//this is what happens after a delay
[DELAY CODE]
self.label1.alpha = 0.0
})
}
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
UIView.animateWithDuration(2.0, animations: { () -> Void in
self.label2.alpha = 1.0
}
Essentially what this should do is make label1 appear and disappear only once, and make label2 appear every time firstView shows up on the screen. The problem is that I have an error in the first line telling me "Method does not override any method from its superclass". So how can I do what I am trying to achieve?

You have to remove the animated:Bool from your viewDidLoad-method. there is no such parameter in this method.
So it should look like that:
override func viewDidLoad() {

Try This :
UIView.animateWithDuration(2.0,
animations: { () -> Void in
// Your animation method
self.label1.alpha = 1.0
}) { (Bool) -> Void in
// Call your delay method here.
// Delay - 3 seconds
NSTimer.scheduledTimerWithTimeInterval(NSTimeInterval(3),
target: self,
selector: "hideLabel",
userInfo: nil,
repeats: false)
}
//
func hideLabel {
UIView.animateWithDuration(2,
animations: { () -> Void in
self.label1.alpha = 0.0
})
}

Related

UIViewPropertyAnimator not respecting duration and delay parameters

I have a very strange issue in my app. I'm using UIViewPropertyAnimator to animate changing images inside UIImageView.
Sounds like trivial task but for some reaso my view ends up changing the image instantly so I end up with images flashing at light speed instead of given duration and delay parameters.
Here's the code:
private lazy var images: [UIImage?] = [
UIImage(named: "widgethint"),
UIImage(named: "shortcuthint"),
UIImage(named: "spotlighthint")
]
private var imageIndex = 0
private var animator: UIViewPropertyAnimator?
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
animator = repeatingAnimator()
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
animator?.stopAnimation(true)
}
private func repeatingAnimator() -> UIViewPropertyAnimator {
return .runningPropertyAnimator(withDuration: 2, delay: 6, options: [], animations: {
self.imageView.image = self.images[self.imageIndex]
}, completion: { pos in
if self.imageIndex >= self.images.count - 1 {
self.imageIndex = 0
} else {
self.imageIndex += 1
}
self.animator = self.repeatingAnimator()
})
}
So, according to the code animation should take 2 seconds to complete and start after 6 seconds delay, but it starts immediately and takes milliseconds to complete so I end up with horrible slideshow. Why could it be happening?
I also tried using the UIViewPropertyAnimator(duration: 2, curve: .linear) and calling repeatingAnimator().startAnimation(afterDelay: 6) but the result is the same.
Okay, I've figured it out, but it's still a bit annoying that such simple task cannot be done using the "Modern" animation API.
So, animating images inside UIImageView is apparently not supported by UIViewPropertyAnimator, during debugging I tried animating view's background color and it was working as expected. So I had to use Timer instead and old UIView.transition(with:) method
Here's the working code:
private let animationDelay: TimeInterval = 2.4
private var animationTimer: Timer!
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
setAnimation(true)
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
setAnimation(false)
}
private func setAnimation(_ enabled: Bool) {
guard enabled else {
animationTimer?.invalidate()
animationTimer = nil
return
}
animationTimer = Timer.scheduledTimer(withTimeInterval: animationDelay, repeats: true) { _ in
UIView.transition(with: self.imageView, duration: 0.5, options: [.curveLinear, .transitionCrossDissolve], animations: {
self.imageView.image = self.images[self.imageIndex]
}, completion: { succ in
guard succ else {
return
}
if self.imageIndex >= self.images.count - 1 {
self.imageIndex = 0
} else {
self.imageIndex += 1
}
})
}
}
Hopefully it will save someone some headaches in the future

A infinite animation on button makes it unclickable in Swift

(Swift)
I recently noticed a button on my viewController was not clickable because of the animation that is infinitely assigned to it. However, I really need to be able to click that button.
override func viewDidLoad() {
super.viewDidLoad()
self.startGradientFunction()
}
// Function that changes colors over time
func startGradientFunction() {
UIView.animate(withDuration: 2.5, animations: { () -> Void in
self.view.backgroundColor = self.hexStringToUIColor(hex: "66B8FF")
self.readyBtn.backgroundColor = self.hexStringToUIColor(hex: "3F86C3")
}, completion: { (finished: Bool) in
UIView.animate(withDuration: 2.5, animations: { () -> Void in
self.view.backgroundColor = self.hexStringToUIColor(hex: "57FFC4")
self.readyBtn.backgroundColor = self.hexStringToUIColor(hex: "20CD91")
}, completion: { (finished: Bool) in
UIView.animate(withDuration: 2.5, animations: { () -> Void in
self.view.backgroundColor = self.hexStringToUIColor(hex: "9C49FF")
self.readyBtn.backgroundColor = self.hexStringToUIColor(hex: "7E3BCD")
}, completion: { (finished: Bool) in
self.startGradientFunction()
})
})
})
}
How can I make it selectable again?
I would be glad to know someone can help me.
You need to add user interaction to your animation options:
https://developer.apple.com/documentation/uikit/uiview/animationoptions/1622440-allowuserinteraction
(But you should also fix your way of doing a repeating animation so that you don’t recurse infinitely.)

UIKeyboardWillShowNotification is called three times

I need to move a UIView up as soon as the keyboard will become visible. But the problem I'm facing right now is that my UIKeyboardWillShowNotification is called three times when I'm using a custom Keyboard (e.g. SwiftKey) which results in a bad animation.
Is there a way to handle only the last notification? I could easily dodge the first one because the height is 0, but the second one looks like a valid height and I don't find an answer on how to solve this.
Here is what I've so far:
override func viewDidLoad() {
super.viewDidLoad()
NSNotificationCenter.defaultCenter().addObserver(self, selector: "keyboardWillAppear:", name: UIKeyboardWillShowNotification, object: nil)
NSNotificationCenter.defaultCenter().addObserver(self, selector: "keyboardWillDisappear:", name: UIKeyboardWillHideNotification, object: nil)
}
override func viewWillDisappear(animated: Bool) {
super.viewWillDisappear(animated)
NSNotificationCenter.defaultCenter().removeObserver(self)
}
func keyboardWillAppear(notification: NSNotification){
print("keyboard appear")
if let keyboardSize = (notification.userInfo?[UIKeyboardFrameEndUserInfoKey] as? NSValue)?.CGRectValue() {
print("with height: \(keyboardSize.height)")
if keyboardSize.height == 0.0 {
return
}
self.txtViewBottomSpace.constant = keyboardSize.height
UIView.animateWithDuration(0.4, animations: { () -> Void in
self.view.layoutIfNeeded()
})
}
}
func keyboardWillDisappear(notification: NSNotification){
print("Keyboard disappear")
self.txtViewBottomSpace.constant = 0.0
UIView.animateWithDuration(0.4, animations: { () -> Void in
self.view.layoutIfNeeded()
})
}
My Log output is:
keyboard appear
with height: 0.0
keyboard appear
with height: 216.0
keyboard appear
with height: 258.0
Keyboard disappear
So is there any way to only handle the third notification and "ignore" the first two?
Set all bellow fields to NO can resolve this problem.
Capitalizaion: None
Correction: No
Smart Dashes: No
Smart insert: No
Smart Quote: No
Spell Checking: No
Change the notification name UIKeyboardDidShowNotification and UIKeyboardDidHideNotification then solve the problem
override func viewDidLoad() {
super.viewDidLoad()
NSNotificationCenter.defaultCenter().addObserver(self, selector: "keyboardWillAppear:", name: UIKeyboardDidShowNotification, object: nil)
NSNotificationCenter.defaultCenter().addObserver(self, selector: "keyboardWillDisappear:", name: UIKeyboardDidHideNotification, object: nil)
}
override func viewWillDisappear(animated: Bool) {
super.viewWillDisappear(animated)
NSNotificationCenter.defaultCenter().removeObserver(self)
}
func keyboardWillAppear(notification: NSNotification){
print("keyboard appear")
if let keyboardSize = (notification.userInfo?[UIKeyboardFrameEndUserInfoKey] as? NSValue)?.CGRectValue() {
print("with height: \(keyboardSize.height)")
if keyboardSize.height == 0.0 {
return
}
self.txtViewBottomSpace.constant = keyboardSize.height
UIView.animateWithDuration(0.4, animations: { () -> Void in
self.view.layoutIfNeeded()
})
}
}
func keyboardWillDisappear(notification: NSNotification){
print("Keyboard disappear")
self.txtViewBottomSpace.constant = 0.0
UIView.animateWithDuration(0.4, animations: { () -> Void in
self.view.layoutIfNeeded()
})
}
I suggest to replace the static animation duration (0.4) with the animation duration of the keyboard, returned in the userInfo dictionary of the notification under UIKeyboardAnimationDurationUserInfoKey.
In this way your animation will be in sync with the keyboard animation. You can also retrieve the animation curve used by the keyboard with the UIKeyboardAnimationCurveUserInfoKey key.
func keyboardWillAppear(notification: NSNotification){
print("keyboard appear")
if let keyboardSize = (notification.userInfo?[UIKeyboardFrameEndUserInfoKey] as? NSValue)?.CGRectValue() {
let animationDuration = notification.userInfo?[UIKeyboardAnimationDurationUserInfoKey]?.doubleValue;
print("with height: \(keyboardSize.height)")
if keyboardSize.height == 0.0 {
return
}
self.txtViewBottomSpace.constant = keyboardSize.height
UIView.animateWithDuration(animationDuration!, delay: 0.0, options: .BeginFromCurrentState, animations: { () -> Void in
self.view.layoutIfNeeded()
})
}
}
func keyboardWillDisappear(notification: NSNotification){
print("Keyboard disappear")
let animationDuration = notification.userInfo?[UIKeyboardAnimationDurationUserInfoKey]?.doubleValue;
self.txtViewBottomSpace.constant = 0.0
UIView.animateWithDuration(animationDuration!, delay: 0.0, options: .BeginFromCurrentState, animations: { () -> Void in
self.view.layoutIfNeeded()
})
}
The reason for this is because keyboards can have different sizes, especially third party ones. So the first notification you receive will always be for the default system height, and any you receive after that will include the new heights of a third party keyboard extension if one is loaded. In order to get around this, in your method that handles the notification, you need to get the height originally, and then set that as a default height (I think 226). Then set a variable to this first height, and then for resulting calls to the notification method you can check if the new height is greater than the original height, and if it is you can find the delta, and then readjust your frames accordingly.

UILabel is not appearing in delayed sequence as expected

I want to make my UILabels appear in a delayed sequence. Thus one after another.
The code below works when I make them fade in using the alpha value but it doesn't do what I want it to do when I use the .hidden property of the UILabels.
The code makes my UILabels appear at all the same time instead of sum1TimeLabel first after 5 seconds, sum2TimeLabel second after 30 seconds and finally sum3TimeLabel third after 60 seconds. What am I doing wrong?
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
UIView.animateWithDuration(5.0, animations: {
self.sum1TimeLabel!.hidden = false;
})
UIView.animateWithDuration(30.0, animations: {
self.sum2TimeLabel!.hidden = false;
})
UIView.animateWithDuration(60.0, animations: {
self.sum3TimeLabel!.hidden = false;
})
}
As explained by SO user #matt in this answer, you can simply create a delay function with Grand Central Dispatch instead of using animateWithDuration, as animateWithDuration is usually meant for animating things over a period of time, and since the value you are animating is a Bool, it can't animate it.
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
delay(5.0) {
self.sum1TimeLabel?.hidden = false
}
delay(30.0) {
self.sum2TimeLabel?.hidden = false
}
delay(60.0) {
self.sum3TimeLabel?.hidden = false
}
}
func delay(delay:Double, closure:()->()) {
dispatch_after(
dispatch_time(
DISPATCH_TIME_NOW,
Int64(delay * Double(NSEC_PER_SEC))
),
dispatch_get_main_queue(), closure)
}
// Try this
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
self.sum1TimeLabel!.hidden = true;
self.sum2TimeLabel!.hidden = true;
self.sum3TimeLabel!.hidden = true;
UIView.animateWithDuration(5.0, animations: {
}, completion: {
self.sum1TimeLabel!.hidden = false;
})
UIView.animateWithDuration(30.0, animations: {
}, completion: {
self.sum2TimeLabel!.hidden = false;
})
UIView.animateWithDuration(60.0, animations: {
}, completion: {
self.sum3TimeLabel!.hidden = false;
})
}
class ViewController: UIViewController {
#IBOutlet weak var label1: UILabel!
#IBOutlet weak var label2: UILabel!
#IBOutlet weak var label3: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
label1.alpha = 0
label2.alpha = 0
label3.alpha = 0
UIView.animateWithDuration(5.0, animations: { () -> Void in
print("animation on label1")
self.label1.alpha = 0.2
}) { (b) -> Void in
print("complete on label1")
self.label1.alpha = 1
UIView.animateWithDuration(5.0, animations: { () -> Void in
print("animation on label2")
self.label2.alpha = 0.2
}, completion: { (b) -> Void in
print("complete on label2")
self.label2.alpha = 1
UIView.animateWithDuration(5.0, animations: { () -> Void in
print("animation on label3")
self.label3.alpha = 0.2
}, completion: { (b) -> Void in
print("complete on label3")
self.label3.alpha = 1
})
})
}
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
You are NOT able to animate hidden property. there is two states only. how to change hidden continuously with time? instead you can animate alpha :-). try change the alpha value inside animation block to 0, or to 1.

My Swift code is not calling deinit because of a closure strong reference or NSTimer. How can I solve it?

override func viewDidLoad() {
super.viewDidLoad()
fadeBackground()
NSTimer.scheduledTimerWithTimeInterval(self.fadeTime, target: self, selector: Selector("fadeBackground"), userInfo: nil, repeats: true)
}
func fadeBackground(){
var spiralView: UIImageView?
var subviews = self.view.subviews
for v in subviews{
if v.isKindOfClass(UIImageView){
spiralView = v as? UIImageView
}
}
UIView.animateWithDuration(self.fadeTime, delay: 0, options: UIViewAnimationOptions.AllowUserInteraction, animations: { [unowned self] () -> Void in
var randomIndex = Int(arc4random_uniform(UInt32(CONSTANTS.MainColorScheme.count)))
var randomIndex2 = Int(arc4random_uniform(UInt32(CONSTANTS.MainColorScheme.count)))
self.view.backgroundColor = CONSTANTS.MainColorScheme[randomIndex]
if spiralView != nil {
spiralView!.tintColor = CONSTANTS.MainColorScheme[randomIndex2]
}
}) { (stuff Bool) -> Void in
}
}
As you can see, I already have [unowned self] in the closure. I'm not sure why this view controller is still not calling deinit(). I guess there's still a memory leak somewhere?
Do I need to stop the timer on viewDidDisappear? How do I do that?
Solved my question.
I had to invalidate my timer on viewWillDisappear.
var fader: NSTimer?
override func viewWillDisappear(animated: Bool) {
println("lobby viewWillDisappear")
super.viewWillDisappear(animated)
self.fader!.invalidate()
self.fader = nil
}

Resources