Resize a UITextField while typing (by using Autolayout)

IphoneCocoa TouchUitextfieldAutolayout

Iphone Problem Overview


I have a UITableViewCell which has two UITextFields (without borders). The following constraints are used to set up the horizontal layout.

@"|-10-[leftTextField(>=80)]-(>=10)-[rightTextField(>=40)]-10-|"

Nothing fancy, and works as expected.

enter image description here

As you can see the upper textField has the correct size. The lower textField started with an empty text, and because of the empty text it is 80 points wide. When I type text into the editing textField the text scrolls to the left, it does not change its width.

I don't like that, the width of the textField should adjust while the user types into that textField.

In my opinion that should work out of the box. By implementing a IBAction for the UIControlEventEditingChanged event I can confirm that typing actually changes the intrinsicContentSize of the UITextField.

But well, the width does not change until the textField is no longer the first responder. If I put the cursor into another textField the width of the edited textField is set. That's a bit late for what I want.

These commented out lines is what I tried without any success:

- (IBAction)textFieldDidChange:(UITextField *)textField {
    [UIView animateWithDuration:0.1 animations:^{
//        [self.contentView removeConstraints:horizontalConstraints];
//        [self.contentView addConstraints:horizontalConstraints];
//        [self.contentView layoutIfNeeded];

//        [self.contentView setNeedsLayout];

//        [self.contentView setNeedsUpdateConstraints];
    }];
    NSLog(@"%@", NSStringFromCGSize(textField.intrinsicContentSize));
}

Does anybody know what I am missing? What could I try to make this work?

Iphone Solutions


Solution 1 - Iphone

This is what works for me:

- (IBAction) textFieldDidChange: (UITextField*) textField
{
    [UIView animateWithDuration:0.1 animations:^{
        [textField invalidateIntrinsicContentSize];
    }];
}

What's interesting is that it seems as if it SHOULD work out of the box, given this text from the docs:

> In addition, if a property of a view changes and that change affects > the intrinsic content size, the view must call > invalidateIntrinsicContentSize so that the layout system notices the > change and can re-layout. In the implementation of your view class, > you must ensure that if the value of any property upon which the > intrinsic size depends changes, you invoke > invalidateIntrinsicContentSize. For example, a text field calls > invalidateIntrinsicContentSize if the string value changes.

My best guess is that the textfield only calls invalidateIntrinsicContentSize once editing has finished, not during.

Edit: A bunch of "This is not working for me". I think the confusion here is perhaps the triggering event that is tied to the textFieldDidChange: handler. The event needs to be UIControlEventEditingChanged. If you're using IB, double check that you're handling the right event.

The UITextField also cannot be constrained in size. You can lock it into position with constraints, but any width constraint, or set of left+right positioning constraints will prevent it from resizing to its intrinsic content size.

Solution 2 - Iphone

I don't know why UITextField only updates its intrinsicContentSize when it resigns its first responder status. This happens for both iOS 7 and iOS 8.

As a temporary solution, I override intrinsicContentSize and determine the size using typingAttributes. This accounts for leftView and rightView as well

// This method is target-action for UIControlEventEditingChanged
func textFieldEditingChanged(textField: UITextField) {
  textField.invalidateIntrinsicContentSize()
}


override var intrinsicContentSize: CGSize {
    if isEditing {
      let string = (text ?? "") as NSString
      let size = string.size(attributes: typingAttributes)
      return CGSize(width: size.width + (rightView?.bounds.size.width ?? 0) + (leftView?.bounds.size.width ?? 0) + 2,
                    height: size.height)
    }

    return super.intrinsicContentSize
  }

Here I make the it wider to account for the caret

Solution 3 - Iphone

Here's the answer in Swift. As a bonus, this snippet doesn't collapse the text field if there is a placeholder.

// UITextField subclass

