Search code examples
cocoa-touchuitextfieldios9

iOS 9 UITextField textRectForBounds issue


I am running on iOS 9, XCode 7 GM. I have a class that extends UITextField (CustomOffsetTextField) and provides support for custom text positioning like so:

override func textRectForBounds(bounds: CGRect) -> CGRect {
    return CGRectOffset(bounds, textOffset.x + leftViewOffset, textOffset.y)
}

override func placeholderRectForBounds(bounds: CGRect) -> CGRect {
    return CGRectOffset(bounds, textOffset.x + leftViewOffset, textOffset.y)
}

override func editingRectForBounds(bounds: CGRect) -> CGRect {
    return CGRectOffset(bounds, textOffset.x + leftViewOffset, textOffset.y)
}

leftViewOffset is the width of the text field's leftView, if one exists. textOffset is a CGPoint that defines custom x and y offsets to apply to the text rects.

My issue is occurring on my sign in view. I have 2 instances of my CustomOffsetTextField - one for the user's e-mail and one for their password.

On first load of the view controller, if I enter text into one field and then tap into the other field, that text will jump back to position 0,0 in its text field briefly before jumping back to the position that is defined by textRectForBounds. Some basic print debugging verifies that these functions are always returning the values that I expect them to.

After this initial hiccup, the text field then behaves as I would expect it to. This issue only occurs one time in each text field after the view controller loads. After that, I can tap back and forth between the fields as much as I want without it happening again.

Has anyone seen similar issues with UITextField in iOS 9? If so, were you able to find a fix?


Solution

  • In iOS 9 an extra UIKeyboardWillShowNotification notification is sent whenever you tap between the text fields. If you have a call to [self.view layoutIfNeeded] in the notification callback it will cause the jump.

    // Animate
    [UIView beginAnimations:@"keyboardDidShowAnimations" context:NULL];
    [UIView setAnimationDuration:duration];
    [UIView setAnimationCurve:curve];
    [self.view layoutIfNeeded];
    [UIView commitAnimations];
    

    It's related to this: https://forums.developer.apple.com/message/53905#53905

    If you're testing in the simulator without the software keyboard, you'll get an extra UIKeyboardWillHideNotification instead which will likely cause the same issue if you have a layoutIfNeeded call in that notification callback as well.

    I resolved this by putting checks at the top of the callbacks to ensure that I really needed to animate/update constraints.

    - (void)keyboardWillShow:(NSNotification *)note {
        BOOL shouldAnimate = self.someConstraint.constant != kMinimumSize;
        if (shouldAnimate) {
    ...
    
    - (void)keyboardWillHide:(NSNotification *)note {
        BOOL shouldAnimate = self.someConstraint.constant == kMinimumSize;
        if (shouldAnimate) {
    ...
    

    Update: This won't necessarily work properly with 3rd party keyboards which call the notification methods multiple times. See https://stackoverflow.com/a/26004605.