UIView - Animate with duration seems to ignore duration - ios

I'm facing a problem : I have a very simple custom UIView which groups one UITextField and one UILabel.
The UILabel is on the UITextField, and I want that the UILabel go up to 40px when user begins to edit the UITextField.
I'm using constraints to set the textfield's frame and the label's frame.
So I just set the delegate in my custom view with UITextFieldDelegate protocol, and implement the delegate method : textFieldDidBeginEditing, as bellow :
I'm just animating the constraint (center Y) of my label in the delegate method.
func textFieldDidBeginEditing(textField: UITextField) {
self.centerVerticallyLabelConstraint.constant = -40
UIView.animateWithDuration(0.5) {
self.textFieldLabel.layoutIfNeeded()
}
}
What happens is that the UILabel goes up correctly, but it goes up instantly. It ignores the duration of 0.5 in my animation. I don't know why :
I'm not animating something else at the same time, I only have this animation.
I haven't disable animations for UIView.
The delegate is set to my UITextfield.
Any idea?

Try calling layoutIfNeeded on your view instead of textview.
UIView.animate(withDuration: 0.5) {
self.view.layoutIfNeeded()
}
Hope this helps.

need to call self.view.layoutIfNeeded() not self.textFieldLabel.layoutIfNeeded()
Thanks

Try Below code to execute:
func textFieldDidBeginEditing(textField: UITextField) {
UIView.animate(withDuration: 0.5) {
self.textFieldLabel.frame.origin.y -= 40
}
}

For me, the issue seems to be because I was already on the main thread & I called the animation block inside a GCD main.async call.
ie I had
DispatchQueue.main.async {
<# update the constraint s#>
self.animatedViewsParent.setNeedsLayout()
animatedView.setNeedsLayout()
UIView.animate(withDuration: 2.0) {
self.animatedViewsParent.layoutIfNeeded()
}
}
The view animates moves but does not animate gradually over 2.0 seconds. Removing the outer DispatchQueue.main.async {} solves the problem.

Related

About iOS Constraint update Animation - Animation block + layoutIfNeeded

Hello I'm trying to understand view update cycle
I could understand almost things, but there are some mysterious things
I learn that animation blocks are also triggered at update cycle
To try to understand layoutIfNeeded in Animation block, I wrote some sample codes like below
class MyView: UIView {
override func layoutSubviews() {
super.layoutSubviews()
print("Layout Subviews")
}
}
class ViewController: UIViewController {
#IBOutlet weak var blueHeight: NSLayoutConstraint!
#IBAction func heightPressed(_ sender: AnyObject) {
if self.blueHeight.constant == 25.0 {
self.blueHeight.constant = self.view.bounds.height - 100.0
} else {
self.blueHeight.constant = 25.0
}
self.view.setNeedsLayout()
UIView.animate(withDuration: 2.0, animations: {
print("animation block")
self.view.layoutIfNeeded() // ----- **
}) { (_) in
print("animation ended")
}
}
override func viewDidLoad() {
super.viewDidLoad()
}
}
The output was like this
"Layout Subviews"
"Layout Subviews" ---- because of initial settings
"Animation block"
"Layout Subviews"
"Animation ended"
but when i changed layoutIfNeeded to setNeedsLayout the output is changed
"Layout Subviews"
"Layout Subviews" ---- because of initial settings
"Animation block"
"Animation ended"
"Layout Subviews"
so i understand that animation block executed first and view updates, so animation block has higher priority than view's update in update cycle
is that right? If i was wrong, i want to understand relationship between view's update cycles and animation block
What is inside the animation block, if it is animatable, does not run until animation time at the end of the transaction.
layoutIfNeeded counts as animatable. What it does in an animation block is: it causes the changes in frame etc. performed in code or by autolayout in layoutSubviews to be animated rather than immediate.
Basically, this is a way of wrapping the next layout moment in an animation.

View not displayed immediately after layoutIfNeeded is called

I'm trying to have a simple slide-down-from-top animation using auto layout (with the help of SnapKit).
Below is the code I use to generate a view, set its constraints and force a reload (thus display) of said view. In the same code cope I change the top constraint to a different value and call layoutIfNeeded in an animation block.
What happens instead though is that the view is displayed at the intended position without any animations whatsoever.
Here is the code called in a method after a short delay (for other purposes):
DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(4), execute: {
self.view.addSubview(view)
view.backgroundColor = .black
view.snp.makeConstraints { (make) -> Void in
make.left.right.equalToSuperview()
make.height.equalTo(200)
make.bottom.equalTo(self.view.snp.top) // Pin view to top to prepare slide-down animation
}
view.setNeedsLayout()
view.layoutIfNeeded()
view.snp.updateConstraints { (make) -> Void in
make.bottom.equalTo(self.view.snp.top).offset(200)
}
view.setNeedsLayout()
UIView.animate(withDuration: 5, animations: {
view.layoutIfNeeded()
})
})
Thanks to In Swift, how do I animate a UIView with auto-layout like a page sliding in? I found the culprit.
Although I don't 100% understand why this is, but layoutIfNeeded needs to be called from the superview, not the view itself.
So calling self.view.layoutIfNeeded() is the correct way. Note that view is the currently being added UIView, whereas self.view is the superview, where view is being added to.

