Thursday 13 June 2013

Secure Text Field With Custom Numeric KeyPad

Hi Guys,

Today I have decided to discuss about a custom secure text field with custom numeric keypad. Usually, when we have a UITextField with secure option enabled and using a default keyboard, last character entered by user will be shown for some time and then it will be converted in to a dot. Please see below screenshot.

But this is not same if we use a custom keyboard. If we use a custom keyboard, last digit won't appear to end user. When ever we press a key in custom keyboard, a dot will be added directly with out any animation. Please see the below screenshot.


To have last character animation for custom keyboard, I came up with a Custom secured UITextField called MLKSecuredTextField. Lets see how I have implemented it. 

Create a Single View application as shown below :
Now, create MKLSecureTextField class which will be a subclass of UITextField as shown below :
The logic I have followed is, I have subclasses UITextField and created following initializers :

- (id)init
{
    if( self = [super init] )
    {
        [self setUpTextFieldProperties];
    }
    
    return self;
}

- (id)initWithFrame:(CGRect)frame
{
    if( self = [super initWithFrame:frame] )
    {
        [self setUpTextFieldProperties];
    }
    
    return self;
}

- (void)setUpTextFieldProperties
{
    // Following are default Values. Can assign custom values after creating MLKSecuredTextField.
    self.borderStyle = UITextBorderStyleRoundedRect;
    self.textColor = [UIColor clearColor];
    self.font = [UIFont systemFontOfSize:FONT_SIZE];
    self.clearButtonMode = UITextFieldViewModeAlways;
    self.placeholder = PLACEHOLDER_TEXT;
    self.autoresizingMask = UIViewAutoresizingFlexibleTopMargin;
    
    self.dotViews = [[NSMutableArray alloc] init];
    
    self.textLength = 0;
    self.text = @"";
    
    // Add text changes observer to get notifications whenever text changes
    [self addObserver:self forKeyPath:TEXT options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:nil];
}

 setUpTextFieldProperties method will setup the default properties of our custom textfield and adds a observer to "text" property of textfield. We are observing changes made to text of the text field. I am setting the text color as clear color so that whatever text user enters won't appear.

Whenever there is a change in the text, I am updating the last character to dot and showing the current character for some time. If no text entered, last enter character will be changed to dot after 2 seconds time interval. I have used NSTimer to achieve this. Following is the code :

- (void)observeValueForKeyPath:(NSString *)keyPath
                      ofObject:(id)object
                        change:(NSDictionary *)change
                       context:(void *)context {
    
    if ([keyPath isEqual:TEXT])
    {
        [self textChanged];
    }
}

- (void)textChanged
{
    // Remove the dot view if a character removed
    if( self.textLength > [self.text length] )
    {
        [self invalidateDotViewTimerForIndex:([self.text length ]- 1)];
        
        [[self.dotViews objectAtIndex:(self.textLength - 1)] removeFromSuperview];
        [self.dotViews removeObjectAtIndex:(self.textLength - 1)];
    }
    else if( [self.text length] > 0 )
    {
        [self invalidateDotViewTimerForIndex:([self.text length] - 2)];
        
        [self addCharacterLabelAtIndex:([self.text length] - 1)];
        dotViewTimer = [NSTimer scheduledTimerWithTimeInterval:TIMER_INTERVAL target:self selector:@selector(addDotToLastCharacter) userInfo:nil repeats:NO];
    }
    
    // Capture new lenght
    self.textLength = [self.text length];
    
    // User clicked clear text button. Clear all dot views
    if( self.textLength == 0 )
    {
        [self clearAllDotViews];
    }
}

Override following UITextField methods to set the text rect and editing rect for our custom MLKSecureTextField :

- (CGRect)textRectForBounds:(CGRect) bounds
{
    CGRect origValue = [super textRectForBounds: bounds];
    
    return TEXT_LABEL_OFFSET;
}

- (CGRect)editingRectForBounds:(CGRect) bounds
{
    CGRect origValue = [super textRectForBounds: bounds];
    
    return TEXT_LABEL_OFFSET;
}

Now add business logic to add dots, to show last character for time and clearing all dots when user taps clear button on text field.

- (void)addDotToLastCharacter
{
    [self.characterLabel removeFromSuperview];
    [self addDotViewForCharAtIndex:([self.text length] - 1)];
}

- (void)addDotViewForCharAtIndex:(NSInteger)index
{
    UIView *dotView = [[UIView alloc] initWithFrame:DOT_VIEW_RECT];
    dotView.backgroundColor = [UIColor blackColor];
    [dotView.layer setMasksToBounds:YES];
    [dotView.layer setCornerRadius:DOT_RADIUS];
    
    [self addSubview:dotView];
    [self.dotViews addObject:dotView];
}

- (void)addCharacterLabelAtIndex:(NSInteger)index
{
    self.characterLabel = [[UILabel alloc] initWithFrame:CHAR_LABEL_RECT];
    self.characterLabel.text = [NSString stringWithFormat:@"%C",[self.text characterAtIndex:index]];
    self.characterLabel.font = self.font;
    self.characterLabel.backgroundColor = [UIColor clearColor];
    
    [self addSubview:self.characterLabel];
}

- (void)invalidateDotViewTimerForIndex:(NSInteger)index
{
    if( dotViewTimer.isValid )
    {
        [dotViewTimer invalidate];
        [self.characterLabel removeFromSuperview];
        [self addDotViewForCharAtIndex:index];
    }
}

- (void)clearAllDotViews
{
    while( self.dotViews.count )
    {
        [[self.dotViews objectAtIndex:self.dotViews.count-1] removeFromSuperview];
        [self.dotViews removeObjectAtIndex:self.dotViews.count-1];
    }
}

Here are the #defines I have used :

#define DOT_VIEW_RECT       CGRectMake(8 + (index * (9 + 1.5)), 13, 9, 9)
#define CHAR_LABEL_RECT     CGRectMake(8 + (index * (9 + 1.5)), 10, 9, 14)

#define TEXT_LABEL_OFFSET   CGRectOffset(origValue, 0.0f, 3.0f)

#define TEXT            @"text"
#define FONT_SIZE       17
#define TIMER_INTERVAL  2.0

#define DOT_RADIUS      4.5f

#define PLACEHOLDER_TEXT    @"MLKSecuredTextField"

Okay, we have completed creating custom secure text field. Now use it in a view controller and see the output. Create MLKSecureTextField and add it to the View :

secureTextField = [[MLKSecuredTextField alloc] initWithFrame:CGRectMake(20, 20, 280, 30)];
secureTextField.delegate = self;
[self.view addSubview:secureTextField];

For your understanding, I have used three text fields, one MLKTextFieldField with custom numeric keypad, one UITextField with default keypad and one UITextField with custom numeric keypad.
Now try to enter text in the UITextField with default keyboard :
See you are able to see last character for some time, after that it will be converted to a dot. Lets see what is the behavior with UITextField with custom numeric keypad :
Observe the behavior, when ever you press a key it will be directly added as a dot in the text field. Lets see behavior with our custom MLKSecureTextField :
Have you observed that, last character is appeared for some time and after that changed to a dot. Yeah!!!! we got the expected behavior. 

Thats all from this post. Please add your comments and suggestions.


1 comment: