blob: c7f8147098db5fadc3c2fac8107f4af3c4315709 [file] [log] [blame] [edit]
// Copyright 2017-present the Material Components for iOS authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#import "MaterialTextFields+Theming.h"
#import "MaterialTextFields.h"
@interface TextFieldOutlinedObjectiveCExample
: UIViewController <UITextFieldDelegate, UITextViewDelegate>
@property(nonatomic) MDCTextInputControllerOutlined *nameController;
@property(nonatomic) MDCTextInputControllerOutlined *addressController;
@property(nonatomic) MDCTextInputControllerOutlined *cityController;
@property(nonatomic) MDCTextInputControllerOutlined *stateController;
@property(nonatomic) MDCTextInputControllerOutlined *zipController;
@property(nonatomic) MDCTextInputControllerOutlined *phoneController;
@property(nonatomic) MDCTextInputControllerOutlinedTextArea *messageController;
@property(nonatomic, strong) MDCContainerScheme *containerScheme;
@property(nonatomic) UIScrollView *scrollView;
@end
@implementation TextFieldOutlinedObjectiveCExample
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
- (void)styleTextInputController:(id<MDCTextInputController>)controller {
if ([controller isKindOfClass:[MDCTextInputControllerOutlined class]]) {
MDCTextInputControllerOutlined *outlinedController =
(MDCTextInputControllerOutlined *)controller;
[outlinedController applyThemeWithScheme:self.containerScheme];
}
}
- (void)viewDidLoad {
[super viewDidLoad];
if (!self.containerScheme) {
self.containerScheme = [[MDCContainerScheme alloc] init];
}
self.view.backgroundColor = self.containerScheme.colorScheme.backgroundColor;
[self registerKeyboardNotifications];
[self setupScrollView];
MDCTextField *textFieldName = [[MDCTextField alloc] init];
textFieldName.translatesAutoresizingMaskIntoConstraints = NO;
[self.scrollView addSubview:textFieldName];
textFieldName.delegate = self;
textFieldName.clearButtonMode = UITextFieldViewModeUnlessEditing;
textFieldName.backgroundColor = [UIColor whiteColor];
UIImage *leadingImage = [UIImage
imageNamed:@"ic_search"
inBundle:[NSBundle
bundleForClass:[TextFieldOutlinedObjectiveCExample class]]
compatibleWithTraitCollection:nil];
textFieldName.leadingView = [[UIImageView alloc] initWithImage:leadingImage];
textFieldName.leadingViewMode = UITextFieldViewModeAlways;
UIImage *trailingImage = [UIImage
imageNamed:@"ic_done"
inBundle:[NSBundle
bundleForClass:[TextFieldOutlinedObjectiveCExample class]]
compatibleWithTraitCollection:nil];
textFieldName.trailingView = [[UIImageView alloc] initWithImage:trailingImage];
textFieldName.trailingViewMode = UITextFieldViewModeAlways;
self.nameController = [[MDCTextInputControllerOutlined alloc] initWithTextInput:textFieldName];
self.nameController.placeholderText = @"Full Name";
[self styleTextInputController:self.nameController];
MDCTextField *textFieldAddress = [[MDCTextField alloc] init];
textFieldAddress.translatesAutoresizingMaskIntoConstraints = NO;
[self.scrollView addSubview:textFieldAddress];
textFieldAddress.delegate = self;
textFieldAddress.clearButtonMode = UITextFieldViewModeUnlessEditing;
textFieldAddress.backgroundColor = [UIColor whiteColor];
self.addressController =
[[MDCTextInputControllerOutlined alloc] initWithTextInput:textFieldAddress];
self.addressController.placeholderText = @"Address";
[self styleTextInputController:self.addressController];
MDCTextField *textFieldCity = [[MDCTextField alloc] init];
textFieldCity.translatesAutoresizingMaskIntoConstraints = NO;
[self.scrollView addSubview:textFieldCity];
textFieldCity.delegate = self;
textFieldCity.clearButtonMode = UITextFieldViewModeUnlessEditing;
textFieldCity.backgroundColor = [UIColor whiteColor];
self.cityController = [[MDCTextInputControllerOutlined alloc] initWithTextInput:textFieldCity];
self.cityController.placeholderText = @"City";
[self styleTextInputController:self.cityController];
MDCTextField *textFieldState = [[MDCTextField alloc] init];
textFieldState.translatesAutoresizingMaskIntoConstraints = NO;
textFieldState.delegate = self;
textFieldState.clearButtonMode = UITextFieldViewModeUnlessEditing;
textFieldState.backgroundColor = [UIColor whiteColor];
self.stateController = [[MDCTextInputControllerOutlined alloc] initWithTextInput:textFieldState];
self.stateController.placeholderText = @"State";
[self styleTextInputController:self.stateController];
MDCTextField *textFieldZip = [[MDCTextField alloc] init];
textFieldZip.translatesAutoresizingMaskIntoConstraints = NO;
textFieldZip.delegate = self;
textFieldZip.clearButtonMode = UITextFieldViewModeUnlessEditing;
textFieldZip.backgroundColor = [UIColor whiteColor];
self.zipController = [[MDCTextInputControllerOutlined alloc] initWithTextInput:textFieldZip];
self.zipController.placeholderText = @"Zip Code";
[self styleTextInputController:self.zipController];
UIView *stateZip = [[UIView alloc] initWithFrame:CGRectZero];
stateZip.translatesAutoresizingMaskIntoConstraints = NO;
[self.scrollView addSubview:stateZip];
stateZip.opaque = NO;
[stateZip addSubview:textFieldState];
[stateZip addSubview:textFieldZip];
MDCTextField *textFieldPhone = [[MDCTextField alloc] init];
textFieldPhone.translatesAutoresizingMaskIntoConstraints = NO;
[self.scrollView addSubview:textFieldPhone];
textFieldPhone.delegate = self;
textFieldPhone.clearButtonMode = UITextFieldViewModeUnlessEditing;
textFieldPhone.backgroundColor = [UIColor whiteColor];
self.phoneController = [[MDCTextInputControllerOutlined alloc] initWithTextInput:textFieldPhone];
self.phoneController.placeholderText = @"Phone Number";
self.phoneController.helperText = @"XXX-XXX-XXXX";
[self styleTextInputController:self.phoneController];
MDCMultilineTextField *textFieldMessage = [[MDCMultilineTextField alloc] init];
textFieldMessage.translatesAutoresizingMaskIntoConstraints = NO;
[self.scrollView addSubview:textFieldMessage];
textFieldMessage.textView.delegate = self;
self.messageController =
[[MDCTextInputControllerOutlinedTextArea alloc] initWithTextInput:textFieldMessage];
textFieldMessage.text = @"This is where you could put a multi-line message like an email.\n\n"
"It can even handle new lines.";
self.messageController.placeholderText = @"Message";
[self styleTextInputController:self.messageController];
id<UILayoutSupport> topGuide = self.topLayoutGuide;
NSDictionary *views = @{
@"topGuide" : topGuide,
@"name" : textFieldName,
@"address" : textFieldAddress,
@"city" : textFieldCity,
@"state" : textFieldState,
@"zip" : textFieldZip,
@"stateZip" : stateZip,
@"phone" : textFieldPhone,
@"message" : textFieldMessage
};
NSMutableArray<NSLayoutConstraint *> *constraints = [NSMutableArray
arrayWithArray:[NSLayoutConstraint
constraintsWithVisualFormat:
@"V:[topGuide]-[name]-[address]-[city]-[stateZip]-[phone]-[message]"
options:NSLayoutFormatAlignAllLeading |
NSLayoutFormatAlignAllTrailing
metrics:nil
views:views]];
[constraints addObject:[NSLayoutConstraint constraintWithItem:textFieldName
attribute:NSLayoutAttributeLeading
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeLeadingMargin
multiplier:1
constant:0]];
[constraints addObject:[NSLayoutConstraint constraintWithItem:textFieldName
attribute:NSLayoutAttributeTrailing
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeTrailingMargin
multiplier:1
constant:0]];
if (@available(iOS 11.0, *)) {
[NSLayoutConstraint activateConstraints:@[
[NSLayoutConstraint constraintWithItem:textFieldName
attribute:NSLayoutAttributeTop
relatedBy:NSLayoutRelationEqual
toItem:self.scrollView.contentLayoutGuide
attribute:NSLayoutAttributeTop
multiplier:1
constant:20],
[NSLayoutConstraint constraintWithItem:textFieldMessage
attribute:NSLayoutAttributeBottom
relatedBy:NSLayoutRelationEqual
toItem:self.scrollView.contentLayoutGuide
attribute:NSLayoutAttributeBottomMargin
multiplier:1
constant:-20]
]];
} else {
[NSLayoutConstraint activateConstraints:@[
[NSLayoutConstraint constraintWithItem:textFieldName
attribute:NSLayoutAttributeTop
relatedBy:NSLayoutRelationEqual
toItem:self.scrollView
attribute:NSLayoutAttributeTop
multiplier:1
constant:20],
[NSLayoutConstraint constraintWithItem:textFieldMessage
attribute:NSLayoutAttributeBottom
relatedBy:NSLayoutRelationEqual
toItem:self.scrollView
attribute:NSLayoutAttributeBottomMargin
multiplier:1
constant:-20]
]];
}
[constraints
addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[state(80)]-[zip]|"
options:0
metrics:nil
views:views]];
[constraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[state]|"
options:0
metrics:nil
views:views]];
[constraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[zip]|"
options:0
metrics:nil
views:views]];
[NSLayoutConstraint activateConstraints:constraints];
}
- (void)setupScrollView {
self.scrollView = [[UIScrollView alloc] initWithFrame:CGRectZero];
self.scrollView.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addSubview:self.scrollView];
[NSLayoutConstraint
activateConstraints:[NSLayoutConstraint
constraintsWithVisualFormat:@"V:|[scrollView]|"
options:0
metrics:nil
views:@{@"scrollView" : self.scrollView}]];
[NSLayoutConstraint
activateConstraints:[NSLayoutConstraint
constraintsWithVisualFormat:@"H:|[scrollView]|"
options:0
metrics:nil
views:@{@"scrollView" : self.scrollView}]];
UIEdgeInsets margins = UIEdgeInsetsMake(0, 16, 0, 16);
self.scrollView.layoutMargins = margins;
UITapGestureRecognizer *tapGestureRecognizer =
[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapDidTouch:)];
[self.scrollView addGestureRecognizer:tapGestureRecognizer];
}
#pragma mark - UITextFieldDelegate
- (BOOL)textFieldShouldReturn:(UITextField *)textField {
[textField resignFirstResponder];
if (textField == (UITextField *)self.nameController.textInput) {
if ([textField.text rangeOfCharacterFromSet:[NSCharacterSet decimalDigitCharacterSet]].length &&
![self.nameController.errorText isEqualToString:@"Error: You cannot enter numbers"]) {
// The entered text contains numbers and we have not set an error
[self.nameController setErrorText:@"You cannot enter numbers" errorAccessibilityValue:nil];
// Since we are doing manual layout, we need to react to the expansion of the input that will
// come from setting an error.
[self.view setNeedsLayout];
} else if (self.nameController.errorText != nil) {
// There should be no error but error text is being shown.
[self.nameController setErrorText:nil errorAccessibilityValue:nil];
// Since we are doing manual layout, we need to react to the contraction of the input that
// will come from setting an error.
[self.view setNeedsLayout];
}
} else if (textField == (UITextField *)self.cityController.textInput) {
if ([textField.text rangeOfCharacterFromSet:[[NSCharacterSet letterCharacterSet] invertedSet]]
.length > 0) {
[self.cityController setErrorText:@"Error: City can only contain letters"
errorAccessibilityValue:nil];
} else {
[self.cityController setErrorText:nil errorAccessibilityValue:nil];
}
} else if (textField == (UITextField *)self.phoneController.textInput) {
if (![self isValidPhoneNumber:textField.text partially:NO]) {
[self.phoneController setErrorText:@"Invalid Phone Number" errorAccessibilityValue:nil];
} else if (self.phoneController.errorText != nil) {
[self.phoneController setErrorText:nil errorAccessibilityValue:nil];
}
} else if (textField == (UITextField *)self.zipController.textInput) {
if ([textField.text rangeOfCharacterFromSet:[NSCharacterSet letterCharacterSet]].length > 0) {
[self.zipController setErrorText:@"Error: Zip can only contain numbers"
errorAccessibilityValue:nil];
} else if (textField.text.length > 5) {
[self.zipController setErrorText:@"Error: Zip can only contain five digits"
errorAccessibilityValue:nil];
} else {
[self.zipController setErrorText:nil errorAccessibilityValue:nil];
}
}
return NO;
}
- (BOOL)textFieldShouldClear:(UITextField *)textField {
if (textField == (UITextField *)self.nameController.textInput) {
[self.nameController setErrorText:nil errorAccessibilityValue:nil];
} else if (textField == (UITextField *)self.cityController.textInput) {
[self.cityController setErrorText:nil errorAccessibilityValue:nil];
} else if (textField == (UITextField *)self.phoneController.textInput) {
[self.phoneController setErrorText:nil errorAccessibilityValue:nil];
} else if (textField == (UITextField *)self.zipController.textInput) {
[self.zipController setErrorText:nil errorAccessibilityValue:nil];
}
return YES;
}
- (BOOL)textField:(UITextField *)textField
shouldChangeCharactersInRange:(NSRange)range
replacementString:(NSString *)string {
NSString *finishedString = [textField.text stringByReplacingCharactersInRange:range
withString:string];
if (textField == (UITextField *)self.nameController.textInput) {
if ([finishedString rangeOfCharacterFromSet:[NSCharacterSet decimalDigitCharacterSet]].length &&
![self.nameController.errorText isEqualToString:@"Error: You cannot enter numbers"]) {
// The entered text contains numbers and we have not set an error
[self.nameController setErrorText:@"You cannot enter numbers" errorAccessibilityValue:nil];
// Since we are doing manual layout, we need to react to the expansion of the input that will
// come from setting an error.
[self.view setNeedsLayout];
} else if (self.nameController.errorText != nil) {
// There should be no error but error text is being shown.
[self.nameController setErrorText:nil errorAccessibilityValue:nil];
// Since we are doing manual layout, we need to react to the contraction of the input that
// will come from setting an error.
[self.view setNeedsLayout];
}
}
if (textField == (UITextField *)self.cityController.textInput) {
if ([finishedString rangeOfCharacterFromSet:[[NSCharacterSet letterCharacterSet] invertedSet]]
.length > 0) {
[self.cityController setErrorText:@"Error: City can only contain letters"
errorAccessibilityValue:nil];
} else {
[self.cityController setErrorText:nil errorAccessibilityValue:nil];
}
}
if (textField == (UITextField *)self.zipController.textInput) {
if ([finishedString rangeOfCharacterFromSet:[NSCharacterSet letterCharacterSet]].length > 0) {
[self.zipController setErrorText:@"Error: Zip can only contain numbers"
errorAccessibilityValue:nil];
} else if (finishedString.length > 5) {
[self.zipController setErrorText:@"Error: Zip can only contain five digits"
errorAccessibilityValue:nil];
} else {
[self.zipController setErrorText:nil errorAccessibilityValue:nil];
}
}
if (textField == (UITextField *)self.phoneController.textInput) {
if (![self isValidPhoneNumber:finishedString partially:YES] &&
![self.phoneController.errorText isEqualToString:@"Error: Invalid phone number"]) {
// The entered text is not valid and we have not set an error
[self.phoneController setErrorText:@"Invalid phone number" errorAccessibilityValue:nil];
// The text field has helper text that already expanded the frame so we don't need to call
// setNeedsLayout.
} else if (self.phoneController.errorText != nil) {
[self.phoneController setErrorText:nil errorAccessibilityValue:nil];
// The text field has helper text and cannot contract the frame so we don't need to call
// setNeedsLayout.
}
}
return YES;
}
#pragma mark - UITextViewDelegate
- (void)textViewDidChange:(UITextView *)textView {
NSLog(@"%@", textView.text);
}
#pragma mark - Gesture Handling
- (void)tapDidTouch:(UIGestureRecognizer *)sender {
[self.view endEditing:YES];
}
#pragma mark - Keyboard Handling
- (void)registerKeyboardNotifications {
NSNotificationCenter *defaultCenter = [NSNotificationCenter defaultCenter];
[defaultCenter addObserver:self
selector:@selector(keyboardWillShow:)
name:UIKeyboardWillShowNotification
object:nil];
[defaultCenter addObserver:self
selector:@selector(keyboardWillShow:)
name:UIKeyboardWillChangeFrameNotification
object:nil];
[defaultCenter addObserver:self
selector:@selector(keyboardWillHide:)
name:UIKeyboardWillHideNotification
object:nil];
}
- (void)keyboardWillShow:(NSNotification *)notif {
CGRect keyboardFrame = [notif.userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue];
self.scrollView.contentInset = UIEdgeInsetsMake(0, 0, CGRectGetHeight(keyboardFrame), 0);
}
- (void)keyboardWillHide:(NSNotification *)notif {
self.scrollView.contentInset = UIEdgeInsetsZero;
}
#pragma mark - Phone Number Validation
- (BOOL)isValidPhoneNumber:(NSString *)inputString partially:(BOOL)isPartialCheck {
// In real life there would be much more robust validation that takes locale into account, checks
// against invalid phone numbers (like those that begin with 0), and perhaps even auto-inserts the
// hyphens so the user doesn't have to.
if (inputString.length == 0) {
return YES;
}
NSCharacterSet *characterSet = [NSCharacterSet characterSetWithCharactersInString:@"0123456789-"];
characterSet = [characterSet invertedSet];
BOOL isValid = ![inputString rangeOfCharacterFromSet:characterSet].length;
if (!isPartialCheck) {
isValid = isValid && inputString.length == 12;
} else {
isValid = isValid && inputString.length <= 12;
}
return isValid;
}
@end
@implementation TextFieldOutlinedObjectiveCExample (CatalogByConvention)
+ (NSDictionary *)catalogMetadata {
return @{
@"breadcrumbs" : @[ @"Text Field", @"Outlined text fields" ],
@"primaryDemo" : @YES,
@"presentable" : @YES,
};
}
@end