UITextField secureTextEntry bullets with a custom font?

I’m using a custom font in a UITextField, which has secureTextEntry turned on. When I’m typing in the cell, I see the bullets in my chosen font, but when the field loses focus, those bullets revert to the system standard font. If I tap the field again, they change back to my font, and so on.

Is there a way I can ensure that they continue to display the custom font’s bullets, even when the field is out of focus?

enter image description hereenter image description here


Solution 1:

A subclass that works this issue around. Create an arbitrary UITextField, then set the secure property to YES (via KVC in IB).

Actually it implements a comment suggested by lukech. When textfield ends editing, it switches to an arbitrary textfield, then set a bulk of dots into, and some hack in text accessor to always get the actual text the field holds.

@interface SecureTextFieldWithCustomFont : UITextField
@property (nonatomic) BOOL secure;
@property (nonatomic, strong) NSString *actualText;
@end


@implementation SecureTextFieldWithCustomFont


-(void)awakeFromNib
{
    [super awakeFromNib];

    if (self.secureTextEntry)
    {
        // Listen for changes.
        [self addTarget:self action:@selector(editingDidBegin) forControlEvents:UIControlEventEditingDidBegin];
        [self addTarget:self action:@selector(editingDidChange) forControlEvents:UIControlEventEditingChanged];
        [self addTarget:self action:@selector(editingDidFinish) forControlEvents:UIControlEventEditingDidEnd];
    }
}

-(NSString*)text
{
    if (self.editing || self.secure == NO)
    { return [super text]; }

    else
    { return self.actualText; }
}

-(void)editingDidBegin
{
    self.secureTextEntry = YES;
    self.text = self.actualText;
}

-(void)editingDidChange
{ self.actualText = self.text; }

-(void)editingDidFinish
{
    self.secureTextEntry = NO;
    self.actualText = self.text;
    self.text = [self dotPlaceholder];
}

-(NSString*)dotPlaceholder
{
    int index = 0;
    NSMutableString *dots = @"".mutableCopy;
    while (index < self.text.length)
    { [dots appendString:@"•"]; index++; }
    return dots;
}


@end

May be augmented to work with non NIB instantiations, handling default values, etc, but you probably get the idea.

Solution 2:

For those having trouble with losing custom fonts when toggling secureTextEntry, I found a work-around (I'm using the iOS 8.4 SDK). I was trying to make a toggle for showing/hiding a password in a UITextField. Every time I'd toggle secureTextEntry = NO my custom font got borked, and only the last character showed the correct font. Something funky is definitely going on with this, but here's my solution:

-(void)showPassword {
    [self.textField resignFirstResponder];
    self.textField.secureTextEntry = NO;
}

First responder needs to be resigned for some reason. You don't seem to need to resign the first responder when setting secureTextEntry to YES, only when setting to NO.

Solution 3:

The actual problem appears to be that the editing view (UITextField does not draw its own text while editing) uses bullets (U+2022) to draw redacted characters, while UITextField uses black circles (U+25CF). I suppose that in the default fonts, these characters look the same.

Here's an alternate workaround for anyone interested, which uses a custom text field subclass, but doesn't require juggling the text property or other special configuration. IMO, this keeps things relatively clean.

@interface MyTextField : UITextField
@end

@implementation MyTextField

- (void)drawTextInRect:(CGRect)rect
{
    if (self.isSecureTextEntry)
    {
        NSMutableParagraphStyle *paragraphStyle = [NSMutableParagraphStyle new];
        paragraphStyle.alignment = self.textAlignment;

        NSMutableDictionary *attributes = [NSMutableDictionary dictionary];
        [attributes setValue:self.font forKey:NSFontAttributeName];
        [attributes setValue:self.textColor forKey:NSForegroundColorAttributeName];
        [attributes setValue:paragraphStyle forKey:NSParagraphStyleAttributeName];

        CGSize textSize = [self.text sizeWithAttributes:attributes];

        rect = CGRectInset(rect, 0, (CGRectGetHeight(rect) - textSize.height) * 0.5);
        rect.origin.y = floorf(rect.origin.y);

        NSMutableString *redactedText = [NSMutableString new];
        while (redactedText.length < self.text.length)
        {
            [redactedText appendString:@"\u2022"];
        }

        [redactedText drawInRect:rect withAttributes:attributes];
    }
    else
    {
        [super drawTextInRect:rect];
    }
}

@end