UITextField text jumps when animating width constraint - ios

I'm experiencing a glitch where my UITextField's text jumps to its final position (it doesn't animate) when animating the textfield's width constraint. Take a look at this gif:
When the "Grow" button is tapped, the textfield's width grows. But "hello world" jumps immediately to the center instead of gliding there. When the "Shrink" button is tapped, "hello world" jumps immediately back to the left.
My animation function looks like this:
func animateGrowShrinkTextFields(grow: Bool) {
if grow {
UIView.animate(withDuration: 0.5, animations: {
self.widthConstraint.constant = 330
self.view.layoutIfNeeded()
}, completion: nil)
} else {
UIView.animate(withDuration: 0.5, animations: {
self.widthConstraint.constant = 100
self.view.layoutIfNeeded()
}, completion: nil)
}
}
I have tried the following list suggestions; none of them worked.
I called self.view.layoutIfNeeded() and self.helloWorldTextField.layoutIfNeeded() before and within the animation block as suggested in this answer: https://stackoverflow.com/a/32996503/2179970
I tried self.view.layoutSubviews and self.helloWorldTextField.layoutSubview as suggested in this answer: https://stackoverflow.com/a/30845306/2179970
Also tried setNeedsLayout() UITextField text jumps iOS 9
I even tried changing the font as suggested here: https://stackoverflow.com/a/35681037/2179970
I tried resignFirstResponder (although though I never tap or edit the textfield in my tests, so it should not involve the firstResponder) as suggested here: https://stackoverflow.com/a/33334567/2179970
I tried subclassing UITextField as seen here: https://stackoverflow.com/a/40279630/2179970
I also tried using a UILabel and got the same jumpy result.
The following question is also very similar to mine but does not have an answer yet: UITextfield text position not animating while width constraint is animated
Here is my project on Github: https://github.com/starkindustries/ConstraintAnimationTest

Solution Demo
I've found a working solution. It feels a little hackish but it works. Here is a gif of the final result. Notice that helloWorldTextField has a blue border to show its location within the second textfield behind it.
Instructions
Make two textfields: helloWorldTextField (the original from the question) and borderTextField (a new textfield). Remove helloWorldTextFields's border and background color. Keep borderTextField's border and background color. Center helloWorldTextField within borderTextField. Then animate the width of borderTextField.
Github link and Code
Here is the project on Github: https://github.com/starkindustries/ConstraintAnimationTest
Here is the code within MyViewController class. Everything else is setup in the storyboard which can be viewed on Github at the link above.
class MyViewController: UIViewController {
// Hello World TextField Border var
#IBOutlet weak var borderTextFieldWidth: NSLayoutConstraint!
// Button Vars
#IBOutlet weak var myButton: UIButton!
var grow: Bool = false
func animateGrowShrinkTextFields(grow: Bool, duration: TimeInterval) {
if grow {
UIView.animate(withDuration: duration, animations: {
self.borderTextFieldWidth.constant = 330
self.view.layoutIfNeeded()
}, completion: { (finished: Bool) in
print("Grow animation complete!")
})
} else {
UIView.animate(withDuration: duration, animations: {
self.borderTextFieldWidth.constant = 115
self.view.layoutIfNeeded()
}, completion: { (finished: Bool) in
print("Shrink animation complete!")
})
}
}
#IBAction func toggle(){
let duration: TimeInterval = 1.0
grow = !grow
let title = grow ? "Shrink" : "Grow"
myButton.setTitle(title, for: UIControlState.normal)
animateGrowShrinkTextFields(grow: grow, duration: duration)
}
}
Notes and References
What led me to this solution was #JimmyJames's comment: "You are just animating the UITextField width, but the content inside is not animated."
I researched how to animate font changes and came across this question: Is there a way to animate changing a UILabel's textAlignment?
In that question #CSmith mentioned that "you can animate the FRAME, not the textAlignment" https://stackoverflow.com/a/19251634/2179970
The accepted answer in that question suggests to use a UILabel within another frame. https://stackoverflow.com/a/19251735/2179970
Hope this helps anyone else who comes across this problem. If anyone has another way to solve this, please post a comment or another answer. Thanks!

