How We Created LUNField – Animated Adaptive Text field for Mobile Apps

Tired of standard iOS text fields and can’t find a perfect fit among the custom ones? Looking for text field animations for mobile apps? You’re on the right track! Keep on reading!

Animated adaptive text field for mobile apps

What motivated us to create LUNFields?

Do you like filling out lots of information while signing up for a new app or service? We doubt it. That’s why smart developers tend to ask for less data during the signup process.
However, as we all know, bigger screens on mobile phones are becoming a commonplace. As a result signup pages might look not too good with too much free space.

We assessed the market

GitHub and CocoaPods offer a lot of different custom text field examples. However none of them was good enough for us.

What we wanted was:

  • to get a text field, that could easily be divided into a few sections depending on given input.
  • to implement smooth animations while using user friendly, chill notifications.
  • to implement validity check for entered text.

It’s hard to convey how disappointed we were when we couldn’t find even one custom solution that would offer validity check for entered text.

Due to failure to find a ready-to-go solution we decided to develop our own text field with animation.

Read also:

How did we create it?

Well, first of all we concentrated all our inspiration and kicked off.

As we mentioned before, our first priority was to get adaptive text fields, that could control their own size and the size of the sections inside. The first thing we wrote was the LUNFieldDataSource protocol.

@protocol LUNFieldDataSource <NSObject>

@required
- (NSUInteger)numberOfSectionsInTextField:(LUNField *)LUNField;

- (NSUInteger)numberOfCharactersInSection:(NSInteger)section inTextField:(LUNField *)LUNField;

@end

This protocol is a groundwork for LUNField. It’s used to determine the number of text fields in a group and number of characters for each of them.

Having all that data, we can go ahead to our UIView (which is yet blank), where a predetermined number of text fields is created, their sizes calculated proportionally.

(void)updateTextFields method is responsible for the process described above.

- (void)updateTextFields {
    NSUInteger numberOfSections = [_dataSource numberOfSectionsInTextField:self];
    NSUInteger lengthOfSections[numberOfSections];
    NSUInteger summLength;
    for (NSUInteger i = 0; i < numberOfSections; ++i) { lengthOfSections[i] = [_dataSource numberOfCharactersInSection:i inTextField:self]; summLength += lengthOfSections[i]; } if (numberOfSections > 1) {
        self.isMultifield = YES;
    } else {
        self.isMultifield = NO;
    }
    for (NSUInteger i = 0; i < numberOfSections; ++i) { LUNTextFieldWithTextInsets *textField = [LUNTextFieldWithTextInsets new]; [self setupTextField:textField]; [self addSubview:textField]; [self addConstraint:[NSLayoutConstraint constraintWithItem:self attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:textField attribute:NSLayoutAttributeHeight multiplier:1.0f constant:0.0f]]; [self addConstraint:[NSLayoutConstraint constraintWithItem:self attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:textField attribute:NSLayoutAttributeTop multiplier:1.0f constant:0.0f]]; if (i == 0) { [self addConstraint:[NSLayoutConstraint constraintWithItem:self attribute:NSLayoutAttributeLeading relatedBy:NSLayoutRelationEqual toItem:textField attribute:NSLayoutAttributeLeading multiplier:1.0f constant:-1 * kLUNDefaultMarginBetweenTextFields]]; } if (i > 0) {
            [self addConstraint:[NSLayoutConstraint constraintWithItem:self.textFields[i - 1] attribute:NSLayoutAttributeTrailing relatedBy:NSLayoutRelationEqual toItem:textField attribute:NSLayoutAttributeLeading multiplier:1.0f constant:-kLUNDefaultMarginBetweenTextFields]];
            [self addConstraint:[NSLayoutConstraint constraintWithItem:textField attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:self.textFields[0] attribute:NSLayoutAttributeWidth multiplier:(lengthOfSections[i] / (CGFloat) lengthOfSections[0]) constant:0.0f]];
        }
        if (i == numberOfSections - 1) {
            [self addConstraint:[NSLayoutConstraint constraintWithItem:self attribute:NSLayoutAttributeTrailing relatedBy:NSLayoutRelationEqual toItem:textField attribute:NSLayoutAttributeTrailing multiplier:1.0f constant:kLUNDefaultMarginBetweenTextFields]];
            textField.inputAccessoryView = self.accessoryView;
        }
        [self layoutIfNeeded];
        CGAffineTransform concat = CGAffineTransformConcat(CGAffineTransformMakeScale(1 + 2 * kLUNDefaultMarginBetweenTextFields / (self.frame.size.width - (numberOfSections + 1) * kLUNDefaultMarginBetweenTextFields) / numberOfSections, 0.001), CGAffineTransformMakeTranslation(0, textField.frame.size.height / 2 - self.borderWidth));
        textField.transform = concat;
        [self.textFields addObject:textField];
    }
}

