Andrew Overton 1d7fedd32d
[TextFields] Updates to SimpleTextFIeld and InputChipView prototype (#6647)
[TextFields] Updates to SimpleTextFIeld and InputChipView prototype

This PR contains a lot of updates to SimpleTextField. Some of these updates are for little things I noticed I got wrong after looking at the guidelines a bit more. The more substantial changes introduced here, however, are related to feedback given in the design doc overview meeting in January. As a reminder, the two takeaways from that meeting were:

1. SimpleTextField shouldn't depend on MDCContainerScheme
2. The styles should not be represented as enum cases. Instead, they should be objects.

These changes address those pieces of feedback. While I still need to get around to updating the design doc to reflect the changes, I don't see the harm in merging the updates into their home in a MaterialComponentsExamples experimental directory.

This PR also contains changes that are not directly related to SimpleTextField!

Late last year, when we had the week of free time to do whatever we wanted, I began thinking about incorporating chips into SimpleTextFields. Not far into it I decided that any view implementing input chip functionality should be its own view, i.e. it shouldn't be stuffed in a UITextField, which carries inheritance-related burden. I started work on such a view in January, and briefly used it during the SimpleTextField design doc overview meeting to demonstrate how SimpleTextField styling could be generalized across views.

Following that meeting, and following the refactoring work to address feedback on SimpleTextFields, I updated the InputChipView to see how extensible my "container style as object" approach was. I think the work here, while definitely not a finished product, demonstrates that it's fairly usable. MDCSimpleTextField (I added an MDC) and InputChipView (no MDC) both conform to a protocol called MDCContainedInputView. That protocol declares a property for objects conforming to MDCContainerStyling. There are a few other classes and protocols in there, but I will write or talk more about those in a different setting. The key here is that I think it all might work, based off these changes. There are still some loose ends, but they can be taken care of. They _will_ be taken care of if the team decides to take this direction. They maybe won't if we decide to continue investing in MDCTextFields for all of our user input needs. If we do end up deciding to bring all this into the codebase I want the next addition to this family of contained input views to be a subclass of UITextView, for multiline text input.

This gif shows the updated MDCSimpleTextField styled as an "outlined" text field:
![stf_outlined_2](https://user-images.githubusercontent.com/8020010/53023951-2a09c700-342c-11e9-88df-9605d652c46c.gif)
This gif shows the updated MDCSimpleTextField styled as a "filled" text field:
![stf_filled_2](https://user-images.githubusercontent.com/8020010/53023948-29713080-342c-11e9-9581-8891fa090ec1.gif)
This gif shows the updated InputChipView styled as an "outlined" input chip view whose chips do not wrap:
![outlined_non_wrapping_2](https://user-images.githubusercontent.com/8020010/53023945-28d89a00-342c-11e9-9138-c63ff1a4c3ea.gif)

This gif shows the updated InputChipView styled as an "outlined" input chip view whose chips wrap:
![outlined_wrapping_2](https://user-images.githubusercontent.com/8020010/53023944-28d89a00-342c-11e9-8e9d-ec185bcdf468.gif)
This gif shows the updated InputChipView styled as a "filled" input chip view whose chips do not wrap:
![filled_non_wrapping_2](https://user-images.githubusercontent.com/8020010/53023946-28d89a00-342c-11e9-9a4c-1fb5e87673e9.gif)
This gif shows the updated InputChipView styled as a "filled" input chip view whose chips wrap:
![filled_wrapping_2](https://user-images.githubusercontent.com/8020010/53023947-29713080-342c-11e9-98d2-3fe2f971d878.gif)
2019-02-20 09:47:42 -05:00

419 lines
19 KiB
Objective-C

// Copyright 2019-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.h"
@interface TextFieldCustomFontExample : UIViewController <UITextFieldDelegate, UITextViewDelegate>
@property(nonatomic) MDCTextInputControllerOutlined *systemFontController;
@property(nonatomic) MDCTextInputControllerOutlined *systemFontDynamicController;
@property(nonatomic) MDCTextInputControllerOutlined *customFontController;
@property(nonatomic) MDCTextInputControllerOutlined *customFontDynamicController;
@property(nonatomic) MDCTextInputControllerOutlinedTextArea *multilineController;
@property(nonatomic) MDCTextInputControllerOutlinedTextArea *multilineDynamicController;
@property(nonatomic) MDCTextInputControllerOutlinedTextArea *multilineCustomFontDynamicController;
@property(nonatomic) UIFont *normalTextFont;
@property(nonatomic) UIScrollView *scrollView;
@end
@implementation TextFieldCustomFontExample
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor whiteColor];
[self registerKeyboardNotifications];
[self setupScrollView];
// Create a System Font TextField.
MDCTextField *systemFontTextField = [self setupSystemFontTextField];
// Create a System Font Dynamic TextField.
MDCTextField *systemFontDynamicTextField = [self setupSystemFontDynamicTextField];
// Create a Custom Font TextField.
MDCTextField *customFontTextField = [self setupCustomFontTextField];
;
// Create a Custom Font Dynamic TextField.
MDCTextField *customFontDynamicTextField = [self setupCustomFontDynamicTextField];
;
// Create a System Multiline TextField.
MDCMultilineTextField *multilineTextField = [self setupSystemMultilineTextField];
// Create a System Multiline Dynamic TextField.
MDCMultilineTextField *multilineDynamicTextField = [self setupSystemMultilineDynamicTextField];
// Create a Custom Multiline Dyamic TextField.
MDCMultilineTextField *multilineCustomDynamicTextField =
[self setupCustomMultilineDynamicTextField];
NSDictionary *views = @{
@"t1" : systemFontTextField,
@"t2" : systemFontDynamicTextField,
@"t3" : customFontTextField,
@"t4" : customFontDynamicTextField,
@"t5" : multilineTextField,
@"t6" : multilineDynamicTextField,
@"t7" : multilineCustomDynamicTextField
};
NSMutableArray<NSLayoutConstraint *> *constraints = [NSMutableArray
arrayWithArray:[NSLayoutConstraint
constraintsWithVisualFormat:@"V:[t1]-[t2]-[t3]-[t4]-[t5]-[t6]-[t7]"
options:NSLayoutFormatAlignAllLeading |
NSLayoutFormatAlignAllTrailing
metrics:nil
views:views]];
[constraints addObject:[NSLayoutConstraint constraintWithItem:systemFontTextField
attribute:NSLayoutAttributeLeading
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeLeadingMargin
multiplier:1
constant:0]];
[constraints addObject:[NSLayoutConstraint constraintWithItem:systemFontTextField
attribute:NSLayoutAttributeTrailing
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeTrailingMargin
multiplier:1
constant:0]];
if (@available(iOS 11.0, *)) {
[NSLayoutConstraint activateConstraints:@[
[NSLayoutConstraint constraintWithItem:systemFontTextField
attribute:NSLayoutAttributeTop
relatedBy:NSLayoutRelationEqual
toItem:self.scrollView.contentLayoutGuide
attribute:NSLayoutAttributeTop
multiplier:1
constant:20],
[NSLayoutConstraint constraintWithItem:multilineCustomDynamicTextField
attribute:NSLayoutAttributeBottom
relatedBy:NSLayoutRelationEqual
toItem:self.scrollView.contentLayoutGuide
attribute:NSLayoutAttributeBottomMargin
multiplier:1
constant:-20]
]];
} else {
[NSLayoutConstraint activateConstraints:@[
[NSLayoutConstraint constraintWithItem:systemFontTextField
attribute:NSLayoutAttributeTop
relatedBy:NSLayoutRelationEqual
toItem:self.scrollView
attribute:NSLayoutAttributeTop
multiplier:1
constant:20],
[NSLayoutConstraint constraintWithItem:multilineCustomDynamicTextField
attribute:NSLayoutAttributeBottom
relatedBy:NSLayoutRelationEqual
toItem:self.scrollView
attribute:NSLayoutAttributeBottomMargin
multiplier:1
constant:-20]
]];
}
[NSLayoutConstraint activateConstraints:constraints];
}
- (void)setupScrollView {
self.scrollView = [[UIScrollView alloc] initWithFrame:CGRectZero];
self.scrollView.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addSubview:self.scrollView];
NSArray *constraints =
[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[scrollView]|"
options:0
metrics:nil
views:@{@"scrollView" : self.scrollView}];
[NSLayoutConstraint activateConstraints:constraints];
constraints = [NSLayoutConstraint constraintsWithVisualFormat:@"H:|[scrollView]|"
options:0
metrics:nil
views:@{@"scrollView" : self.scrollView}];
[NSLayoutConstraint activateConstraints:constraints];
UIEdgeInsets margins = UIEdgeInsetsMake(0, 16, 0, 16);
self.scrollView.layoutMargins = margins;
UITapGestureRecognizer *tapGestureRecognizer =
[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapDidTouch:)];
[self.scrollView addGestureRecognizer:tapGestureRecognizer];
}
- (MDCTextField *)setupSystemFontTextField {
MDCTextField *systemFontTextField = [[MDCTextField alloc] init];
self.normalTextFont = systemFontTextField.font;
systemFontTextField.translatesAutoresizingMaskIntoConstraints = NO;
[self.scrollView addSubview:systemFontTextField];
systemFontTextField.delegate = self;
systemFontTextField.clearButtonMode = UITextFieldViewModeUnlessEditing;
systemFontTextField.backgroundColor = [UIColor whiteColor];
self.systemFontController =
[[MDCTextInputControllerOutlined alloc] initWithTextInput:systemFontTextField];
self.systemFontController.placeholderText = @"System Font";
self.systemFontController.mdc_adjustsFontForContentSizeCategory = NO;
return systemFontTextField;
}
- (MDCTextField *)setupSystemFontDynamicTextField {
MDCTextField *systemFontDynamicTextField = [[MDCTextField alloc] init];
systemFontDynamicTextField.translatesAutoresizingMaskIntoConstraints = NO;
[self.scrollView addSubview:systemFontDynamicTextField];
systemFontDynamicTextField.delegate = self;
systemFontDynamicTextField.clearButtonMode = UITextFieldViewModeUnlessEditing;
systemFontDynamicTextField.backgroundColor = [UIColor whiteColor];
self.systemFontDynamicController =
[[MDCTextInputControllerOutlined alloc] initWithTextInput:systemFontDynamicTextField];
self.systemFontDynamicController.placeholderText = @"System Font - Dynamic";
self.systemFontDynamicController.mdc_adjustsFontForContentSizeCategory = YES;
return systemFontDynamicTextField;
}
- (MDCTextField *)setupCustomFontTextField {
MDCTextField *customFontTextField = [[MDCTextField alloc] init];
customFontTextField.translatesAutoresizingMaskIntoConstraints = NO;
[self.scrollView addSubview:customFontTextField];
customFontTextField.delegate = self;
customFontTextField.clearButtonMode = UITextFieldViewModeUnlessEditing;
customFontTextField.backgroundColor = [UIColor whiteColor];
self.customFontController =
[[MDCTextInputControllerOutlined alloc] initWithTextInput:customFontTextField];
self.customFontController.placeholderText = @"Custom Font";
self.customFontController.mdc_adjustsFontForContentSizeCategory = NO;
self.customFontController.leadingUnderlineLabelFont = [UIFont fontWithName:@"AmericanTypewriter"
size:12];
self.customFontController.trailingUnderlineLabelFont = [UIFont fontWithName:@"Chalkduster"
size:12];
self.customFontController.inlinePlaceholderFont = [UIFont fontWithName:@"AmericanTypewriter"
size:12];
self.customFontController.textInputFont = [UIFont fontWithName:@"Chalkduster" size:16];
return customFontTextField;
}
- (MDCTextField *)setupCustomFontDynamicTextField {
MDCTextField *customFontDynamicTextField = [[MDCTextField alloc] init];
customFontDynamicTextField.translatesAutoresizingMaskIntoConstraints = NO;
customFontDynamicTextField.delegate = self;
customFontDynamicTextField.clearButtonMode = UITextFieldViewModeUnlessEditing;
customFontDynamicTextField.backgroundColor = [UIColor whiteColor];
self.customFontDynamicController =
[[MDCTextInputControllerOutlined alloc] initWithTextInput:customFontDynamicTextField];
self.customFontDynamicController.placeholderText = @"Custom Font - Dynamic";
self.customFontDynamicController.helperText = @"Helper";
self.customFontDynamicController.leadingUnderlineLabelFont = [UIFont fontWithName:@"Zapfino"
size:12];
self.customFontDynamicController.trailingUnderlineLabelFont = [UIFont fontWithName:@"Chalkduster"
size:12];
self.customFontDynamicController.inlinePlaceholderFont = [UIFont fontWithName:@"Zapfino" size:12];
self.customFontDynamicController.textInputFont = [UIFont fontWithName:@"Zapfino" size:16];
self.customFontDynamicController.mdc_adjustsFontForContentSizeCategory = YES;
[self.scrollView addSubview:customFontDynamicTextField];
return customFontDynamicTextField;
}
- (MDCMultilineTextField *)setupSystemMultilineTextField {
MDCMultilineTextField *multilineTextField = [[MDCMultilineTextField alloc] init];
multilineTextField.translatesAutoresizingMaskIntoConstraints = NO;
[self.scrollView addSubview:multilineTextField];
multilineTextField.textView.delegate = self;
self.multilineController =
[[MDCTextInputControllerOutlinedTextArea alloc] initWithTextInput:multilineTextField];
self.multilineController.placeholderText = @"Multiline Text";
self.multilineController.mdc_adjustsFontForContentSizeCategory = NO;
return multilineTextField;
}
- (MDCMultilineTextField *)setupSystemMultilineDynamicTextField {
MDCMultilineTextField *multilineDynamicTextField = [[MDCMultilineTextField alloc] init];
multilineDynamicTextField.translatesAutoresizingMaskIntoConstraints = NO;
[self.scrollView addSubview:multilineDynamicTextField];
multilineDynamicTextField.textView.delegate = self;
self.multilineDynamicController =
[[MDCTextInputControllerOutlinedTextArea alloc] initWithTextInput:multilineDynamicTextField];
self.multilineDynamicController.placeholderText = @"Multiline Dynamic Text";
self.multilineDynamicController.mdc_adjustsFontForContentSizeCategory = YES;
return multilineDynamicTextField;
}
- (MDCMultilineTextField *)setupCustomMultilineDynamicTextField {
MDCMultilineTextField *multilineCustomDynamicTextField = [[MDCMultilineTextField alloc] init];
multilineCustomDynamicTextField.translatesAutoresizingMaskIntoConstraints = NO;
[self.scrollView addSubview:multilineCustomDynamicTextField];
multilineCustomDynamicTextField.textView.delegate = self;
self.multilineCustomFontDynamicController = [[MDCTextInputControllerOutlinedTextArea alloc]
initWithTextInput:multilineCustomDynamicTextField];
self.multilineCustomFontDynamicController.placeholderText = @"Multiline Custom Font Dynamic Text";
self.multilineCustomFontDynamicController.leadingUnderlineLabelFont =
[UIFont fontWithName:@"AmericanTypewriter" size:12];
self.customFontDynamicController.trailingUnderlineLabelFont = [UIFont fontWithName:@"Chalkduster"
size:12];
self.multilineCustomFontDynamicController.inlinePlaceholderFont = [UIFont fontWithName:@"Zapfino"
size:12];
self.multilineCustomFontDynamicController.textInputFont =
[UIFont fontWithName:@"AmericanTypewriter" size:16];
self.multilineCustomFontDynamicController.mdc_adjustsFontForContentSizeCategory = YES;
return multilineCustomDynamicTextField;
}
#pragma mark - UITextFieldDelegate
- (BOOL)textFieldShouldReturn:(UITextField *)textField {
[textField resignFirstResponder];
return NO;
}
- (BOOL)textField:(UITextField *)textField
shouldChangeCharactersInRange:(NSRange)range
replacementString:(NSString *)string {
NSString *finishedString = [textField.text stringByReplacingCharactersInRange:range
withString:string];
if (textField == (UITextField *)self.systemFontController.textInput) {
if ([finishedString rangeOfCharacterFromSet:[NSCharacterSet decimalDigitCharacterSet]].length &&
![self.systemFontController.errorText isEqualToString:@"Error: You cannot enter numbers"]) {
// The entered text contains numbers and we have not set an error
[self.systemFontController 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.systemFontController.errorText != nil) {
// There should be no error but error text is being shown.
[self.systemFontController 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.customFontController.textInput) {
if ([finishedString rangeOfCharacterFromSet:[[NSCharacterSet letterCharacterSet] invertedSet]]
.length > 0) {
[self.customFontController setErrorText:@"Error: City can only contain letters"
errorAccessibilityValue:nil];
} else {
[self.customFontController setErrorText:nil errorAccessibilityValue:nil];
}
}
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 TextFieldCustomFontExample (CatalogByConvention)
+ (NSDictionary *)catalogMetadata {
return @{
@"breadcrumbs" : @[ @"Text Field", @"Custom Fonts" ],
@"primaryDemo" : @NO,
@"presentable" : @NO,
};
}
@end