UITextField, text jumps down slightly when editing begins UITextField, text jumps down slightly when editing begins swift swift

UITextField, text jumps down slightly when editing begins


I must admit that I've always seen this little bouncing during my developments but I never investigate around it.Some points that I've used to replicate your issue:

  1. two textfields with default dimensions and settings
  2. using system default font and change it's size from 13 to 25 just totest the behavior

I suppose there are more other ways to solve your problem but I don't find a fast property to stop this little "jumping" of text. I've decide to analize the current UITextField ($ xcrun swift -version = Swift 3.1) to see it's composition:

import UIKitclass ViewController: UIViewController, UITextFieldDelegate {    @IBOutlet weak var myTextField: UITextField!    override func viewDidLoad() {        super.viewDidLoad()        myTextField.delegate = self    }    func textFieldDidBeginEditing(_ textField: UITextField) {        print("\ntextFieldDidBeginEditing")        showLayersDescription()    }    func textFieldDidEndEditing(_ textField: UITextField) {        print("\ntextFieldDidEndEditing")        showLayersDescription()    }    func showLayersDescription() {        myTextField.layer.sublayers?.forEach{ print($0.debugDescription)}    }}

where myTextField is essentially one of the two textfields

Output using font size 17:

enter image description here

Essentially seems there are 3 CALayer:

  • a layer with frame = CGRect (0 0; 252 30) that have_UITextFieldRoundedRectBackgroundViewNeue as delegate that have the same dimension of our textfield
  • a layer with frame = CGRect (7 2; 238 26) that is showed only in ourtextFieldDidBeginEditing and it have UIFieldEditor as delegatethat seems to be the responsible for the editing part..
  • a layer that appear only in textFieldDidEndEditing with frame = CGRect.zero and with delegate UITextFieldLabel

If I write something to the first textField then I go to the next textfield nothing happened BUT if I return to the first textField and then to the second the layer with frame equal to CGRect.zero change to frame = CGRect(7 0.5; 238 27.5)

This is the little height (0.5) that we can see during the beginning and the ending of our editing.

We can try to refine the debug and intercept these layers with an extension:

extension UITextField{    func debugLayers() {        print("We have the follow layers:")        let FieldEditor: AnyObject.Type = NSClassFromString("UIFieldEditor")!        let LabelLayer: AnyObject.Type = NSClassFromString("_UILabelLayer")!        let TextFieldRoundRect: AnyObject.Type = NSClassFromString("_UITextFieldRoundedRectBackgroundViewNeue")!        self.layer.sublayers?.forEach{            if ($0.delegate?.self.isKind(of: TextFieldRoundRect))! { print("- layer with _UITextFieldRoundedRectBackgroundViewNeue as delegate have frame:\($0.frame)") }            if ($0.delegate?.self.isKind(of: FieldEditor))! { print("- layer with UIFieldEditor as delegate have frame:\($0.frame)") }            if $0.self.isKind(of: LabelLayer) { print("- layer is kind of _UILabelLayer have frame:\($0.frame)") }        }    }}

So we have for example:

import UIKitclass ViewController: UIViewController, UITextFieldDelegate {    @IBOutlet weak var myTextField: UITextField!    override func viewDidLoad() {        super.viewDidLoad()        myTextField.delegate = self    }    func textFieldDidBeginEditing(_ textField: UITextField) {        print("\ntextFieldDidBeginEditing")        myTextField.debugLayers()    }    func textFieldDidEndEditing(_ textField: UITextField) {        print("\ntextFieldDidEndEditing")        myTextField.debugLayers()    }}

Output always with font size 17:

enter image description here

As we can see we have always this 0.5 difference in height in that layer..

Making other tries I've seen that this behaviour happened only if the default size is between 13 and 17, under 13 and from 18 to 25 this not happened has you've report to your question.

A solution:

I think the best way to intercept and trying to correct this one it's to make a new extension:

