Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

UITextField in iOS 8.1 have different vertical text alignment between editing and not editing mode when using some fonts

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.

like image 552
Diogo T Avatar asked Jan 05 '15 17:01

Diogo T


1 Answers

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.

like image 74
Christian Schnorr Avatar answered Oct 27 '22 01:10

Christian Schnorr