Another solution for the issue is set yourLabel.contentMode = .center on init, and animate in animation block as usually

Related

UIStackView show/hide animation is not working properly

I'm using UIStackView and it contains 3 UIView instances, which has fixed height
I'm trying to hide these subviews by clicking button
first and second view show/hide well with proper animation
but last view doesn't animate
class ViewController: UIViewController {
private var flag: Bool = true
#IBOutlet weak var targetView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func buttonDidTapped(_ sender: Any) {
flag = !flag
UIView.animate(withDuration: 0.5) {
self.view.layoutIfNeeded()
self.targetView.isHidden = !self.flag
}
}
}
The issue is the way stack views change their frames when hiding an arranged subview.
Easiest way to see what's happening:
set your Green view to Alpha: 0.5
toggle .isHidden on the Blue view
You'll see that the 50% translucent Green view "slides up over" the Blue view... the Blue view does not "shrink in height" during the animation.
To solve your specific issue, set Clips To Bounds to true on your stack view. Now, when you toggle .isHidden on your Green view, the animation will look correct.
That will not change the "slide over" appearance if you have translucent views, but that's a different issue.
As a side note, you can simplify your code and get rid of the flag like this:
UIView.animate(withDuration: 0.5) {
// not needed
//self.view.layoutIfNeeded()
self.targetView.isHidden.toggle()
}
Try change your code from:
UIView.animate(withDuration: 0.5) {
self.view.layoutIfNeeded()
self.targetView.isHidden = !self.flag
}
to:
self.targetView.isHidden = !self.flag
UIView.animate(withDuration: 0.5) {
self.view.layoutIfNeeded()
}
Looks like you animate before change.

Weird animation behavior using Swift 4 and UIView

I really can't figure out what's wrong with my code. Let me explain in detail what was my aim:
I have two UIView in the same UIViewController. They're called "redSquare" and "greenSquare".
When the UIViewController is presented I want to animate the redSquare in order to move on the Y-axis till it reaches the top-border of the greenSquare.
This is how I set the xCode project:
The behavior that I got is completely the opposite and I really can't understand why and what's happening:
Any tips or explanations for this?
Okay. Part of the problem is that you're aligning the center Y... which means that you're trying to break constraints with your animation.
Another part of the problem is that you are doing your animation in viewDidLoad, which totally runs before viewWillAppear and viewDidAppear get called.
For my own animations, I usually animate a constraint.
That is, get rid of the center-Y constraint for your red box and add a new constraint putting the red box some Y distance from the bottom of the superview. Connect this new constraint to an outlet and then you can animate like this:
#IBOutlet weak var redYConstraint : NSLayoutConstraint!
override func viewDidLoad() {
super.viewDidLoad()
// This line sets the red box to be in the center Y of the green box
self.redYConstraint.constant = self.greenSquare.frame.midY
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
UIView.animate(withDuration: 3.0, delay: 2.0, options: UIViewAnimationOptions.curveEaseIn, animations: {
self.redYConstraint.constant = self.greenSquare.frame.maxY
self.view.layoutIfNeeded()
}, completion: nil)
}

UITextfield text position not animating while width constraint is animated

