40

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 here enter image description here

Luke
  • 9,512
  • 15
  • 82
  • 146

10 Answers10

18

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.

Community
  • 1
  • 1
Geri Borbás
  • 15,810
  • 18
  • 109
  • 172
  • 1
    Thanks for this, it works great. I did a little improvement over your code: 1) got rid of the `secure` property (unneeded IMO, I assume all textFields of this subclass are secure); 2) avoid editingDidChange updates; 3) implement appropriate setText: method. You can find it here: https://gist.github.com/rsanchezsaez/9836102 – Ricardo Sanchez-Saez Mar 28 '14 at 15:59
  • I can't recall exactly, but I had some further issues with secure text fields (at startup), so I decided to switch to arbitrary `UITextFields` instead as a starting point. – Geri Borbás Apr 02 '14 at 10:56
  • 2
    I created category on UITextField to fix this issue: https://github.com/elegion/ELFixSecureTextFieldFont (based on your answer) – glyuck Apr 02 '14 at 11:53
  • 1
    @glyuck, your solution seems much more complex. Why would you say that it's better? – André Fratelli Dec 16 '14 at 02:21
  • this solution doesn't work 100% of the time. I experienced that the password sent to the server has the following leading characters: "••" (they could easily be the dots). I guess something somewhere gets messed up. – Alberto M Nov 03 '17 at 09:37
  • Wow, kind of weird that this is still an issue 4 years later. – Geri Borbás Apr 12 '18 at 13:23
12

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.

poliveira
  • 147
  • 1
  • 7
Joshua Haines
  • 349
  • 5
  • 8
9

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
Austin
  • 5,625
  • 1
  • 29
  • 43
  • This worked like a charm, thanks! The only downside to this method is that bullets jump a little when going in and out of edit mode. This can be fixed by just hard-coding `rect.origin.y`, but maybe there's a cleaner solution? – maxkonovalov Jun 13 '14 at 08:25
  • Huh, in my situation there was a jump if I omitted the `rect.origin.y = floorf(rect.origin.y)` statement. It might work better to use `roundf()` in stead of `floorf()`. – Austin Jun 13 '14 at 14:41
  • I see, but in my case I had to set origin.y to 1.5, so it might differ depending on the text font and size... – maxkonovalov Jun 13 '14 at 15:40
  • 3
    the method drawTextInRect is never called. – Tapan Thaker Jul 14 '14 at 19:53
  • @Austin works great! I like your solution better than Geri's. Well done! – mokagio Jan 13 '15 at 03:37
3

While this is an iOS bug (and new in iOS 7, I should add), I do have another way to work around it that one might find acceptable. The functionality is still slightly degraded but not by much.

