| // 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 "MDCBaseTextField.h" |
| |
| #import <Foundation/Foundation.h> |
| |
| #import <MDFInternationalization/MDFInternationalization.h> |
| |
| #import "MaterialMath.h" |
| #import "MaterialTextControlsPrivate+BaseStyle.h" |
| #import "MaterialTextControlsPrivate+Shared.h" |
| #import "private/MDCBaseTextFieldLayout.h" |
| |
| @interface MDCBaseTextField () <MDCTextControl> |
| |
| @property(strong, nonatomic) UILabel *label; |
| @property(nonatomic, strong) MDCTextControlAssistiveLabelView *assistiveLabelView; |
| @property(strong, nonatomic) MDCBaseTextFieldLayout *layout; |
| @property(nonatomic, assign) MDCTextControlState textControlState; |
| @property(nonatomic, assign) MDCTextControlLabelState labelState; |
| @property(nonatomic, assign) CGRect labelFrame; |
| @property(nonatomic, assign) NSTimeInterval animationDuration; |
| @property(nonatomic, assign) CGSize mostRecentlyComputedIntrinsicContentSize; |
| |
| /** |
| This property maps MDCTextControlStates as NSNumbers to |
| MDCTextControlColorViewModels. |
| */ |
| @property(nonatomic, strong) |
| NSMutableDictionary<NSNumber *, MDCTextControlColorViewModel *> *colorViewModels; |
| |
| @end |
| |
| @implementation MDCBaseTextField |
| @synthesize containerStyle = _containerStyle; |
| @synthesize assistiveLabelDrawPriority = _assistiveLabelDrawPriority; |
| @synthesize customAssistiveLabelDrawPriority = _customAssistiveLabelDrawPriority; |
| @synthesize preferredContainerHeight = _preferredContainerHeight; |
| |
| #pragma mark Object Lifecycle |
| |
| - (instancetype)initWithFrame:(CGRect)frame { |
| self = [super initWithFrame:frame]; |
| if (self) { |
| [self commonMDCBaseTextFieldInit]; |
| } |
| return self; |
| } |
| |
| - (instancetype)initWithCoder:(NSCoder *)aDecoder { |
| self = [super initWithCoder:aDecoder]; |
| if (self) { |
| [self commonMDCBaseTextFieldInit]; |
| } |
| return self; |
| } |
| |
| - (void)commonMDCBaseTextFieldInit { |
| [self initializeProperties]; |
| [self setUpColorViewModels]; |
| [self setUpLabel]; |
| [self setUpAssistiveLabels]; |
| [self observeContentSizeCategoryNotifications]; |
| } |
| |
| - (void)dealloc { |
| [[NSNotificationCenter defaultCenter] removeObserver:self]; |
| } |
| |
| #pragma mark View Setup |
| |
| - (void)initializeProperties { |
| self.animationDuration = kMDCTextControlDefaultAnimationDuration; |
| self.labelBehavior = MDCTextControlLabelBehaviorFloats; |
| self.labelState = [self determineCurrentLabelState]; |
| self.textControlState = [self determineCurrentTextControlState]; |
| self.containerStyle = [[MDCTextControlStyleBase alloc] init]; |
| self.colorViewModels = [[NSMutableDictionary alloc] init]; |
| } |
| |
| - (void)setUpColorViewModels { |
| self.colorViewModels[@(MDCTextControlStateNormal)] = |
| [[MDCTextControlColorViewModel alloc] initWithState:MDCTextControlStateNormal]; |
| self.colorViewModels[@(MDCTextControlStateEditing)] = |
| [[MDCTextControlColorViewModel alloc] initWithState:MDCTextControlStateEditing]; |
| self.colorViewModels[@(MDCTextControlStateDisabled)] = |
| [[MDCTextControlColorViewModel alloc] initWithState:MDCTextControlStateDisabled]; |
| } |
| |
| - (void)setUpAssistiveLabels { |
| self.assistiveLabelDrawPriority = MDCTextControlAssistiveLabelDrawPriorityTrailing; |
| self.assistiveLabelView = [[MDCTextControlAssistiveLabelView alloc] init]; |
| CGFloat assistiveFontSize = MDCRound([UIFont systemFontSize] * (CGFloat)0.75); |
| UIFont *assistiveFont = [UIFont systemFontOfSize:assistiveFontSize]; |
| self.assistiveLabelView.leadingAssistiveLabel.font = assistiveFont; |
| self.assistiveLabelView.trailingAssistiveLabel.font = assistiveFont; |
| [self addSubview:self.assistiveLabelView]; |
| } |
| |
| - (void)setUpLabel { |
| self.label = [[UILabel alloc] init]; |
| [self addSubview:self.label]; |
| } |
| |
| #pragma mark UIView Overrides |
| |
| - (void)layoutSubviews { |
| [self preLayoutSubviews]; |
| [super layoutSubviews]; |
| [self postLayoutSubviews]; |
| } |
| |
| // UITextField's sizeToFit calls this method and then also calls setNeedsLayout. |
| // When the system calls this method the size parameter is the view's current size. |
| - (CGSize)sizeThatFits:(CGSize)size { |
| return [self preferredSizeWithWidth:size.width]; |
| } |
| |
| - (CGSize)intrinsicContentSize { |
| self.mostRecentlyComputedIntrinsicContentSize = |
| [self preferredSizeWithWidth:CGRectGetWidth(self.bounds)]; |
| return self.mostRecentlyComputedIntrinsicContentSize; |
| } |
| |
| - (void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection { |
| [super traitCollectionDidChange:previousTraitCollection]; |
| [self setNeedsLayout]; |
| } |
| |
| - (void)setSemanticContentAttribute:(UISemanticContentAttribute)semanticContentAttribute { |
| [super setSemanticContentAttribute:semanticContentAttribute]; |
| [self setNeedsLayout]; |
| } |
| |
| #pragma mark Layout |
| |
| /** |
| UITextField layout methods such as @c -textRectForBounds: and @c -editingRectForBounds: are called |
| within @c -layoutSubviews. The exact values of the CGRects MDCBaseTextField returns from these |
| methods depend on many factors, and are calculated alongside all the other frames of |
| MDCBaseTextField's subviews. To ensure that these values are known before UITextField's layout |
| methods expect them, they are determined by this method, which is called before the superclass's @c |
| -layoutSubviews in the layout cycle. |
| */ |
| - (void)preLayoutSubviews { |
| if ([self widthHasChangedSinceIntrinsicContentSizeWasLastComputed]) { |
| [self invalidateIntrinsicContentSize]; |
| } |
| self.textControlState = [self determineCurrentTextControlState]; |
| self.labelState = [self determineCurrentLabelState]; |
| MDCTextControlColorViewModel *colorViewModel = |
| [self textControlColorViewModelForState:self.textControlState]; |
| [self applyColorViewModel:colorViewModel withLabelState:self.labelState]; |
| CGSize fittingSize = CGSizeMake(CGRectGetWidth(self.bounds), CGFLOAT_MAX); |
| self.layout = [self calculateLayoutWithTextFieldSize:fittingSize]; |
| self.labelFrame = [self.layout labelFrameWithLabelState:self.labelState]; |
| } |
| |
| - (void)postLayoutSubviews { |
| self.label.hidden = self.labelState == MDCTextControlLabelStateNone; |
| self.assistiveLabelView.frame = self.layout.assistiveLabelViewFrame; |
| self.assistiveLabelView.layout = self.layout.assistiveLabelViewLayout; |
| [self.assistiveLabelView setNeedsLayout]; |
| self.leftView.hidden = self.layout.leftViewHidden; |
| self.rightView.hidden = self.layout.rightViewHidden; |
| [self animateLabel]; |
| [self.containerStyle applyStyleToTextControl:self animationDuration:self.animationDuration]; |
| if ([self calculatedHeightHasChangedSinceIntrinsicContentSizeWasLastComputed]) { |
| [self invalidateIntrinsicContentSize]; |
| } |
| } |
| |
| - (CGRect)textRectFromLayout:(MDCBaseTextFieldLayout *)layout |
| labelState:(MDCTextControlLabelState)labelState { |
| CGRect textRect = layout.textRectNormal; |
| if (labelState == MDCTextControlLabelStateFloating) { |
| textRect = layout.textRectFloating; |
| } |
| return textRect; |
| } |
| |
| /** |
| To understand this method one must understand that the CGRect UITextField returns from @c |
| -textRectForBounds: does not actually represent the CGRect of visible text in UITextField. It |
| represents the CGRect of an internal "field editing" class, which has a height that is |
| significantly taller than the text (@c font.lineHeight) itself. Providing a height in @c |
| -textRectForBounds: that differs from the height determined by the superclass results in a text |
| field with poor text rendering, sometimes to the point of the text not being visible. By taking the |
| desired CGRect of the visible text from the layout object, giving it the height preferred by the |
| superclass's implementation of @c -textRectForBounds:, and then ensuring that this new CGRect has |
| the same midY as the original CGRect, we are able to take control of the text's positioning. |
| */ |
| - (CGRect)adjustTextAreaFrame:(CGRect)textRect |
| withParentClassTextAreaFrame:(CGRect)parentClassTextAreaFrame { |
| CGFloat systemDefinedHeight = CGRectGetHeight(parentClassTextAreaFrame); |
| CGFloat minY = CGRectGetMidY(textRect) - (systemDefinedHeight * (CGFloat)0.5); |
| return CGRectMake(CGRectGetMinX(textRect), minY, CGRectGetWidth(textRect), systemDefinedHeight); |
| } |
| |
| - (MDCBaseTextFieldLayout *)calculateLayoutWithTextFieldSize:(CGSize)textFieldSize { |
| CGFloat clampedCustomAssistiveLabelDrawPriority = |
| [self clampedCustomAssistiveLabelDrawPriority:self.customAssistiveLabelDrawPriority]; |
| CGFloat clearButtonSideLength = [self clearButtonSideLengthWithTextFieldSize:textFieldSize]; |
| id<MDCTextControlVerticalPositioningReference> positioningReference = |
| [self createPositioningReference]; |
| return [[MDCBaseTextFieldLayout alloc] |
| initWithTextFieldSize:textFieldSize |
| positioningReference:positioningReference |
| text:self.text |
| font:self.normalFont |
| floatingFont:self.floatingFont |
| label:self.label |
| leftView:self.leftView |
| leftViewMode:self.leftViewMode |
| rightView:self.rightView |
| rightViewMode:self.rightViewMode |
| clearButtonSideLength:clearButtonSideLength |
| clearButtonMode:self.clearButtonMode |
| leadingAssistiveLabel:self.assistiveLabelView.leadingAssistiveLabel |
| trailingAssistiveLabel:self.assistiveLabelView.trailingAssistiveLabel |
| assistiveLabelDrawPriority:self.assistiveLabelDrawPriority |
| customAssistiveLabelDrawPriority:clampedCustomAssistiveLabelDrawPriority |
| isRTL:self.shouldLayoutForRTL |
| isEditing:self.isEditing]; |
| } |
| |
| - (id<MDCTextControlVerticalPositioningReference>)createPositioningReference { |
| return [self.containerStyle |
| positioningReferenceWithFloatingFontLineHeight:self.floatingFont.lineHeight |
| normalFontLineHeight:self.normalFont.lineHeight |
| textRowHeight:self.normalFont.lineHeight |
| numberOfTextRows:self.numberOfVisibleTextRows |
| density:0 |
| preferredContainerHeight:self.preferredContainerHeight]; |
| } |
| |
| - (CGFloat)clampedCustomAssistiveLabelDrawPriority:(CGFloat)customPriority { |
| CGFloat value = customPriority; |
| if (value < 0) { |
| value = 0; |
| } else if (value > 1) { |
| value = 1; |
| } |
| return value; |
| } |
| |
| - (CGFloat)clearButtonSideLengthWithTextFieldSize:(CGSize)textFieldSize { |
| CGRect bounds = CGRectMake(0, 0, textFieldSize.width, textFieldSize.height); |
| CGRect systemPlaceholderRect = [super clearButtonRectForBounds:bounds]; |
| return systemPlaceholderRect.size.height; |
| } |
| |
| - (CGSize)preferredSizeWithWidth:(CGFloat)width { |
| CGSize fittingSize = CGSizeMake(width, CGFLOAT_MAX); |
| MDCBaseTextFieldLayout *layout = [self calculateLayoutWithTextFieldSize:fittingSize]; |
| return CGSizeMake(width, layout.calculatedHeight); |
| } |
| |
| - (BOOL)widthHasChangedSinceIntrinsicContentSizeWasLastComputed { |
| return CGRectGetWidth(self.bounds) != self.mostRecentlyComputedIntrinsicContentSize.width; |
| } |
| |
| - (BOOL)calculatedHeightHasChangedSinceIntrinsicContentSizeWasLastComputed { |
| return self.layout.calculatedHeight != self.mostRecentlyComputedIntrinsicContentSize.height; |
| } |
| |
| - (BOOL)shouldLayoutForRTL { |
| if (self.semanticContentAttribute == UISemanticContentAttributeForceRightToLeft) { |
| return YES; |
| } else if (self.semanticContentAttribute == UISemanticContentAttributeForceLeftToRight) { |
| return NO; |
| } else { |
| return self.mdf_effectiveUserInterfaceLayoutDirection == |
| UIUserInterfaceLayoutDirectionRightToLeft; |
| } |
| } |
| |
| #pragma mark UITextField Accessor Overrides |
| |
| - (void)setEnabled:(BOOL)enabled { |
| [super setEnabled:enabled]; |
| |
| [self setNeedsLayout]; |
| } |
| |
| - (void)setLeftViewMode:(UITextFieldViewMode)leftViewMode { |
| [self mdc_setLeftViewMode:leftViewMode]; |
| } |
| |
| - (void)setRightViewMode:(UITextFieldViewMode)rightViewMode { |
| [self mdc_setRightViewMode:rightViewMode]; |
| } |
| |
| - (void)setLeftView:(UIView *)leftView { |
| [self mdc_setLeftView:leftView]; |
| } |
| |
| - (void)setRightView:(UIView *)rightView { |
| [self mdc_setRightView:rightView]; |
| } |
| |
| #pragma mark Custom Accessors |
| |
| - (UILabel *)leadingAssistiveLabel { |
| return self.assistiveLabelView.leadingAssistiveLabel; |
| } |
| |
| - (UILabel *)trailingAssistiveLabel { |
| return self.assistiveLabelView.trailingAssistiveLabel; |
| } |
| |
| - (void)setTrailingView:(UIView *)trailingView { |
| if ([self shouldLayoutForRTL]) { |
| [self mdc_setLeftView:trailingView]; |
| } else { |
| [self mdc_setRightView:trailingView]; |
| } |
| } |
| |
| - (UIView *)trailingView { |
| if ([self shouldLayoutForRTL]) { |
| return self.leftView; |
| } else { |
| return self.rightView; |
| } |
| } |
| |
| - (void)setLeadingView:(UIView *)leadingView { |
| if ([self shouldLayoutForRTL]) { |
| [self mdc_setRightView:leadingView]; |
| } else { |
| [self mdc_setLeftView:leadingView]; |
| } |
| } |
| |
| - (UIView *)leadingView { |
| if ([self shouldLayoutForRTL]) { |
| return self.rightView; |
| } else { |
| return self.leftView; |
| } |
| } |
| |
| - (void)mdc_setLeftView:(UIView *)leftView { |
| [super setLeftView:leftView]; |
| } |
| |
| - (void)mdc_setRightView:(UIView *)rightView { |
| [super setRightView:rightView]; |
| } |
| |
| - (void)setTrailingViewMode:(UITextFieldViewMode)trailingViewMode { |
| if ([self shouldLayoutForRTL]) { |
| [self mdc_setLeftViewMode:trailingViewMode]; |
| } else { |
| [self mdc_setRightViewMode:trailingViewMode]; |
| } |
| } |
| |
| - (UITextFieldViewMode)trailingViewMode { |
| if ([self shouldLayoutForRTL]) { |
| return self.leftViewMode; |
| } else { |
| return self.rightViewMode; |
| } |
| } |
| |
| - (void)setLeadingViewMode:(UITextFieldViewMode)leadingViewMode { |
| if ([self shouldLayoutForRTL]) { |
| [self mdc_setRightViewMode:leadingViewMode]; |
| } else { |
| [self mdc_setLeftViewMode:leadingViewMode]; |
| } |
| } |
| |
| - (UITextFieldViewMode)leadingViewMode { |
| if ([self shouldLayoutForRTL]) { |
| return self.rightViewMode; |
| } else { |
| return self.leftViewMode; |
| } |
| } |
| |
| - (void)mdc_setLeftViewMode:(UITextFieldViewMode)leftViewMode { |
| [super setLeftViewMode:leftViewMode]; |
| } |
| |
| - (void)mdc_setRightViewMode:(UITextFieldViewMode)rightViewMode { |
| [super setRightViewMode:rightViewMode]; |
| } |
| |
| #pragma mark MDCTextControl accessors |
| |
| - (void)setLabelBehavior:(MDCTextControlLabelBehavior)labelBehavior { |
| if (_labelBehavior == labelBehavior) { |
| return; |
| } |
| _labelBehavior = labelBehavior; |
| [self setNeedsLayout]; |
| } |
| |
| - (void)setContainerStyle:(id<MDCTextControlStyle>)containerStyle { |
| id<MDCTextControlStyle> oldStyle = _containerStyle; |
| if (oldStyle) { |
| [oldStyle removeStyleFrom:self]; |
| } |
| _containerStyle = containerStyle; |
| [_containerStyle applyStyleToTextControl:self animationDuration:0]; |
| } |
| |
| - (CGRect)containerFrame { |
| return CGRectMake(0, 0, CGRectGetWidth(self.frame), self.layout.containerHeight); |
| } |
| |
| - (CGFloat)numberOfVisibleTextRows { |
| return 1; |
| } |
| |
| #pragma mark UITextField Layout Overrides |
| |
| - (CGRect)textRectForBounds:(CGRect)bounds { |
| CGRect textRect = [self textRectFromLayout:self.layout labelState:self.labelState]; |
| return [self adjustTextAreaFrame:textRect |
| withParentClassTextAreaFrame:[super textRectForBounds:bounds]]; |
| } |
| |
| - (CGRect)editingRectForBounds:(CGRect)bounds { |
| CGRect textRect = [self textRectFromLayout:self.layout labelState:self.labelState]; |
| return [self adjustTextAreaFrame:textRect |
| withParentClassTextAreaFrame:[super editingRectForBounds:bounds]]; |
| } |
| |
| // The implementations for this method and the method below deserve some context! Unfortunately, |
| // Apple's RTL behavior with these methods is very unintuitive. Imagine you're in an RTL locale and |
| // you set @c leftView on a standard UITextField. Even though the property that you set is called @c |
| // leftView, the method @c -rightViewRectForBounds: will be called. They are treating @c leftView as |
| // @c rightView, even though @c rightView is nil. The RTL-aware wrappers around these APIs that |
| // MDCBaseTextField introduce handle this situation more accurately. |
| - (CGRect)leftViewRectForBounds:(CGRect)bounds { |
| if ([self shouldLayoutForRTL]) { |
| return self.layout.rightViewFrame; |
| } else { |
| return self.layout.leftViewFrame; |
| } |
| } |
| |
| - (CGRect)rightViewRectForBounds:(CGRect)bounds { |
| if ([self shouldLayoutForRTL]) { |
| return self.layout.leftViewFrame; |
| } else { |
| return self.layout.rightViewFrame; |
| } |
| } |
| |
| - (CGRect)borderRectForBounds:(CGRect)bounds { |
| if (!self.containerStyle) { |
| return [super borderRectForBounds:bounds]; |
| } |
| return CGRectZero; |
| } |
| |
| - (CGRect)clearButtonRectForBounds:(CGRect)bounds { |
| if (self.labelState == MDCTextControlLabelStateFloating) { |
| return self.layout.clearButtonFrameFloating; |
| } |
| return self.layout.clearButtonFrameNormal; |
| } |
| |
| - (CGRect)placeholderRectForBounds:(CGRect)bounds { |
| if (self.shouldPlaceholderBeVisible) { |
| return [super placeholderRectForBounds:bounds]; |
| } |
| return CGRectZero; |
| } |
| |
| #pragma mark UITextField Drawing Overrides |
| |
| - (void)drawPlaceholderInRect:(CGRect)rect { |
| if (self.shouldPlaceholderBeVisible) { |
| [super drawPlaceholderInRect:rect]; |
| } |
| } |
| |
| #pragma mark Fonts |
| |
| - (UIFont *)normalFont { |
| return self.font ?: [self uiTextFieldDefaultFont]; |
| } |
| |
| - (UIFont *)floatingFont { |
| return [self.containerStyle floatingFontWithNormalFont:self.normalFont]; |
| } |
| |
| - (UIFont *)uiTextFieldDefaultFont { |
| // This value comes from https://developer.apple.com/documentation/uikit/uitextfield/1619604-font |
| return [UIFont preferredFontForTextStyle:UIFontTextStyleBody]; |
| } |
| |
| #pragma mark Dynamic Type |
| |
| - (void)setAdjustsFontForContentSizeCategory:(BOOL)adjustsFontForContentSizeCategory { |
| if (@available(iOS 10.0, *)) { |
| [super setAdjustsFontForContentSizeCategory:adjustsFontForContentSizeCategory]; |
| self.leadingAssistiveLabel.adjustsFontForContentSizeCategory = |
| adjustsFontForContentSizeCategory; |
| self.trailingAssistiveLabel.adjustsFontForContentSizeCategory = |
| adjustsFontForContentSizeCategory; |
| } |
| } |
| |
| - (void)observeContentSizeCategoryNotifications { |
| [[NSNotificationCenter defaultCenter] addObserver:self |
| selector:@selector(contentSizeCategoryDidChange:) |
| name:UIContentSizeCategoryDidChangeNotification |
| object:nil]; |
| } |
| |
| - (void)contentSizeCategoryDidChange:(NSNotification *)notification { |
| [self setNeedsLayout]; |
| } |
| |
| #pragma mark MDCTextControlState |
| |
| - (MDCTextControlState)determineCurrentTextControlState { |
| return [self textControlStateWithIsEnabled:self.isEnabled isEditing:self.isEditing]; |
| } |
| |
| - (MDCTextControlState)textControlStateWithIsEnabled:(BOOL)isEnabled isEditing:(BOOL)isEditing { |
| if (isEnabled) { |
| if (isEditing) { |
| return MDCTextControlStateEditing; |
| } else { |
| return MDCTextControlStateNormal; |
| } |
| } else { |
| return MDCTextControlStateDisabled; |
| } |
| } |
| |
| #pragma mark Placeholder |
| |
| - (BOOL)shouldPlaceholderBeVisible { |
| return [self shouldPlaceholderBeVisibleWithPlaceholder:self.placeholder |
| text:self.text |
| labelState:self.labelState]; |
| } |
| |
| - (BOOL)shouldPlaceholderBeVisibleWithPlaceholder:(NSString *)placeholder |
| text:(NSString *)text |
| labelState:(MDCTextControlLabelState)labelState { |
| BOOL hasPlaceholder = placeholder.length > 0; |
| BOOL hasText = text.length > 0; |
| |
| if (hasPlaceholder) { |
| if (hasText) { |
| return NO; |
| } else { |
| if (labelState == MDCTextControlLabelStateNormal) { |
| return NO; |
| } else { |
| return YES; |
| } |
| } |
| } else { |
| return NO; |
| } |
| } |
| |
| #pragma mark Label |
| |
| - (void)animateLabel { |
| __weak MDCBaseTextField *weakSelf = self; |
| [MDCTextControlLabelAnimation animateLabel:self.label |
| state:self.labelState |
| normalLabelFrame:self.layout.labelFrameNormal |
| floatingLabelFrame:self.layout.labelFrameFloating |
| normalFont:self.normalFont |
| floatingFont:self.floatingFont |
| animationDuration:self.animationDuration |
| completion:^(BOOL finished) { |
| if (finished) { |
| // Ensure that the label position is correct in case of |
| // competing animations. |
| weakSelf.label.frame = weakSelf.labelFrame; |
| } |
| }]; |
| } |
| |
| - (BOOL)canLabelFloat { |
| return self.labelBehavior == MDCTextControlLabelBehaviorFloats; |
| } |
| |
| - (MDCTextControlLabelState)determineCurrentLabelState { |
| return [self labelStateWithLabel:self.label |
| text:self.text |
| canLabelFloat:self.canLabelFloat |
| isEditing:self.isEditing]; |
| } |
| |
| - (MDCTextControlLabelState)labelStateWithLabel:(UILabel *)label |
| text:(NSString *)text |
| canLabelFloat:(BOOL)canLabelFloat |
| isEditing:(BOOL)isEditing { |
| BOOL hasFloatingLabelText = label.text.length > 0; |
| BOOL hasText = text.length > 0; |
| if (hasFloatingLabelText) { |
| if (canLabelFloat) { |
| if (isEditing) { |
| return MDCTextControlLabelStateFloating; |
| } else { |
| if (hasText) { |
| return MDCTextControlLabelStateFloating; |
| } else { |
| return MDCTextControlLabelStateNormal; |
| } |
| } |
| } else { |
| if (hasText) { |
| return MDCTextControlLabelStateNone; |
| } else { |
| return MDCTextControlLabelStateNormal; |
| } |
| } |
| } else { |
| return MDCTextControlLabelStateNone; |
| } |
| } |
| |
| #pragma mark Coloring |
| |
| - (void)applyColorViewModel:(MDCTextControlColorViewModel *)colorViewModel |
| withLabelState:(MDCTextControlLabelState)labelState { |
| UIColor *labelColor = [UIColor clearColor]; |
| if (labelState == MDCTextControlLabelStateNormal) { |
| labelColor = colorViewModel.normalLabelColor; |
| } else if (labelState == MDCTextControlLabelStateFloating) { |
| labelColor = colorViewModel.floatingLabelColor; |
| } |
| self.textColor = colorViewModel.textColor; |
| self.leadingAssistiveLabel.textColor = colorViewModel.leadingAssistiveLabelColor; |
| self.trailingAssistiveLabel.textColor = colorViewModel.trailingAssistiveLabelColor; |
| self.label.textColor = labelColor; |
| } |
| |
| - (void)setTextControlColorViewModel:(MDCTextControlColorViewModel *)colorViewModel |
| forState:(MDCTextControlState)textControlState { |
| if (colorViewModel) { |
| self.colorViewModels[@(textControlState)] = colorViewModel; |
| } |
| } |
| |
| - (MDCTextControlColorViewModel *)textControlColorViewModelForState: |
| (MDCTextControlState)textControlState { |
| MDCTextControlColorViewModel *colorViewModel = self.colorViewModels[@(textControlState)]; |
| if (!colorViewModel) { |
| colorViewModel = [[MDCTextControlColorViewModel alloc] initWithState:textControlState]; |
| } |
| return colorViewModel; |
| } |
| |
| #pragma mark Accessibility Overrides |
| |
| - (NSString *)accessibilityLabel { |
| NSString *superAccessibilityLabel = [super accessibilityLabel]; |
| if (superAccessibilityLabel.length > 0) { |
| return superAccessibilityLabel; |
| } |
| |
| NSMutableArray *accessibilityLabelComponents = [NSMutableArray new]; |
| if (self.label.text.length > 0) { |
| [accessibilityLabelComponents addObject:self.label.text]; |
| } |
| if (self.leadingAssistiveLabel.text.length > 0) { |
| [accessibilityLabelComponents addObject:self.leadingAssistiveLabel.text]; |
| } |
| if (self.trailingAssistiveLabel.text.length > 0) { |
| [accessibilityLabelComponents addObject:self.trailingAssistiveLabel.text]; |
| } |
| |
| if (accessibilityLabelComponents.count > 0) { |
| return [accessibilityLabelComponents componentsJoinedByString:@", "]; |
| } |
| |
| return nil; |
| } |
| |
| #pragma mark Color Accessors |
| |
| - (void)setNormalLabelColor:(nonnull UIColor *)labelColor forState:(MDCTextControlState)state { |
| MDCTextControlColorViewModel *colorViewModel = [self textControlColorViewModelForState:state]; |
| colorViewModel.normalLabelColor = labelColor; |
| [self setNeedsLayout]; |
| } |
| |
| - (UIColor *)normalLabelColorForState:(MDCTextControlState)state { |
| MDCTextControlColorViewModel *colorViewModel = [self textControlColorViewModelForState:state]; |
| return colorViewModel.normalLabelColor; |
| } |
| |
| - (void)setFloatingLabelColor:(nonnull UIColor *)labelColor forState:(MDCTextControlState)state { |
| MDCTextControlColorViewModel *colorViewModel = [self textControlColorViewModelForState:state]; |
| colorViewModel.floatingLabelColor = labelColor; |
| [self setNeedsLayout]; |
| } |
| |
| - (UIColor *)floatingLabelColorForState:(MDCTextControlState)state { |
| MDCTextControlColorViewModel *colorViewModel = [self textControlColorViewModelForState:state]; |
| return colorViewModel.floatingLabelColor; |
| } |
| |
| - (void)setTextColor:(nonnull UIColor *)labelColor forState:(MDCTextControlState)state { |
| MDCTextControlColorViewModel *colorViewModel = [self textControlColorViewModelForState:state]; |
| colorViewModel.textColor = labelColor; |
| [self setNeedsLayout]; |
| } |
| |
| - (UIColor *)textColorForState:(MDCTextControlState)state { |
| MDCTextControlColorViewModel *colorViewModel = [self textControlColorViewModelForState:state]; |
| return colorViewModel.textColor; |
| } |
| |
| - (void)setLeadingAssistiveLabelColor:(nonnull UIColor *)leadingAssistiveLabelColor |
| forState:(MDCTextControlState)state { |
| MDCTextControlColorViewModel *colorViewModel = [self textControlColorViewModelForState:state]; |
| colorViewModel.leadingAssistiveLabelColor = leadingAssistiveLabelColor; |
| [self setNeedsLayout]; |
| } |
| |
| - (nonnull UIColor *)leadingAssistiveLabelColorForState:(MDCTextControlState)state { |
| MDCTextControlColorViewModel *colorViewModel = [self textControlColorViewModelForState:state]; |
| return colorViewModel.leadingAssistiveLabelColor; |
| } |
| |
| - (void)setTrailingAssistiveLabelColor:(nonnull UIColor *)trailingAssistiveLabelColor |
| forState:(MDCTextControlState)state { |
| MDCTextControlColorViewModel *colorViewModel = [self textControlColorViewModelForState:state]; |
| colorViewModel.trailingAssistiveLabelColor = trailingAssistiveLabelColor; |
| [self setNeedsLayout]; |
| } |
| |
| - (nonnull UIColor *)trailingAssistiveLabelColorForState:(MDCTextControlState)state { |
| MDCTextControlColorViewModel *colorViewModel = [self textControlColorViewModelForState:state]; |
| return colorViewModel.trailingAssistiveLabelColor; |
| } |
| |
| @end |