Next thing we did was created a UILabel to store placeholders for text fields. The label alignment is customizable in LUNField.

- (void)updatePlaceholderLabel {
    self.placeholderLabel = [UILabel new];
    [self addSubview:self.placeholderLabel];
    self.placeholderLabel.translatesAutoresizingMaskIntoConstraints = NO;
    self.placeholderLabel.textColor = self.placeholderFontColor;
    self.placeholderLabel.font = self.placeholderFont;
    self.placeholderLabel.text = self.placeholderText;
    [self layoutIfNeeded];
    self.placeholderLabelTopConstraint = [NSLayoutConstraint constraintWithItem:self attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.placeholderLabel attribute:NSLayoutAttributeTop multiplier:1.0f constant:0];
    self.placeholderLabelTopConstraint.constant = -1 * (self.frame.size.height / 2 - self.placeholderLabel.frame.size.height / 2);
    [self addConstraint:self.placeholderLabelTopConstraint];
    [self layoutIfNeeded];
    self.placeholderLabel.textAlignment = NSTextAlignmentCenter;
    [self setupPlaceholderPositionWithAlignment:self.placeholderAlignment];
}

To get smooth animations we used UITapGestureRecognizer and wrote a custom handler for it. The handler processes current LUNField state and, if necessary, animates the field.

- (void)viewTapped {
    BOOL isEdited = NO;
    for (UITextField *textField in self.textFields) {
        if (textField.text.length &amp;gt; 0) {
            isEdited = YES;
            break;
        }
    }
    
    if (!self.isEditing &amp;amp;&amp;amp; !isEdited) {
        self.isEditing = YES;
        [self.textFields[0] becomeFirstResponder];
        void (^placeholderLabelAnimation)() = ^void() {
            self.placeholderLabel.textColor = self.upperPlaceholderFontColor;
            CGAffineTransform translation;
            switch (self.placeholderLabel.textAlignment) {
                case NSTextAlignmentCenter:
                    translation = CGAffineTransformMakeTranslation(0, 0);
                    break;
                case NSTextAlignmentLeft:
                    translation = CGAffineTransformMakeTranslation(-1 * (1 - kLUNScaleFactor) / 2 * self.placeholderLabel.frame.size.width + kLUNDefaultMarginBetweenTextFields, 0);
                    break;
                case NSTextAlignmentRight:
                    translation = CGAffineTransformMakeTranslation((1 - kLUNScaleFactor) / 2 * self.placeholderLabel.frame.size.width - 2 * kLUNDefaultMarginBetweenTextFields, 0);
                    break;
                default:
                    translation = CGAffineTransformMakeTranslation(0, 0);
                    break;
            }
            CGAffineTransform resultTransform = CGAffineTransformConcat(CGAffineTransformMakeScale(kLUNScaleFactor, kLUNScaleFactor), translation);
            self.placeholderLabel.transform = resultTransform;
        };
        
        void (^textFieldsAnimation)() = ^void() {
            for (UITextField *currentTexField in self.textFields) {
                currentTexField.layer.borderColor = self.upperBorderColor.CGColor;
                currentTexField.transform = CGAffineTransformIdentity;
            }
        };
        
        void (^borderAndUnderliningAnimation)() = ^void() {
            if (self.borderColor) {
                self.lineView.alpha = 0.0f;
            }
            self.lineView.backgroundColor = self.upperBorderColor ? self.upperBorderColor : self.underliningColor;
        };
        
        void (^placeholderImageViewAnimation)() = ^void() {
            self.placeholderImageView.alpha = 0.0f;
        };
        
        self.placeholderImageViewTopConstraint.constant = self.placeholderImageView.frame.size.height;
        self.placeholderLabelTopConstraint.constant = self.placeholderLabel.frame.size.height;
        
        self.lineViewLeftConstraint.constant = -kLUNDefaultMarginBetweenTextFields;
        self.lineViewRightConstraint.constant = kLUNDefaultMarginBetweenTextFields;
        
        [UIView animateWithDuration:kLUNUpAnimationDuration delay:0 usingSpringWithDamping:kLUNDamping initialSpringVelocity:kLUNInitialVelocity options:UIViewAnimationOptionCurveEaseInOut animations:^{
            placeholderLabelAnimation();
            borderAndUnderliningAnimation();
            placeholderImageViewAnimation();
            textFieldsAnimation();
            [self layoutIfNeeded];
            
        }                completion:nil];
    }
}