override func awakeFromNib() {
    super.awakeFromNib()
    self.addTarget(self, action: #selector(textFieldTextDidChange), forControlEvents: .EditingChanged)
}

func textFieldTextDidChange(textField: UITextField) {
    textField.invalidateIntrinsicContentSize()
}

override func intrinsicContentSize() -> CGSize {
    if self.editing {
        let textSize: CGSize = NSString(string: ((text ?? "" == "") ? self.placeholder : self.text) ?? "").sizeWithAttributes(self.typingAttributes)
        return CGSize(width: textSize.width + (self.leftView?.bounds.size.width ?? 0) + (self.rightView?.bounds.size.width ?? 0) + 2, height: textSize.height)
    } else {
        return super.intrinsicContentSize()
    }
}

Solution 4 - Iphone

First things first: in Swift 4, UITextField's intrinsicContentSize is a computed variable, not a method.

Below is a Swift4 solution with a specialized use case. A right justified textfield can resize and extend leftward up to a certain pixel boundary. The textfield is assigned to the CurrencyTextField class which inherits from UITextField. CurrencyTextField is then extended to provide custom behavior for returning the object's intrinsicContentSize, a computed variable. .
The "editing changed" event of the CurrencyTextField instance is mapped to the repositionAndResize IBAction method in its host UIViewController class. This method simply invalidates the current intrinsic content size of the CurrencyTextField instance. Invoking the CurrencyTextField's invalidateIntrinsicContentSize method ( inherited form UITextField ) triggers an attempt to get the object's intrinsic content size.

The custom logic in CurrencyTextField's intrinsicContentSize getter method has to convert the CurrentTextField's text property to NSString in order ascertain its size, which it returns as the intrinsicContentSize if the x coordinate of the CurrencyTextField is greater than the stipulated pixel boundary.

class CurrencyTextField:UITextField {

}

extension CurrencyTextField {


     override open var intrinsicContentSize: CGSize {

        if isEditing {
            let string = (text ?? "") as NSString
            let stringSize:CGSize = string.size(withAttributes: 
             [NSAttributedStringKey.font: UIFont.systemFont(ofSize: 
             19.0)])
             if self.frame.minX > 71.0 {
                return stringSize
             }
       }
    
       return super.intrinsicContentSize
    }


}

class TestController: UIViewController {

    @IBAction func repositionAndResize(_ sender: CurrencyTextField) {
        sender.invalidateIntrinsicContentSize()
    }


    override func viewDidLoad() {
        super.viewDidLoad()
       
    }

}

Solution 5 - Iphone

I had a similar requirement with a UITextView (had to dynamically increase it's height and some other things).

What I did was something similar to this:

Considering self.contentView is the superview of textField

- (IBAction)textFieldDidChange:(UITextField *)textField {
    //You can also use "textField.superview" instead of "self.contentView"
    [self.contentView setNeedsUpdateConstraints];

    //Since you wish to animate that expansion right away...
    [UIView animateWithDuration:0.1 animations:^{
        [self.contentView updateConstraintsIfNeeded];
    }];
    NSLog(@"%@", NSStringFromCGSize(textField.intrinsicContentSize));
}

Also, considering the requirements I had, I had to override the updateConstraints method of my UITextView's superview.

Should you choose to opt for that solution (maybe for a more fine tuned approach), you can do:

- (void)updateConstraints {
    [super updateConstraints];
    
    //Firstly, remove the width constraint from the textField.
    [self.myTextField removeConstraint:self.textFieldWidthConstraint];
    self.textFieldWidthConstraint = nil;
    
    CGSize contentSize = self.myTextField.intrinsicContentSize;
    self.textFieldWidthConstraint = [NSLayoutConstraint constraintWithItem:self.myTextField attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationGreaterThanOrEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1 constant:contentSize.width];
    
    [self.myTextField addConstraint:self.textFieldWidthConstraint];
}

As mentioned, the override option was used by me because I required a more fine tuned approach.

Additionally, as always you'll need to make sure that you're checking for any edge cases (in terms of sizing values) that you think you might encounter.

Hope this helps!

Cheers!

Solution 6 - Iphone

For some reason, calling invalidateIntrinsicContentSize wasn't working for me either, but I also ran into issues with the other solutions due to the margins and editing controls.

This solution isn't always needed; if invalidateIntrinsicContentSize works, then all that needs to be done is to add a call to that when the text is changed, as in the other answers. If that isn't working, then here's a solution (adapted from here) I found which also works with a clear control:

class AutoResizingUITextField: UITextField {
    var lastTextBeforeEditing: String?

    override init(frame: CGRect) {
        super.init(frame: frame)
        setupTextChangeNotification()
    }
    
    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        setupTextChangeNotification()
    }                
    
    func setupTextChangeNotification() {
        NotificationCenter.default.addObserver(
            forName: Notification.Name.UITextFieldTextDidChange,
            object: self,
            queue: OperationQueue.main) { (notification) in                   
                self.invalidateIntrinsicContentSize()
        }
        NotificationCenter.default.addObserver(
            forName: Notification.Name.UITextFieldTextDidBeginEditing,
            object: self,
            queue: OperationQueue.main) { (notification) in
                self.lastTextBeforeEditing = self.text
        }
    }                
            
    override var intrinsicContentSize: CGSize {
        var size = super.intrinsicContentSize
        
        if isEditing, let text = text, let lastTextBeforeEditing = lastTextBeforeEditing {
            let string = text as NSString
            let stringSize = string.size(attributes: typingAttributes)
            let origSize = (lastTextBeforeEditing as NSString).size(attributes: typingAttributes)
            size.width = size.width + (stringSize.width - origSize.width)
        }
        
        return size
    }

    deinit {
        NotificationCenter.default.removeObserver(self)
    }
}

If invalidateIntrinsicContentSize is working by itself, this will end up double-counting the change so that needs to be checked first.

Solution 7 - Iphone

Another solution to this is to set leading/trailing constraints to the textField and use isActive to turn them on/off.

When editing start, turn them on, if the text is centered, the effect will be the same (text will seems to be auto resizing as typing). When editing stops, turn constraints off, which will wrap the frame around the text.

Solution 8 - Iphone

For me i also added a target to the textfield (UIControlEventEditingChanged)

- (void)textFieldChanged:(UITextField *)textField {
    [textField sizeToFit];
}

if you have a background color, then just update it in delegate method:

- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string {

    textField.backgroundColor = [UIColor grayColor];
    return YES;
}

Solution 9 - Iphone

You can add this code in viewDidLoad to solve the problem.

if ([self respondsToSelector:@selector(setEdgesForExtendedLayout:)])
{
    self.edgesForExtendedLayout = UIRectEdgeNone;
}

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionMatthias BauchView Question on Stackoverflow
Solution 1 - IphoneTomSwiftView Answer on Stackoverflow
Solution 2 - Iphoneonmyway133View Answer on Stackoverflow
Solution 3 - IphoneWaltersGE1View Answer on Stackoverflow
Solution 4 - IphonesigmonkyView Answer on Stackoverflow
Solution 5 - IphonecodeBearerView Answer on Stackoverflow
Solution 6 - IphoneLearn OpenGL ESView Answer on Stackoverflow
Solution 7 - IphonebauerMusicView Answer on Stackoverflow
Solution 8 - IphonerobegamesiosView Answer on Stackoverflow
Solution 9 - IphoneEric LeeView Answer on Stackoverflow