Zion
Zion

Reputation: 1566

UITextField text jumps when animating width constraint

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:

enter image description here

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.

  1. 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
  2. I tried self.view.layoutSubviews and self.helloWorldTextField.layoutSubview as suggested in this answer: https://stackoverflow.com/a/30845306/2179970
  3. Also tried setNeedsLayout() UITextField text jumps iOS 9
  4. I even tried changing the font as suggested here: https://stackoverflow.com/a/35681037/2179970
  5. 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
  6. I tried subclassing UITextField as seen here: https://stackoverflow.com/a/40279630/2179970
  7. 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

Upvotes: 4

Views: 1090

Answers (2)

zslavman
zslavman

Reputation: 441

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

Upvotes: 0

Zion
Zion

Reputation: 1566

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.

enter image description here

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!

Upvotes: 1

Related Questions