blob: 127bb4e7b1c2365e52cd6491ba471f2eabeb5c3c [file] [log] [blame] [edit]
// 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 "MaterialTextControls+Enums.h"
#import "MDCTextControlAssistiveLabelDrawPriority.h"
#import <MDFInternationalization/MDFInternationalization.h>
#import "MDCBaseTextFieldDelegate.h"
#import "MaterialTextControlsPrivate+BaseStyle.h"
#import "MaterialTextControlsPrivate+Shared.h"
#import "MaterialTextControlsPrivate+TextFields.h"
static char *const kKVOContextMDCBaseTextField = "kKVOContextMDCBaseTextField";
@interface MDCBaseTextField () <MDCTextControlTextField>
@property(strong, nonatomic) UILabel *label;
@property(nonatomic, strong) MDCTextControlAssistiveLabelView *assistiveLabelView;
@property(strong, nonatomic) MDCBaseTextFieldLayout *layout;
@property(nonatomic, assign) MDCTextControlState textControlState;
@property(nonatomic, assign) MDCTextControlLabelPosition labelPosition;
@property(nonatomic, assign) CGRect labelFrame;
@property(nonatomic, assign) NSTimeInterval animationDuration;
@property(nonatomic, assign) CGFloat lastRecordedWidth;
@property(nonatomic, assign) CGFloat lastCalculatedHeight;
/**
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;
#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];
[self observeAssistiveLabelKeyPaths];
}
- (void)dealloc {
[self removeObservers];
}
#pragma mark View Setup
- (void)initializeProperties {
self.animationDuration = kMDCTextControlDefaultAnimationDuration;
self.labelBehavior = MDCTextControlLabelBehaviorFloats;
self.labelPosition = [self determineCurrentLabelPosition];
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 = round([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 UIResponder Overrides
- (BOOL)canPerformAction:(SEL)action withSender:(id)sender {
BOOL canPerformAction = [super canPerformAction:action withSender:sender];
if ([self.baseTextFieldDelegate
respondsToSelector:@selector(baseTextField:
shouldPerformAction:withSender:canPerformAction:)]) {
return [self.baseTextFieldDelegate baseTextField:self
shouldPerformAction:action
withSender:sender
canPerformAction:canPerformAction];
}
return canPerformAction;
}
#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 {
return [self preferredSizeWithWidth:CGRectGetWidth(self.bounds)];
}
- (void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection {
[super traitCollectionDidChange:previousTraitCollection];
[self setNeedsLayout];
}
- (void)setSemanticContentAttribute:(UISemanticContentAttribute)semanticContentAttribute {
[super setSemanticContentAttribute:semanticContentAttribute];
[self setNeedsLayout];
}
- (void)setBounds:(CGRect)bounds {
[super setBounds:bounds];
// I wouldn't think something like the following is necessary, but in light of b/173646979 I think
// it is.
CGFloat newWidth = CGRectGetWidth(bounds);
BOOL widthHasChanged = newWidth != self.lastRecordedWidth;
if (widthHasChanged) {
[self setNeedsLayout];
}
self.lastRecordedWidth = CGRectGetWidth(bounds);
}
#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 {
self.textControlState = [self determineCurrentTextControlState];
self.labelPosition = [self determineCurrentLabelPosition];
MDCTextControlColorViewModel *colorViewModel =
[self textControlColorViewModelForState:self.textControlState];
[self applyColorViewModel:colorViewModel withLabelPosition:self.labelPosition];
CGSize fittingSize = CGSizeMake(CGRectGetWidth(self.bounds), CGFLOAT_MAX);
self.layout = [self calculateLayoutWithTextFieldSize:fittingSize];
self.labelFrame = [self.layout labelFrameWithLabelPosition:self.labelPosition];
}
- (void)postLayoutSubviews {
self.label.hidden = self.labelPosition == MDCTextControlLabelPositionNone;
self.assistiveLabelView.frame = self.layout.assistiveLabelViewFrame;
self.assistiveLabelView.layout = self.layout.assistiveLabelViewLayout;
[self.assistiveLabelView setNeedsLayout];
[self animateLabel];
[self updateSideViews];
[self.containerStyle applyStyleToTextControl:self animationDuration:self.animationDuration];
[self updateCalculatedHeight];
}
- (void)updateCalculatedHeight {
if (self.layout.calculatedHeight != self.lastCalculatedHeight) {
[self invalidateIntrinsicContentSize];
if ([self.baseTextFieldDelegate respondsToSelector:@selector(baseTextField:
didUpdateIntrinsicHeight:)]) {
[self.baseTextFieldDelegate baseTextField:self
didUpdateIntrinsicHeight:self.layout.calculatedHeight];
}
}
self.lastCalculatedHeight = self.layout.calculatedHeight;
}
- (void)updateSideViews {
if (self.layout.displaysLeadingView) {
if (self.leadingView) {
if (self.leadingView.superview != self) {
[self addSubview:self.leadingView];
}
self.leadingView.frame = self.layout.leadingViewFrame;
}
} else {
[self.leadingView removeFromSuperview];
}
if (self.layout.displaysTrailingView) {
if (self.trailingView) {
if (self.trailingView.superview != self) {
[self addSubview:self.trailingView];
}
self.trailingView.frame = self.layout.trailingViewFrame;
}
} else {
[self.trailingView removeFromSuperview];
}
}
- (CGRect)textRectFromLayout:(MDCBaseTextFieldLayout *)layout
labelPosition:(MDCTextControlLabelPosition)labelPosition {
CGRect textRect = layout.textRectNormal;
if (labelPosition == MDCTextControlLabelPositionFloating) {
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> verticalPositioningReference =
[self createVerticalPositioningReference];
id<MDCTextControlHorizontalPositioning> horizontalPositioningReference =
[self createHorizontalPositioningReference];
return [[MDCBaseTextFieldLayout alloc]
initWithTextFieldSize:textFieldSize
positioningReference:verticalPositioningReference
horizontalPositioningReference:horizontalPositioningReference
text:self.text
font:self.normalFont
floatingFont:self.floatingFont
label:self.label
labelPosition:self.labelPosition
labelBehavior:self.labelBehavior
sideViewAlignment:self.sideViewAlignment
leadingView:self.leadingView
leadingViewMode:self.leadingViewMode
trailingView:self.trailingView
trailingViewMode:self.trailingViewMode
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<MDCTextControlHorizontalPositioning>)createHorizontalPositioningReference {
id<MDCTextControlHorizontalPositioning> horizontalPositioningReference =
self.containerStyle.horizontalPositioningReference;
if (self.leadingEdgePaddingOverride) {
horizontalPositioningReference.leadingEdgePadding =
(CGFloat)[self.leadingEdgePaddingOverride doubleValue];
}
if (self.trailingEdgePaddingOverride) {
horizontalPositioningReference.trailingEdgePadding =
(CGFloat)[self.trailingEdgePaddingOverride doubleValue];
}
if (self.horizontalInterItemSpacingOverride) {
horizontalPositioningReference.horizontalInterItemSpacing =
(CGFloat)[self.horizontalInterItemSpacingOverride doubleValue];
}
return horizontalPositioningReference;
}
- (id<MDCTextControlVerticalPositioningReference>)createVerticalPositioningReference {
return [self.containerStyle
positioningReferenceWithFloatingFontLineHeight:self.floatingFont.lineHeight
normalFontLineHeight:self.normalFont.lineHeight
textRowHeight:self.normalFont.lineHeight
numberOfTextRows:self.numberOfLinesOfVisibleText
density:self.verticalDensity
preferredContainerHeight:self.preferredContainerHeight
isMultilineTextControl:NO];
}
- (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)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.leadingViewMode = leftViewMode;
}
- (UITextFieldViewMode)leftViewMode {
return self.leadingViewMode;
}
- (void)setRightViewMode:(UITextFieldViewMode)rightViewMode {
self.trailingViewMode = rightViewMode;
}
- (UITextFieldViewMode)rightViewMode {
return self.trailingViewMode;
}
/**
RTL behavior appears to have been added to UITextField some time after the leftView and rightView
properties. As a result, things don't always behave how one might expect. The values returned from
-leftViewRectForBounds: and -rightViewRectForBounds: can sometimes be ignored in RTL locales, or
when semanticContentAttribute is set. To get around this strange behavior from the superclass we
manage our own leading and trailing views. The setters for the original UITextField leftView and
rightView properties get forwarded to the setters for leadingView and trailingView, respectively.
In RTL, -setRightView: will call -setTrailingView:, which will result in a view that gets rendered
on the left edge of the view, which is consistent with how UITextField behaves.
*/
- (void)setRightView:(UIView *)rightView {
self.trailingView = rightView;
}
- (UIView *)rightView {
return self.trailingView;
}
- (void)setLeftView:(UIView *)leftView {
self.leadingView = leftView;
}
- (UIView *)leftView {
return self.leadingView;
}
#pragma mark Custom Accessors
- (void)setLeadingEdgePaddingOverride:(NSNumber *)leadingEdgePaddingOverride {
_leadingEdgePaddingOverride = leadingEdgePaddingOverride;
[self setNeedsLayout];
}
- (void)setTrailingEdgePaddingOverride:(NSNumber *)trailingEdgePaddingOverride {
_trailingEdgePaddingOverride = trailingEdgePaddingOverride;
[self setNeedsLayout];
}
- (UILabel *)leadingAssistiveLabel {
return self.assistiveLabelView.leadingAssistiveLabel;
}
- (UILabel *)trailingAssistiveLabel {
return self.assistiveLabelView.trailingAssistiveLabel;
}
- (void)setTrailingView:(UIView *)trailingView {
[_trailingView removeFromSuperview];
_trailingView = trailingView;
[self setNeedsLayout];
}
- (void)setLeadingView:(UIView *)leadingView {
[_leadingView removeFromSuperview];
_leadingView = leadingView;
[self setNeedsLayout];
}
- (void)setTrailingViewMode:(UITextFieldViewMode)trailingViewMode {
_trailingViewMode = trailingViewMode;
[self setNeedsLayout];
}
- (void)setLeadingViewMode:(UITextFieldViewMode)leadingViewMode {
_leadingViewMode = leadingViewMode;
[self setNeedsLayout];
}
- (void)setVerticalDensity:(CGFloat)verticalDensity {
_verticalDensity = verticalDensity;
[self setNeedsLayout];
}
#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.bounds), self.layout.containerHeight);
}
- (CGFloat)numberOfLinesOfVisibleText {
return 1;
}
#pragma mark UITextField Layout Overrides
- (CGRect)textRectForBounds:(CGRect)bounds {
CGRect textRect = [self textRectFromLayout:self.layout labelPosition:self.labelPosition];
return [self adjustTextAreaFrame:textRect
withParentClassTextAreaFrame:[super textRectForBounds:bounds]];
}
- (CGRect)editingRectForBounds:(CGRect)bounds {
CGRect textRect = [self textRectFromLayout:self.layout labelPosition:self.labelPosition];
return [self adjustTextAreaFrame:textRect
withParentClassTextAreaFrame:[super editingRectForBounds:bounds]];
}
- (CGRect)borderRectForBounds:(CGRect)bounds {
if (!self.containerStyle) {
return [super borderRectForBounds:bounds];
}
return CGRectZero;
}
- (CGRect)clearButtonRectForBounds:(CGRect)bounds {
return self.layout.clearButtonFrame;
}
- (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 ?: MDCTextControlDefaultUITextFieldFont();
}
- (UIFont *)floatingFont {
return [self.containerStyle floatingFontWithNormalFont:self.normalFont];
}
#pragma mark Dynamic Type
- (void)setAdjustsFontForContentSizeCategory:(BOOL)adjustsFontForContentSizeCategory {
[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 MDCTextControlStateWith(self.isEnabled, self.isEditing);
}
#pragma mark Placeholder
- (BOOL)shouldPlaceholderBeVisible {
return MDCTextControlShouldPlaceholderBeVisibleWithPlaceholder(self.placeholder, self.text,
self.labelPosition);
}
#pragma mark Label
- (void)animateLabel {
__weak MDCBaseTextField *weakSelf = self;
[MDCTextControlLabelAnimation animateLabel:self.label
state:self.labelPosition
normalLabelFrame:self.layout.labelFrameNormal
floatingLabelFrame:self.layout.labelFrameFloating
normalFont:self.normalFont
floatingFont:self.floatingFont
labelTruncationIsPresent:self.layout.labelTruncationIsPresent
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;
}
- (MDCTextControlLabelPosition)determineCurrentLabelPosition {
return MDCTextControlLabelPositionWith(self.label.text.length > 0, self.text.length > 0,
self.canLabelFloat, self.isEditing);
}
#pragma mark Coloring
- (void)applyColorViewModel:(MDCTextControlColorViewModel *)colorViewModel
withLabelPosition:(MDCTextControlLabelPosition)labelPosition {
UIColor *labelColor = [UIColor clearColor];
if (labelPosition == MDCTextControlLabelPositionNormal) {
labelColor = colorViewModel.normalLabelColor;
} else if (labelPosition == MDCTextControlLabelPositionFloating) {
labelColor = colorViewModel.floatingLabelColor;
}
if (![self.textColor isEqual:colorViewModel.textColor]) {
self.textColor = colorViewModel.textColor;
}
if (![self.leadingAssistiveLabel.textColor isEqual:colorViewModel.leadingAssistiveLabelColor]) {
self.leadingAssistiveLabel.textColor = colorViewModel.leadingAssistiveLabelColor;
}
if (![self.trailingAssistiveLabel.textColor isEqual:colorViewModel.trailingAssistiveLabelColor]) {
self.trailingAssistiveLabel.textColor = colorViewModel.trailingAssistiveLabelColor;
}
if (![self.label.textColor isEqual:labelColor]) {
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 MDCTextControlTextField
- (MDCTextControlTextFieldSideViewAlignment)sideViewAlignment {
return MDCTextControlTextFieldSideViewAlignmentCenteredInContainer;
}
#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;
}
- (UIBezierPath *)accessibilityPath {
return [UIBezierPath bezierPathWithRect:self.accessibilityFrame];
}
- (CGRect)accessibilityFrame {
if (self.window) {
CGRect bounds = self.bounds;
CGFloat boundsMinY = CGRectGetMinY(bounds);
CGFloat floatingLabelMinY = CGRectGetMinY(self.layout.labelFrameFloating);
if (floatingLabelMinY < boundsMinY) {
CGFloat difference = boundsMinY - floatingLabelMinY;
bounds.origin.y = boundsMinY - difference;
bounds.size.height = bounds.size.height + difference;
}
return [self convertRect:bounds toCoordinateSpace:self.window.screen.coordinateSpace];
} else {
return [super accessibilityFrame];
}
}
#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;
}
#pragma mark UIKeyInput
- (void)deleteBackward {
BOOL shouldDeleteBackward = YES;
if ([self.baseTextFieldDelegate
respondsToSelector:@selector(baseTextFieldShouldDeleteBackward:)]) {
shouldDeleteBackward = [self.baseTextFieldDelegate baseTextFieldShouldDeleteBackward:self];
}
if (shouldDeleteBackward) {
[super deleteBackward];
if ([self.baseTextFieldDelegate
respondsToSelector:@selector(baseTextFieldDidDeleteBackward:)]) {
[self.baseTextFieldDelegate baseTextFieldDidDeleteBackward:self];
}
}
}
#pragma mark - Key-value observing
- (void)observeAssistiveLabelKeyPaths {
for (NSString *keyPath in [MDCBaseTextField assistiveLabelKVOKeyPaths]) {
[self.leadingAssistiveLabel addObserver:self
forKeyPath:keyPath
options:NSKeyValueObservingOptionNew
context:kKVOContextMDCBaseTextField];
[self.trailingAssistiveLabel addObserver:self
forKeyPath:keyPath
options:NSKeyValueObservingOptionNew
context:kKVOContextMDCBaseTextField];
}
}
- (void)removeObservers {
for (NSString *keyPath in [MDCBaseTextField assistiveLabelKVOKeyPaths]) {
[self.leadingAssistiveLabel removeObserver:self
forKeyPath:keyPath
context:kKVOContextMDCBaseTextField];
[self.trailingAssistiveLabel removeObserver:self
forKeyPath:keyPath
context:kKVOContextMDCBaseTextField];
}
}
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary<NSKeyValueChangeKey, id> *)change
context:(void *)context {
if (context == kKVOContextMDCBaseTextField) {
if (object != self.leadingAssistiveLabel && object != self.trailingAssistiveLabel) {
return;
}
for (NSString *assistiveLabelKeyPath in [MDCBaseTextField assistiveLabelKVOKeyPaths]) {
if ([assistiveLabelKeyPath isEqualToString:keyPath]) {
[self setNeedsLayout];
break;
}
}
}
}
+ (NSArray<NSString *> *)assistiveLabelKVOKeyPaths {
static NSArray<NSString *> *keyPaths = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
keyPaths = @[
NSStringFromSelector(@selector(text)),
NSStringFromSelector(@selector(font)),
];
});
return keyPaths;
}
@end