Why not all NSLayoutconstraints changing with animation, and how to fix it?

I have two subViews in ViewController that located one after another (bottom of first connected to the top of second)
First view is changing its height animated (example below), so I expected that second view will descend with animation too, but its not..
How to make it all animated?
Animation block for first view
func animate(){
layoutIfNeeded()
UIView.animateWithDuration(1){
self.labelHeight.constant = 70 // this is constraint
self.layoutIfNeeded()
}
}
Constraints between sibling views are added to their shared super view so you should call layoutIfNeeded() on it. For example:
func animate(){
self.superview?.layoutIfNeeded()
UIView.animateWithDuration(1){
self.labelHeight.constant = 70 // this is constraint
self.superview?.layoutIfNeeded()
}
}

UIView disappears when keyboard pops up for UITextField

I have an UITextField inside of a container view, when the keyboard pops up the container view disappears. In the same container view is a UICollectionView, whose custom cells each contains a UITextField and the keyboard works just fine for them.
I printed out the frame of the container view in the animation function that is called by keyboardWillShow and the container view's frames are the same for both cases, so it looks like the container view just disappears (instead of "not moved" as i thought) when that specific UITextField is selected. The relevant code is :
func keyboardWillShow(notification: NSNotification) {
if let userInfo = notification.userInfo {
if let keyboardSize = (userInfo[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.CGRectValue() {
kbHeight = keyboardSize.height
self.animateDurationView(true)
}
}
}
func keyboardWillHide(notification: NSNotification) {
self.animateDurationView(false)
}
func animateDurationView(up: Bool) {
var movement = up ? -kbHeight : kbHeight
println(movement)
UIView.animateWithDuration(0.3, animations: {
self.durationView.frame = CGRectOffset(self.durationView.frame, 0, movement)
println(self.durationView.frame)
})
}
the screenshots can be found in this thread: KeyboardWillShow only moves container UIView for certain UITextFields
EDIT: at this point I am almost certain that it is the auto layout constraints that are screwing with me.
Try adding commitAnimations at the end of each animation transaction :) Me too I faced this issue... Now it is working fine.. You can add delegate to the textfield and you can move it delegate methods DidBeginEditing and DidEndEditing methods for textfield :)
Just had the same issue. What I did wrong was trying to apply the movement to an inner view. Moving the outermost view made it work as this outer view does not have any auto layout constraints.
So to answer the question - if anyone else should get here
Only move the self.view

Swift dismiss custom keyboard strange behaviour

I have the most strange situation with a custom keyboard. First of all, I have set up a dummy view for the textfield, in order to hide the stock keyboard let dummyView : UIView = UIView(frame: CGRect(x: 0, y: 0, width: 1, height: 1))
amountField.inputView = dummyView
Then I have my custom keyboard which animates when editing begins in the text field
func textFieldDidBeginEditing(textField: UITextField) {
keyboardContainer.hidden = false
UIView.animateWithDuration(0.6, animations: {
self.keyboardContainer.frame = self.keyboardScreenPosition!
}, completion: {
finished in
if finished {
//just in case
}
})
}
Also, I have set up a button which should end editing and hide my custom keyboard
#IBAction func calculeaza(sender: AnyObject) {
self.amountField.resignFirstResponder()
UIView.animateWithDuration(0.6, animations: {
self.keyboardContainer.frame.origin.y = self.view.bounds.height
}, completion: {
finished in
if finished {
}
})
}
The most strange part comes with the resignFirstResponder(). Let me explain: If that part is not included, the keyboard hides just fine (but the text field keeps on blinking cursor which is not an option ofc). If the resigning part is included, the keyboard animates from the top to its current position, then on pressing the button again it does slides down as intended. I am really puzzled on why is this happening...I debugged the sizes of the view and the heights are ok so it should slide down from the beginning. I really dont understand what is happening. Any help is much appreciated, many thanks!
EDIT: another strange effect is if I move the resign part (or the super end editing) in the animation ending closure. The keyboard slides just fine, then it reappears on screen
This sounds like an issue with autolayout constraints. Those are updated after the animateWithDuration which is potentially causing this odd behavior. If you have (a) constraint(s) in the Storyboard used for autolayout, try updating that(/those). If you haven't already, you'll need to add it as an IBOutlet and animate the autolayout change in the duration.
For example, say the constraint is called theRelevantConstraint. Then replace the middle lines
self.theRelevantConstraint.constant = valueItShouldBe
UIView.animateWithDuration(0.6, animations: {
self.view.layoutIfNeeded()
}, completion: {
finished in
if finished {
}
})

Resources