Basically, the idea is to set the font to the default font family/style whenever the field has something entered in it; but when nothing is entered, set it to your custom font. (The font size can be left alone, as it's the family/style, not the size, that is buggy.) Trap every change of the field's value and set the font accordingly at that time. Then the faint "hint" text when nothing is entered has the font that you want (custom); but when anything is entered (whether you are editing or not) will use default (Helvetica). Since bullets are bullets, this should look fine.

The one downside is that the characters, as you type before being replaced by bullets, will use default font (Helvetica). That's only for a split second per character though. If that is acceptable, then this solution works.

RedRedSuit
  • 79
  • 3
1

I found a trick for this issue.

- (void)textFieldDidBeginEditing:(UITextField *)textField
{
   if ([textField tag]== TAG_PASS || [textField tag]== TAG_CPASS)
    {
       // To fix password dot size
        if ([[textField text] isEqualToString:@"" ])
        {
          [textField setText:@" "];
          [textField resignFirstResponder];
          [textField becomeFirstResponder];
          [textField setText:@""];
        }  
    }
}
Parul
  • 25
  • 4
1
[passWordTextField resignFirstResponder];
passWordTextField.secureTextEntry = !passWordTextField.secureTextEntry;
[passWordTextField becomeFirstResponder];

This is the fastest way to solve this bug!

AaronTKD
  • 109
  • 1
  • 3
  • When it becomes first responder again and the user continues typing, all of what's been typed before will be cleared. – Patrick Lynch Jun 16 '16 at 00:36
  • The code just run when the user start to type, but not typing. So that there is nothing to be cleared. – AaronTKD Jun 20 '16 at 07:10
0

iOS is acting a bit strange when it comes to custom fonts. Try removing "Adjust to Fit" for that textfield. If that doesn't work, I'm guessing that what bothering you is the size increase of the font.

A simple solution for that would be:

- (void)textFieldDidBeginEditing:(UITextField *)textField
{
    if(textField.secureTextEntry)
    {
        [textField setFont:[UIFont fontWithName:@"Helvetica-Bold" size:10.0]];
    }
}

-(void)textFieldDidEndEditing:(UITextField *)textField
{
    if(textField.secureTextEntry)
    {
        [textField setFont:[UIFont fontWithName:@"Helvetica-Bold" size:10.0]];
    }
}

You'll need to play with the size a bit in order for it to look like there is no size change when loosing focus on the UITextField.

If you have a major spacing problem between characters like in the edited question, the simplest (and a bit ugly) solution would be to create a Bullet image that matches the above size & spacing and matches the amount of characters entered by the user that will appear when the user leaves the UITextField.

Segev
  • 19,035
  • 12
  • 80
  • 152
  • It’s happening with any custom font I use, be they TTF, OTF, etc. I’ve tried a few. And Adjust To Fit makes no difference either, unfortunately :( – Luke Jan 07 '14 at 10:59
  • Makes no difference, I’m afraid. The font still reverts to the system standard whenever I leave the text field. – Luke Jan 07 '14 at 11:04
  • @lukech The code above works fine here for every Custom font I've tried. Make sure the `UITextField` has a delegate and that the methods above are getting called. – Segev Jan 07 '14 at 11:05
  • Yup, they definitely are. If I set the font size, it respects it, so the bullets get bigger, but setting the font face itself is ignored. – Luke Jan 07 '14 at 11:07
  • @lukech Why do you care about the font face if all the user sees are black bullets? I can think about one scenario like spacing between letters, is that the case? – Segev Jan 07 '14 at 11:10
  • I want to use the bullet from the custom font, not Helvetica’s bullet. Also, they see each character as they type them in, as is standard. That character needs to be in the custom font. It’s a password field, so it needs to match username, etc. It certainly seems like an iOS bug to me, there’s no reason it shouldn’t obey the font while out of focus. – Luke Jan 07 '14 at 11:12
  • @lukech iOS will always use the default bullets, as for your second argument, I see the custom font as I type even after I refocus on the `UITextField`. – Segev Jan 07 '14 at 11:14
  • Right. You’ll see custom font text and bullets as you type, then as soon as you change fields, the bullets will change. Then if you re-enter the cell, the bullets will change back. That’s ugly, undesirable, and inconsistent UI, and that’s the issue I’m trying to fix here :) – Luke Jan 07 '14 at 11:16
  • @lukech And that's completely understandable but, as far as I can tell, unless you are using a crazy font like `Zapfino` for example, the only thing you need to care about is the size of the font. Between taping out and refocusing on the `UITextField` the size of the font is the main thing noticeable. – Segev Jan 07 '14 at 11:23
  • I’ve added an image showing the two bullets – as you can see, they’re very different, and it’s quite a jarring experience for the user to see them changing back and forth. – Luke Jan 07 '14 at 11:24
  • @lukech It is a bit ugly in did. I've updated my answer. – Segev Jan 07 '14 at 11:30
  • 2
    I think the easiest way is just to turn secureTextEntry off at the `didEndEditing` point, and create a string containing the right number of bullets, then set it to that, and keep track of the actual password separately, then swap it in and out as required. – Luke Jan 07 '14 at 11:43
  • Whoh, I'm gonna try this out above. – Geri Borbás Mar 23 '14 at 19:15
0

A secureTextEntry text field can be avoided altogether:

NSString *pin = @"";
BOOL pasting = FALSE;
-(BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
{
    if(!pasting) {
        pin = [pin stringByReplacingCharactersInRange:range withString:string];

        // Bail out when deleting a character
        if([string length] == 0) {
            return YES;
        }

        pasting = TRUE;
        [textField paste:@"●"];

        return NO;
    } else {
        pasting = FALSE;
        return YES;
    }
}
Randomblue
  • 112,777
  • 145
  • 353
  • 547
0

I recommend to resignFirstResponder before you change scureTextEntry and then becomeFirstResponder again as it is posted here: https://stackoverflow.com/a/34777286/1151916

Community
  • 1
  • 1
Ramis
  • 13,985
  • 7
  • 81
  • 100
0

Swift 5 and iOS 14 is around but isSecureTextEntry set to true for custom font still displays the wrong size bullets, although the actual leading letter is of the correct size.

None of the solutions from stack overflow has worked for me except a hacky workaround of setting the font to the system font when password is in secure mode.

  if textField.isSecureTextEntry {
            self.textField.font = UIFont.systemFont(ofSize: 17)
    } else {
        self.textField.font = UIFont(name: "Roboto-Regular", size: 17)
    }
Mishka
  • 502
  • 5
  • 15