I have three UITextField aligned in a container used to choose a date.
At first only the month textfield is shown in the container and takes the full width, then when the user chose the month, the day textfield appear and they both take half of the container.
The text alignement in these textfields is centered.
My problem is that when I animate their size, the text doesn't animate and jumps directly to the final position while the width of the textfields animate correctly.
Step 1 : The TextField Before Animation
Step 2 : The TexField width is animating but the text is already in the final position
Step 3 : The TexField Finished Animating
My code used to animate the constraint :
monthTextfieldTrailingConstraint.priority = currentDateSelectionType == .month ? UILayoutPriorityDefaultHigh : UILayoutPriorityDefaultLow
dayTextfieldTrailingConstraint.priority = currentDateSelectionType == .day ? UILayoutPriorityDefaultHigh : UILayoutPriorityDefaultLow
yearTextfieldTrailingConstraint.priority = currentDateSelectionType == .year ? UILayoutPriorityDefaultHigh : UILayoutPriorityDefaultLow
UIView.animate(withDuration: nextStepAnimationDuration) {
self.layoutIfNeeded()
}
I had almost the exact same question. Please see my question and its answer for reference: UITextField text jumps when animating width constraint
Solution Demo
The solution is to embed your textfields within another view (e.g. another UITextField or a UIView). In the gif below, I put a textfield within a textfield. Note that helloWorldTextField has a blue border to show its location within the second textfield behind it.
Instructions
For each field (month, day), make two textfields, e.g. monthTextField and monthBorderTextField.
Remove monthTextField's border and background color. Keep borderTextField's border and background color.
Center monthTextField within borderTextField.
Animate the width of borderTextField as needed.
Github link and Code
Here is the link to my project on Github: https://github.com/starkindustries/ConstraintAnimationTest
Here is the code for my test project for my MyViewController class. Everything else is setup in the storyboard which can be viewed on Github at the link above.
class MyViewController: UIViewController {
// Hello World TextField Border var
#IBOutlet weak var borderTextFieldWidth: NSLayoutConstraint!
// Button Vars
#IBOutlet weak var myButton: UIButton!
var grow: Bool = false
func animateGrowShrinkTextFields(grow: Bool, duration: TimeInterval) {
if grow {
UIView.animate(withDuration: duration, animations: {
self.borderTextFieldWidth.constant = 330
self.view.layoutIfNeeded()
}, completion: { (finished: Bool) in
print("Grow animation complete!")
})
} else {
UIView.animate(withDuration: duration, animations: {
self.borderTextFieldWidth.constant = 115
self.view.layoutIfNeeded()
}, completion: { (finished: Bool) in
print("Shrink animation complete!")
})
}
}
#IBAction func toggle(){
let duration: TimeInterval = 1.0
grow = !grow
let title = grow ? "Shrink" : "Grow"
myButton.setTitle(title, for: UIControlState.normal)
animateGrowShrinkTextFields(grow: grow, duration: duration)
}
}
Notes and References
What led me to this solution was #JimmyJames's comment: "You are just animating the UITextField width, but the content inside is not animated."
I researched how to animate font changes and came across this question: Is there a way to animate changing a UILabel's textAlignment?
In that question #CSmith mentioned that "you can animate the FRAME, not the textAlignment" https://stackoverflow.com/a/19251634/2179970
The accepted answer in that question suggests to use a UILabel within another frame. https://stackoverflow.com/a/19251735/2179970

UILabel decrease height, auto layout weird animation

I have expandable UILabel:
func expandDescriptionLabel() {
let calculatedHeightDescriptionLabel = lblDescription.sizeThatFits(CGSizeMake(lblDescription.frame.width, CGFloat.max)).height
expanded = true
UIView.animateWithDuration(0.3, animations: {
self.cnsDescriptionHeight.constant = calculatedHeightDescriptionLabel
self.hideMoreButton()// this line means nothing, results are the same with this line commented out
self.view.layoutIfNeeded()
})
}
func reduceDescriptionLabel() {
expanded = false
self.cnsDescriptionHeight.constant = 60
UIView.animateWithDuration(0.3, animations: {
self.showMoreButton() // this line means nothing, results are the same with this line commented out
self.lblDescription.setNeedsLayout();
self.view.layoutIfNeeded()
})
}
When expanding animation looks just fine, but when height is decreasing strange things happen. Look at GIF below. I tried already CGAffineTransformScale and putting UILabel in UIView and then animating a UIView.
Question is how to make the last part of animation more smooth.
As you can see when Labels height is decreasing, text is moving up and then suddenly appear in correct place(without animation).

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