| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371 |
- #import "BTUIKFormField.h"
- #import "BTUIKVectorArtView.h"
- #import "BTUIKViewUtil.h"
- #import "BTUIKAppearance.h"
- @interface BTUIKFormField ()<BTUIKTextFieldEditDelegate>
- @property (nonatomic, copy) NSString *previousTextFieldText;
- @property (nonatomic, strong) NSMutableArray *layoutConstraints;
- @end
- @implementation BTUIKFormField
- - (instancetype)initWithFrame:(CGRect)frame {
- self = [super initWithFrame:frame];
- if (self) {
- self.backgroundColor = [BTUIKAppearance sharedInstance].formFieldBackgroundColor;
- self.translatesAutoresizingMaskIntoConstraints = NO;
- self.displayAsValid = YES;
- BTUIKTextField *textField = [BTUIKTextField new];
- textField.editDelegate = self;
- _textField = textField;
- self.textField.translatesAutoresizingMaskIntoConstraints = NO;
- self.textField.borderStyle = UITextBorderStyleNone;
- self.textField.backgroundColor = [UIColor clearColor];
- self.textField.opaque = NO;
- self.textField.adjustsFontSizeToFitWidth = YES;
- self.textField.returnKeyType = UIReturnKeyNext;
- self.textField.keyboardAppearance = [BTUIKAppearance sharedInstance].keyboardAppearance;
- [self.textField addTarget:self action:@selector(fieldContentDidChange) forControlEvents:UIControlEventEditingChanged];
- [self.textField addTarget:self action:@selector(editingDidBegin) forControlEvents:UIControlEventEditingDidBegin];
- [self.textField addTarget:self action:@selector(editingDidEnd) forControlEvents:UIControlEventEditingDidEnd];
- self.textField.delegate = self;
- [self addSubview:self.textField];
-
- self.formLabel = [[UILabel alloc] init];
- [BTUIKAppearance styleLabelBoldPrimary:self.formLabel];
- self.formLabel.translatesAutoresizingMaskIntoConstraints = NO;
- self.formLabel.text = @"";
- self.formLabel.accessibilityElementsHidden = YES;
- [self addSubview:self.formLabel];
- [self.formLabel setContentHuggingPriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisHorizontal];
- [self.formLabel setContentCompressionResistancePriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisHorizontal];
- [self.textField setContentCompressionResistancePriority:UILayoutPriorityDefaultLow forAxis:UILayoutConstraintAxisHorizontal];
-
- [self addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tappedField)]];
-
- [self setContentHuggingPriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisVertical];
- self.opaque = NO;
-
- [self updateConstraints];
- }
- return self;
- }
- - (void)updateConstraints {
- if (self.layoutConstraints != nil) {
- [self removeConstraints:self.layoutConstraints];
- }
- self.layoutConstraints = [NSMutableArray array];
-
- NSMutableDictionary* viewBindings = [@{@"view":self, @"textField":self.textField, @"formLabel": self.formLabel} mutableCopy];
-
- if (self.accessoryView) {
- viewBindings[@"accessoryView"] = self.accessoryView;
- }
-
- NSDictionary* metrics = @{@"PADDING":@15};
-
- BOOL hasFormLabel = (self.formLabel.text.length > 0);
-
- [self.layoutConstraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[textField]|"
- options:0
- metrics:metrics
- views:viewBindings]];
- [self.layoutConstraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[formLabel]|"
- options:0
- metrics:metrics
- views:viewBindings]];
- if (hasFormLabel) {
- [self.layoutConstraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-(PADDING)-[formLabel(<=0@1)]-[textField]"
- options:0
- metrics:metrics
- views:viewBindings]];
- } else {
- [self.layoutConstraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-(PADDING)-[textField]"
- options:0
- metrics:metrics
- views:viewBindings]];
- }
-
- if (self.accessoryView && !self.accessoryView.hidden) {
- [self.layoutConstraints addObjectsFromArray:@[[NSLayoutConstraint constraintWithItem:self.accessoryView
- attribute:NSLayoutAttributeCenterY
- relatedBy:NSLayoutRelationEqual
- toItem:self
- attribute:NSLayoutAttributeCenterY
- multiplier:1.0f
- constant:0.0f]]];
-
- ;
-
- [self.layoutConstraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"H:[textField]-[accessoryView]-(PADDING)-|"
- options:0
- metrics:metrics
- views:viewBindings]];
- } else {
- [self.layoutConstraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"H:[textField]-(PADDING)-|"
- options:0
- metrics:metrics
- views:viewBindings]];
- }
- NSArray *contraintsToAdd = [self.layoutConstraints copy];
- [self addConstraints:contraintsToAdd];
-
- NSTextAlignment newAlignment = hasFormLabel ? [BTUIKViewUtil naturalTextAlignmentInverse] : [BTUIKViewUtil naturalTextAlignment];
- if (newAlignment != self.textField.textAlignment) {
- self.textField.textAlignment = newAlignment;
- }
- [super updateConstraints];
- }
- - (void)textFieldDidBeginEditing:(__unused UITextField *)textField {
- if ([self.delegate respondsToSelector:@selector(formFieldDidBeginEditing:)]) {
- [self.delegate formFieldDidBeginEditing:self];
- }
- }
- - (void)textFieldDidEndEditing:(__unused UITextField *)textField {
- if ([self.delegate respondsToSelector:@selector(formFieldDidEndEditing:)]) {
- [self.delegate formFieldDidEndEditing:self];
- }
- }
- #pragma mark - Drawing
- - (void)drawRect:(CGRect)rect {
- // Draw borders
- [[BTUIKAppearance sharedInstance].lineColor setFill];
- CGContextRef context = UIGraphicsGetCurrentContext();
- if (!self.displayAsValid) {
- CGPathRef path = CGPathCreateWithRect(CGRectMake(rect.origin.x, CGRectGetMaxY(rect) - 0.5f, rect.size.width, 0.5f), NULL);
- CGContextAddPath(context, path);
- CGPathRelease(path);
-
- path = CGPathCreateWithRect(CGRectMake(rect.origin.x, 0, rect.size.width, 0.5f), NULL);
- CGContextAddPath(context, path);
-
- CGContextDrawPath(context, kCGPathFill);
- CGPathRelease(path);
- } else {
- if (self.interFieldBorder || self.bottomBorder) {
- CGFloat horizontalMargin = self.bottomBorder ? 0 : 17.0f;
- CGPathRef path = CGPathCreateWithRect(CGRectMake(rect.origin.x + horizontalMargin, CGRectGetMaxY(rect) - 0.5f, rect.size.width - horizontalMargin, 0.5f), NULL);
- CGContextAddPath(context, path);
- CGContextDrawPath(context, kCGPathFill);
- CGPathRelease(path);
- }
- if (self.topBorder) {
- CGPathRef path = CGPathCreateWithRect(CGRectMake(rect.origin.x, 0, rect.size.width, 0.5f), NULL);
- CGContextAddPath(context, path);
- CGContextDrawPath(context, kCGPathFill);
- CGPathRelease(path);
- }
- }
- }
- - (void)setBottomBorder:(BOOL)bottomBorder {
- _bottomBorder = bottomBorder;
- [self setNeedsDisplay];
- }
- - (void)setInterFieldBorder:(BOOL)interFieldBorder {
- _interFieldBorder = interFieldBorder;
- [self setNeedsDisplay];
- }
- - (void)setTopBorder:(BOOL)topBorder {
- _topBorder = topBorder;
- [self setNeedsDisplay];
- }
- - (void)updateAppearance {
- UIColor *textColor;
- NSString *currentAccessibilityLabel = self.textField.accessibilityLabel;
- if (!self.displayAsValid){
- textColor = [BTUIKAppearance sharedInstance].errorForegroundColor;
- if (currentAccessibilityLabel != nil) {
- self.textField.accessibilityLabel = [self addInvalidAccessibilityToString:currentAccessibilityLabel];
- }
- } else {
- textColor = [BTUIKAppearance sharedInstance].primaryTextColor;
- if (currentAccessibilityLabel != nil) {
- self.textField.accessibilityLabel = [self stripInvalidAccessibilityFromString:currentAccessibilityLabel];
- }
- }
-
- NSMutableAttributedString *mutableText = [[NSMutableAttributedString alloc] initWithAttributedString:self.textField.attributedText];
- [mutableText addAttributes:@{NSForegroundColorAttributeName: textColor,
- NSFontAttributeName:[[BTUIKAppearance sharedInstance].font fontWithSize:UIFont.labelFontSize]}
- range:NSMakeRange(0, mutableText.length)];
-
- UITextRange *currentRange = self.textField.selectedTextRange;
-
- self.textField.attributedText = mutableText;
-
- // Reassign current selection range, since it gets cleared after attributedText assignment
- self.textField.selectedTextRange = currentRange;
- }
- #pragma mark - BTUITextFieldEditDelegate methods
- - (void)textFieldWillDeleteBackward:(__unused BTUIKFormField *)textField {
- // _backspace indicates that the backspace key was typed.
- _backspace = YES;
-
- }
- - (void)textFieldDidDeleteBackward:(__unused BTUIKFormField *)textField originalText:(__unused NSString *)originalText {
- // To be implemented by subclasses
- }
- - (void)textField:(__unused BTUIKFormField *)textField willInsertText:(__unused NSString *)text {
- _backspace = NO;
- }
- - (void)textField:(__unused BTUIKFormField *)textField didInsertText:(__unused NSString *)text {
- // To be implemented by subclasses
- }
- #pragma mark - Custom accessors
- - (void)setText:( __unused NSString *)text {
- BOOL shouldChange = [self.textField.delegate textField:self.textField
- shouldChangeCharactersInRange:NSMakeRange(0, self.textField.text.length)
- replacementString:text];
- if (shouldChange) {
- [self.textField.editDelegate textField:self.textField willInsertText:text];
- self.textField.text = text;
- [self fieldContentDidChange];
- [self.textField.editDelegate textField:self.textField didInsertText:text];
- }
- [self updateAppearance];
- }
- - (NSString *)text {
- return self.textField.text;
- }
- #pragma mark - Delegate methods and handlers
- - (void)resetFormField {
- // To be implemented by subclass
- }
- - (BOOL)becomeFirstResponder {
- return [self.textField becomeFirstResponder];
- }
- - (void)fieldContentDidChange {
- // To be implemented by subclass
- if (self.delegate) {
- [self.delegate formFieldDidChange:self];
- }
- [self updateAppearance];
- }
- - (void)editingDidBegin {
- [self setAccessoryHighlighted:YES];
- }
- - (void)editingDidEnd {
- [self setAccessoryHighlighted:NO];
- }
- - (BOOL)textField:(__unused UITextField *)textField shouldChangeCharactersInRange:(__unused NSRange)range replacementString:(__unused NSString *)newText {
- // To be implemented by subclass
- return YES;
- }
- - (BOOL)textFieldShouldReturn:(__unused UITextField *)textField {
- if ([self.delegate respondsToSelector:@selector(formFieldShouldReturn:)]) {
- return [self.delegate formFieldShouldReturn:self];
- } else {
- return YES;
- }
- }
- - (void)tappedField {
- [self.textField becomeFirstResponder];
- }
- #pragma mark UIKeyInput
- - (void)insertText:(__unused NSString *)text {
- [self.textField insertText:text];
- }
- - (void)deleteBackward {
- [self.textField deleteBackward];
- }
- - (BOOL)hasText {
- return [self.textField hasText];
- }
- #pragma mark Accessibility Helpers
- - (NSString *)stripInvalidAccessibilityFromString:(NSString *)str {
- return [str stringByReplacingOccurrencesOfString:@"Invalid: " withString:@""];
- }
- - (NSString *)addInvalidAccessibilityToString:(NSString *)str {
- return [NSString stringWithFormat:@"Invalid: %@", [self stripInvalidAccessibilityFromString:str]];
- }
- #pragma mark Accessory View Helpers
- - (void)setAccessoryView:(UIView *)accessoryView {
- if (self.accessoryView && self.accessoryView.superview) {
- [self.accessoryView removeFromSuperview];
- _accessoryView = nil;
- }
- _accessoryView = accessoryView;
- self.accessoryView.translatesAutoresizingMaskIntoConstraints = NO;
- [self addSubview:self.accessoryView];
- [self.accessoryView setContentHuggingPriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisHorizontal];
- [self.accessoryView setContentCompressionResistancePriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisHorizontal];
- [self updateConstraints];
- }
- - (void)setAccessoryViewHidden:(BOOL)hidden animated:(__unused BOOL)animated {
- if (self.accessoryView == nil) {
- [self updateConstraints];
- return;
- }
- if (animated) {
- [UIView animateWithDuration:0.1 animations:^{
- self.accessoryView.hidden = hidden;
- [self updateConstraints];
- }];
- } else {
- self.accessoryView.hidden = hidden;
- [self updateConstraints];
- }
- }
- - (void)setAccessoryHighlighted:(BOOL)highlight {
- if (self.accessoryView) {
- if ([self.accessoryView respondsToSelector:@selector(setHighlighted:animated:)]) {
- SEL selector = @selector(setHighlighted:animated:);
- BOOL animated = YES;
- NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[self.accessoryView methodSignatureForSelector:selector]];
- [invocation setSelector:selector];
- [invocation setTarget:self.accessoryView];
- [invocation setArgument:&highlight atIndex:2];
- [invocation setArgument:&animated atIndex:3];
- [invocation invoke];
- }
- }
- }
- @end
|