9

UITextField in iOS 8.1.X have different vertical text alignment between editing and not editing mode when using some fonts like Helvetica Neue Light 17:

UITextField bug example

I have a sample project here.

Is there a workaround that I don't need to create a custom text field? The main problem is that when using Dynamic Type, so the font can changes in runtime.

Anyway I opened a radar rdar://19374610.

Diogo T
  • 2,591
  • 1
  • 29
  • 39
  • I remember facing this issue and I think it is related to a certain property on the fonts related to vertical position that is disregarded on some situations and not on others. In this case it has a different behavior during edit and not. What I did at the time as a quick hack was to reposition the UITextField 1 or 2 pixels down when editing started, and back to place when edit ended. – manecosta Jan 05 '15 at 19:14
  • The issue with that approach is that you can't use any border or background on the UITextField that would make it obvious that you're repositioning the whole thing, namely that native rounded border it seems you're using. – manecosta Jan 05 '15 at 19:15
  • This guy was facing the same issue and it came to the same conclusion as I did at the time. http://stackoverflow.com/questions/9674566/text-in-uitextfield-moves-up-after-editing-center-while-editing – manecosta Jan 05 '15 at 19:17
  • @manecosta I don't like this kind of hack because is font dependent. I'm using Dynamic Typing, so the font size can change anytime. – Diogo T Jan 06 '15 at 17:16

2 Answers2

3

Just stumbled across the same issue and decided to 'debug' a little. Basically I just plotted values for various fonts (cap height, point size, preferred height, available height, the distance the text moved) and noticed a pattern.

The reason the text moves up because it is rendered in two completely different ways: the non-editing version is rendered using -drawRect: (you can even override the hook), the editing version is rendered by a so-called UIFieldEditor. This one appears to ceil the text height regardless of whether or not you're on a Retina device and centers it afterwards. On Retina devices though, you should always ceil(scalar * scale) / scale to align on pixels. Hence iOS assumes a greater text height than needed, and moves it a little further up to keep it centered. Funnily enough, the rendering of static text and UIFieldEditor differ.

To fix the issue, subclass UITextField and override -editingRectForBounds:. Here you will want to take the non-editing-rect ('text rect') and account for the shift Apple is going to perform in advance.

- (CGRect)editingRectForBounds:(CGRect)bounds
{
    if (UIDevice.currentDevice.systemVersion.integerValue != 8) return [self textRectForBounds:bounds];

    CGFloat const scale = UIScreen.mainScreen.scale;
    CGFloat const preferred = self.attributedText.size.height;
    CGFloat const delta = ceil(preferred) - preferred;
    CGFloat const adjustment = floor(delta * scale) / scale;

    CGRect const textRect = [self textRectForBounds:bounds];
    CGRect const editingRect = CGRectOffset(textRect, 0.0, adjustment);

    return editingRect;
}

Edit: I just tested the code on older OS versions, including 8.0. On iOS 7.x, everything appears to be fine, iOS 8.0 contains the bug already. We cannot predict the future, so for now I would only include the fix for iOS 8.x, hopefully Apple fixes the problem in iOS 9 themselves.


Another Edit: This code makes the editing text appear at the same location as its static counterpart. If you want to control them separately (which Apple thinks makes sense, since they offer both -textRectForBounds: and -editingRectForBounds:), you may want to replace [self textRectForBounds:bounds] with [super editingRectForBounds:bounds]. If you want to implement this fix in a category using swizzling, you certainly should use the super version.

Christian Schnorr
  • 10,768
  • 8
  • 48
  • 83
-1

This bug is font dependant so I will go with a solution that set the style of the attributed string to compensate this.

An example to fix this for the first textField with the Helvetica Neue Light and size 17:

Suppose the view controller is the delegate of the firstTextField

- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
NSDictionary *style = @{
        NSFontAttributeName : [UIFont fontWithName:@"HelveticaNeue-Light"
                                              size:17],
        NSParagraphStyleAttributeName : [NSParagraphStyle defaultParagraphStyle],
        NSBaselineOffsetAttributeName: @(0.4)
};
_firstTextField.attributedText = [[NSAttributedString alloc] initWithString:_firstTextField.text
                                                                 attributes:style];

}
-(void)textFieldDidBeginEditing:(UITextField *)textField {
NSDictionary *style = @{
        NSFontAttributeName : [UIFont fontWithName:@"HelveticaNeue-Light"
                                              size:17],
        NSParagraphStyleAttributeName : [NSParagraphStyle defaultParagraphStyle],
        NSBaselineOffsetAttributeName: @(0.0)

};
[_firstTextField setTypingAttributes: style];
}

The key part is when you set the NSBaselineAlignment from when you present the textField to when you are editing it. Also I checked what happen when the font is double size (with a textField big enough) and the NSBaselineOffset is still the same to avoid the jumping effect.

  • I see that this bug is font dependent, the issue here is that since I'm using Dynamic Type, the font can change on runtime. So this solution, despite works for specific cases, don't answer the question. I'll rewrite the question to make this more clear. – Diogo T Jan 27 '15 at 16:38