At this point our LUNField was practically ready-to-go. You could enter some text and you’d see its folding/unfolding.

After that we added LUNFieldDelegate, which notifies a user (developer) of any changes in the LUNField.

@protocol LUNFieldDelegate &amp;lt;NSObject&amp;gt;

@optional
- (void)LUNFieldTextChanged:(LUNField *)LUNField;

- (void)LUNFieldHasEndedEditing:(LUNField *)LUNField;

- (BOOL)LUNField:(LUNField *)LUNField containsValidText:(NSString *)text;

@end

One of the LUNFieldDelegate methods requests a check for validity of entered text into a LUNField. Depending on a result this method triggers animation transitions between LUNField states. These are usually border color change (borderColor), placeholder color altering or hint image change (concerning entered text validity)

- (void)animateValidation:(BOOL)valid {
    UIColor *resultBorderColor;
    UIColor *resultPlaceholderLabelColor;
    NSString *resultLabelText;
    UIImage *resultStateImage;
    if (valid) {
        resultBorderColor = self.correctStateBorderColor ? self.correctStateBorderColor : self.borderColor;
        resultPlaceholderLabelColor = self.correctStatePlaceholderLabelTextColor ? self.correctStatePlaceholderLabelTextColor : self.placeholderFontColor;
        resultLabelText = self.correctLabelText ? self.correctLabelText : self.placeholderText;
        resultStateImage = self.correctStateImage;
    } else {
        resultBorderColor = self.incorrectStateBorderColor ? self.incorrectStateBorderColor : self.borderColor;
        resultPlaceholderLabelColor = self.incorrectStatePlaceholderLabelTextColor ? self.incorrectStatePlaceholderLabelTextColor : self.placeholderFontColor;
        resultLabelText = self.incorrectLabelText ? self.incorrectLabelText : self.placeholderText;
        resultStateImage = self.incorrectStateImage;
    }
    
    void (^borderColorAnimation)() = ^void() {
        for (UITextField *currentTextField in self.textFields) {
            currentTextField.layer.borderColor = resultBorderColor.CGColor;
        }
    };
    
    void (^placeholderLabelAnimation)() = ^void() {
        self.placeholderLabel.text = resultLabelText;
        self.placeholderLabel.textColor = resultPlaceholderLabelColor;
        [self easeInOutView:self.placeholderLabel];
    };
    
    void (^stateImageViewAnimation)() = ^void() {
        self.stateImageView.image = resultStateImage;
        self.stateImageView.alpha = 1.0f;
        [self easeInOutView:self.stateImageView];
    };
    
    [UIView animateWithDuration:kLUNChangeStateAnimationDuration delay:0 options:UIViewAnimationOptionCurveEaseInOut animations:^{
        borderColorAnimation();
        placeholderLabelAnimation();
        stateImageViewAnimation();
    } completion:nil];
}

Why use LUNFields?

We always put user experience as our first priority. That’s why we took care of a few things that have been annoying iPhone and iPad users for a while now. Here’s the list of what we’ve achieved:

Divide and rule

Our solution offers a text field that can be divided into a few sections. LunFields will automatically calculate the size of each section proportionally. All you need is just set maximum number of characters for each of the sections.

Developing animated adaptive text field

Smart placeholders

Have you ever started filling out a text field and suddenly got distracted? Once you get back to your phone you often have no idea what was the text field all about. So you have to clear the whole thing and then start all over again.

LUNFields solves this problem. Our placeholders are located above their text fields. Besides they never disappear, so you won’t get confused about the kind of data a text field requires.

Juicy animations

As we all know good user experience is vital for any application. That’s why we decided to create animations for text fields, that would compel users. It wouldn’t be a LunApps product if it didn’t have smooth eye-catching animations.

We notify, not annoy

Keeping the overall idea of smoothness we took care of the notifications. They are soft, chill and user friendly.

How can you use LUNFields?

LUNField is practically an all-purpose tool. Here are some examples of its usage:

One can use it as a plain text field either with or without a frame. The placeholder is movable and stays on the screen at all times.

In addition it can be used to let user enter credit card info, telephone number, SMS codes and all different kinds of data that require special formatting. It’s all customizable.

LUNField gives users better understanding of what they need to fill out into text fields. Friendly soft notifications are always ready to help .

In addition, developers can easily customize LUNField for a new design or another color scheme.

As you see, it’s very easy to build animated Adaptive Text field for Mobile Apps.

Conclusion

In this article we did our best to describe all the upsides of our LUNField solution with text input effects for iOS apps development.

But don’t take our word for it. Go ahead and try it for yourself on GitHub. And don’t forget to give us a star! 😉

Comments

Vladimir Sharavara

Vladimir Sharavara