extension UITextField{    override open func layoutSubviews() {        super.layoutSubviews()        let FieldEditor: AnyObject.Type = NSClassFromString("UIFieldEditor")!        let LabelLayer: AnyObject.Type = NSClassFromString("_UILabelLayer")!        self.layer.sublayers?.forEach{            if ($0.delegate?.self.isKind(of: FieldEditor))! {                var f = $0.frame                f.origin.y = 0.0                $0.frame = f            }            if $0.self.isKind(of: LabelLayer) {                var layerFrame = CGRect.zero                layerFrame.origin = self.editingRect(forBounds: self.bounds).origin                layerFrame.size = self.editingRect(forBounds: self.bounds).size                if let size = self.font?.pointSize, 14 ... 17 ~= size {                    layerFrame.origin.y = -0.5                } else {                    layerFrame.origin.y = 0.0                }                $0.frame = layerFrame            }        }    }} 

Final considerations:

This extension suppress the little "jumping down" of the text during the change to another textField. This is probably to balance the :

contentsCenter = CGRect (0.485 0.485; 0.000588235 0.000588235)

that both the second and third layer have with this group of font sizes.As you see in the extension I've set to zero also the height origin of the layer with UIFieldEditor delegate (that before have 2.0 as height) because it's involved to this change, I've maded it to balance the constraints differences.

Update for a valid and approved extension:

I've read your comment so I've analyzed in deep the situation about layers: what I've found is that the layer who have the gap of -0.5 height NEVER present sublayers, when the other ALWAYS present one sublayer ( have UIFieldEditor as delegate) so we can easily correct the extension as:

extension UITextField{    override open func layoutSubviews() {        super.layoutSubviews()        self.layer.sublayers?.forEach{            if let subs = $0.sublayers, subs.count>0 {                var f = $0.frame                f.origin.y = 0.0                $0.frame = f            } else {                var layerFrame = CGRect.zero                layerFrame.origin = self.editingRect(forBounds: self.bounds).origin                layerFrame.size = self.editingRect(forBounds: self.bounds).size                if let size = self.font?.pointSize, 14 ... 17 ~= size {                    layerFrame.origin.y = -0.5                } else {                    layerFrame.origin.y = 0.0                }                $0.frame = layerFrame            }        }    }}

I've tested this new extension and it behaves correctly like the old one.


So you may have stumbled on something very strange. If you subclass your UITextField to something like this.

class NonJumpyTextField: UITextField {    override func textRect(forBounds bounds: CGRect) -> CGRect {        var previousRect = super.textRect(forBounds: bounds)        print("Bounds: \(bounds)")        print("TextRect: \(previousRect)")        print("TextFieldRect \(frame)")        //not reccomended but does seem to help        previousRect.origin.y = 2.25        return previousRect    }}

and read the logs you get some odd behavior when editing it prints

Bounds: (0.0, 0.0, 100.0, 100.0)TextRect: (7.0, 2.0, 86.0, 96.0)TextFieldRect (64.0, 66.0, 97.0, 30.0)Bounds: (0.0, 0.0, 97.0, 30.0)TextRect: (7.0, 2.0, 83.0, 26.0)TextFieldRect (64.0, 66.0, 97.0, 30.0)Bounds: (0.0, 0.0, 100.0, 100.0)TextRect: (7.0, 2.0, 86.0, 96.0)TextFieldRect (64.0, 66.0, 97.0, 30.0)

when done editing

Bounds: (0.0, 0.0, 97.0, 30.0)TextRect: (7.0, 2.0, 83.0, 26.0)TextFieldRect (64.0, 66.0, 97.0, 30.0)Bounds: (0.0, 0.0, 100.0, 100.0)TextRect: (7.0, 2.0, 86.0, 96.0)TextFieldRect (64.0, 66.0, 97.0, 30.0)

In the sample I was working with brute forcing the y position did help but not really recommended. I suspect the issue has to do with the bounds being a seemingly random 100 x 100. Hopefully something here will help you find a solution that works for you.


Changing the contentVerticalAlignment property of UITextField can lead to this wierd behavior.

You can add a symbolic breakpoint

[UITextField setContentVerticalAlignment:]

to checkout if you change the contentVerticalAlignment property of UITextField accidentally.