| // 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 "MDCMultilineTextField.h" |
| |
| #import "private/MDCTextInputCommonFundament.h" |
| #import "MDCIntrinsicHeightTextView.h" |
| #import "MDCMultilineTextInputLayoutDelegate.h" |
| #import "MDCTextField.h" |
| #import "MDCTextFieldPositioningDelegate.h" |
| #import "MDCTextInputBorderView.h" |
| #import "MDCTextInputUnderlineView.h" |
| |
| #import "MaterialMath.h" |
| #import "MaterialTypography.h" |
| |
| /** The key for localization of the @c clearButton accessibilityLabel. */ |
| static NSString *const kClearButtonKey = @"MaterialTextFieldClearButtonAccessibilityLabel"; |
| /** Table name within the bundle used for localizing accessibility values. */ |
| static NSString *const kAccessibilityLocalizationStringsTableName = @"MaterialTextField"; |
| // The Bundle for string resources. |
| static NSString *const kBundle = @"MaterialTextFields.bundle"; |
| // The font opacity to be used for the caption. |
| static const CGFloat kButtonFontOpacity = 0.54f; |
| |
| @interface MDCMultilineTextField () { |
| UIColor *_cursorColor; |
| |
| MDCIntrinsicHeightTextView *_textView; |
| } |
| |
| @property(nonatomic, assign, getter=isEditing) BOOL editing; |
| |
| @property(nonatomic, assign) CGFloat textViewWidth; |
| |
| @property(nonatomic, strong) MDCTextInputCommonFundament *fundament; |
| |
| @property(nonatomic, strong) NSLayoutConstraint *textViewBottomSuperviewBottom; |
| @property(nonatomic, strong) NSLayoutConstraint *textViewLeading; |
| @property(nonatomic, strong) NSLayoutConstraint *textViewMinHeight; |
| @property(nonatomic, strong) NSLayoutConstraint *textViewTop; |
| @property(nonatomic, strong) NSLayoutConstraint *textViewTrailing; |
| |
| // textViewTrailingTrailingViewLeading is a constraint from .textView's trailing edge to |
| // .trailingView's leading edge. |
| @property(nonatomic, strong) NSLayoutConstraint *textViewTrailingTrailingViewLeading; |
| @property(nonatomic, strong) NSLayoutConstraint *trailingViewCenterY; |
| @property(nonatomic, strong) NSLayoutConstraint *trailingViewTrailing; |
| |
| @end |
| |
| @implementation MDCMultilineTextField |
| |
| @synthesize expandsOnOverflow = _expandsOnOverflow; |
| @synthesize minimumLines = _minimumLines; |
| @synthesize trailingView = _trailingView; |
| @synthesize trailingViewMode = _trailingViewMode; |
| @synthesize mdc_elevationDidChangeBlock = _mdc_elevationDidChangeBlock; |
| @synthesize mdc_overrideBaseElevation = _mdc_overrideBaseElevation; |
| |
| - (instancetype)initWithFrame:(CGRect)frame { |
| self = [super initWithFrame:frame]; |
| if (self) { |
| _textView = [[MDCIntrinsicHeightTextView alloc] initWithFrame:CGRectZero]; |
| [self setupTextView]; |
| |
| _fundament = [[MDCTextInputCommonFundament alloc] initWithTextInput:self]; |
| |
| [self commonMDCMultilineTextFieldInitialization]; |
| } |
| |
| return self; |
| } |
| |
| - (instancetype)initWithCoder:(NSCoder *)aDecoder { |
| self = [super initWithCoder:aDecoder]; |
| if (self) { |
| if (!_textView) { |
| _textView = [[MDCIntrinsicHeightTextView alloc] initWithFrame:CGRectZero]; |
| } |
| // It should be noted that setupTextView sets many properties of the textView that may have |
| // been encoded differently. |
| [self setupTextView]; |
| |
| _fundament = [[MDCTextInputCommonFundament alloc] initWithTextInput:self]; |
| |
| [self commonMDCMultilineTextFieldInitialization]; |
| } |
| |
| return self; |
| } |
| |
| - (instancetype)copyWithZone:(__unused NSZone *)zone { |
| MDCMultilineTextField *copy = [[[self class] alloc] initWithFrame:self.frame]; |
| |
| copy.expandsOnOverflow = self.expandsOnOverflow; |
| copy.cursorColor = self.cursorColor; |
| |
| // The .fundament creates a .clearButton so setting the clearButton's .tintColor must wait for the |
| // final .fundament to be created. |
| copy.fundament = [self.fundament copy]; |
| copy.clearButton.tintColor = self.clearButton.tintColor; |
| |
| copy.layoutDelegate = self.layoutDelegate; |
| copy.minimumLines = self.minimumLines; |
| copy.multilineDelegate = self.multilineDelegate; |
| copy.placeholder = self.placeholder; |
| copy.text = self.text; |
| if ([self.trailingView conformsToProtocol:@protocol(NSCopying)]) { |
| copy.trailingView = [self.trailingView copy]; |
| } |
| copy.trailingViewMode = self.trailingViewMode; |
| return copy; |
| } |
| |
| - (void)commonMDCMultilineTextFieldInitialization { |
| self.backgroundColor = [UIColor clearColor]; |
| |
| self.textColor = _fundament.textColor; |
| // TODO: (#4331) This needs to be converted to the new text scheme. |
| self.font = [UIFont mdc_standardFontForMaterialTextStyle:MDCFontTextStyleBody1]; |
| self.clearButton.tintColor = [UIColor colorWithWhite:0 alpha:kButtonFontOpacity]; |
| NSBundle *bundle = [[self class] bundle]; |
| NSString *accessibilityLabel = |
| [bundle localizedStringForKey:kClearButtonKey |
| value:@"Clear text" |
| table:kAccessibilityLocalizationStringsTableName]; |
| self.clearButton.accessibilityLabel = accessibilityLabel; |
| |
| _cursorColor = MDCTextInputCursorColor(); |
| [self applyCursorColor]; |
| |
| self.editable = YES; |
| |
| self.expandsOnOverflow = YES; |
| _minimumLines = 1; |
| |
| [self setupUnderlineConstraints]; |
| |
| [self setContentCompressionResistancePriority:UILayoutPriorityDefaultHigh + 1 |
| forAxis:UILayoutConstraintAxisVertical]; |
| _mdc_overrideBaseElevation = -1; |
| } |
| |
| - (void)subscribeForNotifications { |
| NSNotificationCenter *defaultCenter = [NSNotificationCenter defaultCenter]; |
| [defaultCenter addObserver:self |
| selector:@selector(textViewDidBeginEditing:) |
| name:UITextViewTextDidBeginEditingNotification |
| object:self.textView]; |
| [defaultCenter addObserver:self |
| selector:@selector(textViewDidEndEditing:) |
| name:UITextViewTextDidEndEditingNotification |
| object:self.textView]; |
| [defaultCenter addObserver:self |
| selector:@selector(textViewDidChange:) |
| name:UITextViewTextDidChangeNotification |
| object:self.textView]; |
| } |
| |
| #pragma mark - UIResponder Overrides |
| |
| - (BOOL)becomeFirstResponder { |
| return [self.textView becomeFirstResponder]; |
| } |
| |
| - (BOOL)isFirstResponder { |
| return self.textView.isFirstResponder; |
| } |
| |
| - (BOOL)resignFirstResponder { |
| return [self.textView resignFirstResponder]; |
| } |
| |
| #pragma mark - TextView Implementation |
| |
| - (void)setupTextView { |
| [self insertSubview:self.textView atIndex:0]; |
| self.textView.translatesAutoresizingMaskIntoConstraints = NO; |
| self.textView.scrollEnabled = !_expandsOnOverflow; |
| |
| [self.textView setContentHuggingPriority:UILayoutPriorityDefaultLow - 1 |
| forAxis:UILayoutConstraintAxisHorizontal]; |
| [self.textView setContentHuggingPriority:UILayoutPriorityDefaultLow - 1 |
| forAxis:UILayoutConstraintAxisVertical]; |
| [self.textView setContentCompressionResistancePriority:UILayoutPriorityDefaultLow - 1 |
| forAxis:UILayoutConstraintAxisHorizontal]; |
| [self.textView setContentCompressionResistancePriority:UILayoutPriorityDefaultHigh + 1 |
| forAxis:UILayoutConstraintAxisVertical]; |
| |
| self.textView.textContainer.lineBreakMode = NSLineBreakByTruncatingTail; |
| self.textView.textContainer.lineFragmentPadding = 0; |
| |
| self.textView.textContainerInset = UIEdgeInsetsZero; |
| [self subscribeForNotifications]; |
| |
| self.textView.backgroundColor = [UIColor clearColor]; |
| self.textView.opaque = NO; |
| } |
| |
| #pragma mark - Underline View Implementation |
| |
| - (void)setupUnderlineConstraints { |
| if (!self.underline) { |
| return; |
| } |
| NSLayoutConstraint *underlineLeading = |
| [NSLayoutConstraint constraintWithItem:self.underline |
| attribute:NSLayoutAttributeLeading |
| relatedBy:NSLayoutRelationEqual |
| toItem:self |
| attribute:NSLayoutAttributeLeading |
| multiplier:1 |
| constant:0]; |
| underlineLeading.priority = UILayoutPriorityDefaultLow; |
| underlineLeading.active = YES; |
| |
| NSLayoutConstraint *underlineTrailing = |
| [NSLayoutConstraint constraintWithItem:self.underline |
| attribute:NSLayoutAttributeTrailing |
| relatedBy:NSLayoutRelationEqual |
| toItem:self |
| attribute:NSLayoutAttributeTrailing |
| multiplier:1 |
| constant:0]; |
| underlineTrailing.priority = UILayoutPriorityDefaultLow; |
| underlineTrailing.active = YES; |
| |
| NSLayoutConstraint *underlineYTextView = |
| [NSLayoutConstraint constraintWithItem:self.underline |
| attribute:NSLayoutAttributeBottom |
| relatedBy:NSLayoutRelationEqual |
| toItem:self.textView |
| attribute:NSLayoutAttributeBottom |
| multiplier:1 |
| constant:MDCTextInputHalfPadding]; |
| underlineYTextView.priority = UILayoutPriorityDefaultLow; |
| underlineYTextView.active = YES; |
| } |
| |
| #pragma mark - Layout (UIView) |
| |
| - (CGSize)intrinsicContentSize { |
| if (self.useConstraintsForIntrinsicContentSize) { |
| return [super intrinsicContentSize]; |
| } |
| CGSize boundingSize = CGSizeZero; |
| boundingSize.width = UIViewNoIntrinsicMetric; |
| |
| [self.textView layoutIfNeeded]; |
| CGFloat estimatedTextViewHeight = |
| [self.textView systemLayoutSizeFittingSize:CGSizeMake(CGRectGetWidth(self.textView.bounds), 0) |
| withHorizontalFittingPriority:UILayoutPriorityDefaultLow |
| verticalFittingPriority:UILayoutPriorityDefaultLow] |
| .height; |
| |
| CGFloat minimumHeight = [self estimatedTextViewLineHeight] * self.minimumLines; |
| estimatedTextViewHeight = MAX(estimatedTextViewHeight, minimumHeight); |
| |
| boundingSize.height = [self textInsets].top + estimatedTextViewHeight + [self textInsets].bottom; |
| |
| return boundingSize; |
| } |
| |
| - (CGSize)sizeThatFits:(CGSize)size { |
| self.sizeThatFitsWidthHint = size.width; |
| CGSize sizeThatFits = [self intrinsicContentSize]; |
| sizeThatFits.width = self.sizeThatFitsWidthHint; |
| self.sizeThatFitsWidthHint = 0; |
| return sizeThatFits; |
| } |
| |
| - (void)setSizeThatFitsWidthHint:(CGFloat)sizeThatFitsWidthHint { |
| self.fundament.sizeThatFitsWidthHint = sizeThatFitsWidthHint; |
| } |
| |
| - (CGFloat)sizeThatFitsWidthHint { |
| return self.fundament.sizeThatFitsWidthHint; |
| } |
| |
| - (void)layoutSubviews { |
| [super layoutSubviews]; |
| |
| if (![self.textView isDescendantOfView:self]) { |
| [self setupTextView]; |
| } |
| if (self.subviews.firstObject != self.textView) { |
| [self sendSubviewToBack:self.textView]; |
| } |
| |
| [self updateTrailingViewAlpha]; |
| |
| [self.fundament layoutSubviewsOfInput]; |
| [self updateBorder]; |
| [self applyCursorColor]; |
| |
| if ([self.positioningDelegate respondsToSelector:@selector(textInputDidLayoutSubviews)]) { |
| [self.positioningDelegate textInputDidLayoutSubviews]; |
| } |
| |
| [self updateIntrinsicSizeFromTextView]; |
| } |
| |
| - (void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection { |
| [super traitCollectionDidChange:previousTraitCollection]; |
| |
| if (self.traitCollectionDidChangeBlock) { |
| self.traitCollectionDidChangeBlock(self, previousTraitCollection); |
| } |
| } |
| |
| - (CGFloat)mdc_currentElevation { |
| return 0; |
| } |
| |
| - (void)updateConstraints { |
| if (!self.textViewLeading) { |
| self.textViewLeading = [NSLayoutConstraint constraintWithItem:self.textView |
| attribute:NSLayoutAttributeLeading |
| relatedBy:NSLayoutRelationEqual |
| toItem:self |
| attribute:NSLayoutAttributeLeading |
| multiplier:1 |
| constant:self.textInsets.left]; |
| self.textViewLeading.priority = UILayoutPriorityDefaultLow; |
| self.textViewLeading.active = YES; |
| } |
| self.textViewLeading.constant = self.textInsets.left; |
| |
| if (!self.textViewBottomSuperviewBottom) { |
| self.textViewBottomSuperviewBottom = |
| [NSLayoutConstraint constraintWithItem:self.textView |
| attribute:NSLayoutAttributeBottom |
| relatedBy:NSLayoutRelationLessThanOrEqual |
| toItem:self |
| attribute:NSLayoutAttributeBottom |
| multiplier:1 |
| constant:-1 * self.textInsets.bottom]; |
| self.textViewBottomSuperviewBottom.priority = UILayoutPriorityDefaultLow; |
| self.textViewBottomSuperviewBottom.active = YES; |
| } |
| self.textViewBottomSuperviewBottom.constant = -1 * self.textInsets.bottom; |
| |
| if (!self.textViewTop) { |
| self.textViewTop = [NSLayoutConstraint constraintWithItem:self.textView |
| attribute:NSLayoutAttributeTop |
| relatedBy:NSLayoutRelationEqual |
| toItem:self |
| attribute:NSLayoutAttributeTop |
| multiplier:1 |
| constant:self.textInsets.top]; |
| self.textViewTop.priority = UILayoutPriorityDefaultLow + 1; |
| self.textViewTop.active = YES; |
| } |
| self.textViewTop.constant = self.textInsets.top; |
| |
| if (!self.textViewTrailing) { |
| self.textViewTrailing = [NSLayoutConstraint constraintWithItem:self.textView |
| attribute:NSLayoutAttributeTrailing |
| relatedBy:NSLayoutRelationEqual |
| toItem:self.clearButton |
| attribute:NSLayoutAttributeLeading |
| multiplier:1 |
| constant:-1 * [self textInsets].right]; |
| self.textViewTrailing.priority = UILayoutPriorityDefaultLow; |
| self.textViewTrailing.active = YES; |
| } |
| self.textViewTrailing.constant = -1 * [self textInsets].right; |
| |
| if (!self.textViewMinHeight) { |
| self.textViewMinHeight = [NSLayoutConstraint |
| constraintWithItem:self.textView |
| attribute:NSLayoutAttributeHeight |
| relatedBy:NSLayoutRelationGreaterThanOrEqual |
| toItem:nil |
| attribute:NSLayoutAttributeNotAnAttribute |
| multiplier:1 |
| constant:[self estimatedTextViewLineHeight] * self.minimumLines]; |
| self.textViewMinHeight.priority = UILayoutPriorityDefaultLow + 1; |
| } |
| self.textViewMinHeight.active = YES; |
| self.textViewMinHeight.constant = [self estimatedTextViewLineHeight] * self.minimumLines; |
| [self.fundament updateConstraintsOfInput]; |
| |
| [self updateTrailingViewLayout]; |
| [self updateIntrinsicSizeFromTextView]; |
| |
| // This must always be the last message in this method. |
| [super updateConstraints]; |
| } |
| |
| + (BOOL)requiresConstraintBasedLayout { |
| return YES; |
| } |
| |
| - (CGFloat)estimatedTextViewLineHeight { |
| CGFloat scale = UIScreen.mainScreen.scale; |
| return ceil(self.textView.font.lineHeight * scale) / scale; |
| } |
| |
| - (void)updateIntrinsicSizeFromTextView { |
| if ([self textViewWidthDidChange]) { |
| [self invalidateIntrinsicContentSize]; |
| } |
| } |
| |
| - (BOOL)textViewWidthDidChange { |
| BOOL widthDidChange = NO; |
| CGFloat currentTextViewWidth = CGRectGetWidth(self.textView.bounds); |
| |
| if (self.textViewWidth != currentTextViewWidth) { |
| widthDidChange = YES; |
| } |
| self.textViewWidth = currentTextViewWidth; |
| |
| return widthDidChange; |
| } |
| |
| #pragma mark - Touch (UIView) |
| |
| - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event { |
| UIView *receiver = [super hitTest:point withEvent:event]; |
| |
| // We do this so the hit zone to become first responder is larger than just the small text view. |
| if (receiver == self && self.isEditing == NO) { |
| return self.textView; |
| } |
| |
| return receiver; |
| } |
| |
| #pragma mark - Trailing View Implementation |
| |
| - (void)updateTrailingViewLayout { |
| if (!self.trailingView) { |
| return; |
| } |
| |
| self.trailingView.translatesAutoresizingMaskIntoConstraints = NO; |
| |
| if (![self.trailingView isDescendantOfView:self]) { |
| [self addSubview:self.trailingView]; |
| } |
| |
| if (!self.trailingViewTrailing) { |
| self.trailingViewTrailing = [NSLayoutConstraint constraintWithItem:self.trailingView |
| attribute:NSLayoutAttributeTrailing |
| relatedBy:NSLayoutRelationEqual |
| toItem:self.clearButton |
| attribute:NSLayoutAttributeTrailing |
| multiplier:1 |
| constant:0]; |
| } |
| self.trailingViewTrailing.active = YES; |
| |
| if (!self.trailingViewCenterY) { |
| self.trailingViewCenterY = [NSLayoutConstraint constraintWithItem:self.trailingView |
| attribute:NSLayoutAttributeCenterY |
| relatedBy:NSLayoutRelationEqual |
| toItem:self.clearButton |
| attribute:NSLayoutAttributeCenterY |
| multiplier:1 |
| constant:0]; |
| } |
| self.trailingViewCenterY.active = YES; |
| |
| if (!self.textViewTrailingTrailingViewLeading) { |
| self.textViewTrailingTrailingViewLeading = |
| [NSLayoutConstraint constraintWithItem:self.textView |
| attribute:NSLayoutAttributeTrailing |
| relatedBy:NSLayoutRelationEqual |
| toItem:self.trailingView |
| attribute:NSLayoutAttributeLeading |
| multiplier:1 |
| constant:self.textViewTrailing.constant]; |
| } |
| self.textViewTrailingTrailingViewLeading.active = !MDCCGFloatEqual([self trailingViewAlpha], 0); |
| } |
| |
| - (void)updateTrailingViewAlpha { |
| self.trailingView.alpha = [self trailingViewAlpha]; |
| } |
| |
| - (CGFloat)trailingViewAlpha { |
| // The trailing view has the same behavior as .rightView in UITextField: It has visual precedence |
| // over the clear button. |
| CGFloat trailingViewAlpha; |
| switch (self.trailingViewMode) { |
| case UITextFieldViewModeAlways: |
| trailingViewAlpha = 1; |
| break; |
| case UITextFieldViewModeWhileEditing: |
| trailingViewAlpha = self.isEditing ? 1 : 0; |
| break; |
| case UITextFieldViewModeUnlessEditing: |
| trailingViewAlpha = self.isEditing ? 0 : 1; |
| break; |
| case UITextFieldViewModeNever: |
| trailingViewAlpha = 0; |
| break; |
| default: |
| NSAssert(NO, @"Invalid enumeration value %li.", (long)self.trailingViewMode); |
| trailingViewAlpha = self.trailingViewAlpha; |
| break; |
| } |
| return trailingViewAlpha; |
| } |
| |
| #pragma mark - Border Implementation |
| |
| - (UIBezierPath *)defaultBorderPath { |
| CGRect borderBound = self.bounds; |
| borderBound.size.height = self.underline.center.y; |
| return [UIBezierPath |
| bezierPathWithRoundedRect:borderBound |
| byRoundingCorners:UIRectCornerTopLeft | UIRectCornerTopRight |
| cornerRadii:CGSizeMake(MDCTextInputBorderRadius, MDCTextInputBorderRadius)]; |
| } |
| |
| - (void)updateBorder { |
| self.borderView.borderPath = self.borderPath; |
| } |
| |
| #pragma mark - Applying Color |
| |
| - (void)applyCursorColor { |
| self.textView.tintColor = self.cursorColor; |
| } |
| |
| #pragma mark - Properties Implementation |
| |
| - (NSAttributedString *)attributedPlaceholder { |
| return self.fundament.attributedPlaceholder; |
| } |
| |
| - (void)setAttributedPlaceholder:(NSAttributedString *)attributedPlaceholder { |
| self.fundament.attributedPlaceholder = attributedPlaceholder; |
| } |
| |
| - (NSAttributedString *)attributedText { |
| return self.textView.attributedText; |
| } |
| |
| - (void)setAttributedText:(NSAttributedString *)attributedText { |
| self.textView.attributedText = attributedText; |
| [self.fundament didSetText]; |
| [[NSNotificationCenter defaultCenter] postNotificationName:MDCTextFieldTextDidSetTextNotification |
| object:self]; |
| } |
| |
| - (UIBezierPath *)borderPath { |
| return self.fundament.borderPath ? self.fundament.borderPath : [self defaultBorderPath]; |
| } |
| |
| - (void)setBorderPath:(UIBezierPath *)borderPath { |
| if (![self.fundament.borderPath isEqual:borderPath]) { |
| self.fundament.borderPath = borderPath; |
| [self updateBorder]; |
| } |
| } |
| |
| - (MDCTextInputBorderView *)borderView { |
| return self.fundament.borderView; |
| } |
| |
| - (void)setBorderView:(MDCTextInputBorderView *)borderView { |
| self.fundament.borderView = borderView; |
| } |
| |
| - (UIButton *)clearButton { |
| return self.fundament.clearButton; |
| } |
| |
| - (UITextFieldViewMode)clearButtonMode { |
| return self.fundament.clearButtonMode; |
| } |
| |
| - (void)setClearButtonMode:(UITextFieldViewMode)clearButtonMode { |
| self.fundament.clearButtonMode = clearButtonMode; |
| } |
| |
| - (UIColor *)cursorColor { |
| return _cursorColor ?: MDCTextInputCursorColor(); |
| } |
| |
| - (void)setCursorColor:(UIColor *)cursorColor { |
| _cursorColor = cursorColor; |
| [self applyCursorColor]; |
| } |
| |
| - (void)setEditable:(BOOL)editable { |
| [self.textView setEditable:editable]; |
| self.fundament.enabled = editable; |
| } |
| |
| - (BOOL)isEnabled { |
| return self.fundament.isEnabled && self.textView.isEditable; |
| } |
| |
| - (void)setEnabled:(BOOL)enabled { |
| self.fundament.enabled = enabled; |
| self.textView.editable = enabled; |
| [[NSNotificationCenter defaultCenter] |
| postNotificationName:MDCTextInputDidToggleEnabledNotification |
| object:self]; |
| } |
| |
| - (void)setExpandsOnOverflow:(BOOL)expandsOnOverflow { |
| if (_expandsOnOverflow != expandsOnOverflow) { |
| _expandsOnOverflow = expandsOnOverflow; |
| self.textView.scrollEnabled = !expandsOnOverflow; |
| [self setNeedsUpdateConstraints]; |
| } |
| } |
| |
| - (UIFont *)font { |
| if (_textView) { |
| return _textView.font; |
| } |
| return self.textView.font; |
| } |
| |
| - (void)setFont:(UIFont *)font { |
| if (self.textView.font != font) { |
| UIFont *previousFont = self.textView.font; |
| [self.textView setFont:font]; |
| [_fundament didSetFont:previousFont]; |
| } |
| } |
| |
| - (BOOL)hidesPlaceholderOnInput { |
| return self.fundament.hidesPlaceholderOnInput; |
| } |
| |
| - (void)setHidesPlaceholderOnInput:(BOOL)hidesPlaceholderOnInput { |
| self.fundament.hidesPlaceholderOnInput = hidesPlaceholderOnInput; |
| } |
| |
| - (UILabel *)leadingUnderlineLabel { |
| return self.fundament.leadingUnderlineLabel; |
| } |
| |
| - (NSUInteger)minimumLines { |
| if (_minimumLines < 1) { |
| _minimumLines = 1; |
| } |
| return _minimumLines; |
| } |
| |
| - (void)setMinimumLines:(NSUInteger)minimumLines { |
| if (_minimumLines != minimumLines) { |
| _minimumLines = minimumLines > 0 ? minimumLines : 1; |
| [self setNeedsUpdateConstraints]; |
| } |
| } |
| |
| - (NSString *)placeholder { |
| return self.fundament.placeholder; |
| } |
| |
| - (void)setPlaceholder:(NSString *)placeholder { |
| [self.fundament setPlaceholder:placeholder]; |
| } |
| |
| - (UILabel *)placeholderLabel { |
| return self.fundament.placeholderLabel; |
| } |
| |
| - (id<MDCTextInputPositioningDelegate>)positioningDelegate { |
| return self.fundament.positioningDelegate; |
| } |
| |
| - (void)setPositioningDelegate:(id<MDCTextInputPositioningDelegate>)positioningDelegate { |
| self.fundament.positioningDelegate = positioningDelegate; |
| } |
| |
| - (NSString *)text { |
| return self.textView.text; |
| } |
| |
| - (void)setText:(NSString *)text { |
| [self.textView setText:text]; |
| [self.fundament didSetText]; |
| [[NSNotificationCenter defaultCenter] postNotificationName:MDCTextFieldTextDidSetTextNotification |
| object:self]; |
| } |
| |
| - (UIColor *)textColor { |
| return self.fundament.textColor; |
| } |
| |
| - (void)setTextColor:(UIColor *)textColor { |
| [self.textView setTextColor:textColor]; |
| self.fundament.textColor = textColor; |
| } |
| |
| - (UIEdgeInsets)textInsets { |
| return self.fundament.textInsets; |
| } |
| |
| - (MDCTextInputTextInsetsMode)textInsetsMode { |
| return self.fundament.textInsetsMode; |
| } |
| |
| - (void)setTextInsetsMode:(MDCTextInputTextInsetsMode)textInsetsMode { |
| self.fundament.textInsetsMode = textInsetsMode; |
| } |
| |
| - (MDCIntrinsicHeightTextView *)textView { |
| if (!_textView) { |
| _textView = [[MDCIntrinsicHeightTextView alloc] initWithFrame:CGRectZero]; |
| [self setupTextView]; |
| [self setupUnderlineConstraints]; |
| } |
| return _textView; |
| } |
| |
| - (void)setTextView:(MDCIntrinsicHeightTextView *)textView { |
| if (![_textView isEqual:textView]) { |
| [_textView removeFromSuperview]; |
| _textView = textView; |
| if (textView) { |
| [self setupTextView]; |
| [self setupUnderlineConstraints]; |
| } |
| } |
| } |
| |
| - (UILabel *)trailingUnderlineLabel { |
| return self.fundament.trailingUnderlineLabel; |
| } |
| |
| - (void)setTrailingView:(UIView *)trailingView { |
| if (_trailingView != trailingView) { |
| [_trailingView removeFromSuperview]; |
| [self addSubview:trailingView]; |
| _trailingView = trailingView; |
| |
| // Remove constraints related to the previous trailingView. |
| self.trailingViewTrailing = nil; |
| self.trailingViewCenterY = nil; |
| self.textViewTrailingTrailingViewLeading = nil; |
| |
| [self setNeedsUpdateConstraints]; |
| } |
| } |
| |
| - (MDCTextInputUnderlineView *)underline { |
| return self.fundament.underline; |
| } |
| |
| - (BOOL)hasTextContent { |
| return self.text.length > 0; |
| } |
| |
| - (void)clearText { |
| self.text = nil; |
| } |
| |
| #pragma mark - UITextView Notification Observation |
| |
| - (void)textViewDidBeginEditing:(__unused NSNotification *)note { |
| self.editing = YES; |
| [self.fundament didBeginEditing]; |
| } |
| |
| - (void)textViewDidChange:(__unused NSNotification *)note { |
| [self.fundament didChange]; |
| CGSize currentSize = self.bounds.size; |
| CGSize requiredSize = [self sizeThatFits:CGSizeMake(currentSize.width, CGFLOAT_MAX)]; |
| if (currentSize.height != requiredSize.height && self.layoutDelegate && |
| [self.layoutDelegate respondsToSelector:@selector(multilineTextField: |
| didChangeContentSize:)]) { |
| id<MDCMultilineTextInputLayoutDelegate> delegate = |
| (id<MDCMultilineTextInputLayoutDelegate>)self.layoutDelegate; |
| [delegate multilineTextField:self didChangeContentSize:requiredSize]; |
| } |
| } |
| |
| - (void)textViewDidEndEditing:(__unused NSNotification *)note { |
| self.editing = NO; |
| [self.fundament didEndEditing]; |
| } |
| |
| #pragma mark - Accessibility |
| |
| - (NSInteger)accessibilityElementCount { |
| if (self.isAccessibilityElement) { |
| return [super accessibilityElementCount]; |
| } else { |
| return [self accessibilityElements].count; |
| } |
| } |
| |
| - (NSString *)accessibilityValue { |
| NSString *value = [self.text length] ? self.text : self.placeholder; |
| |
| if (self.leadingUnderlineLabel.text.length > 0) { |
| [value stringByAppendingFormat:@"%@ %@", [super accessibilityValue], |
| self.leadingUnderlineLabel.accessibilityLabel]; |
| } |
| |
| return value; |
| } |
| |
| - (NSString *)accessibilityLabel { |
| if (self.isAccessibilityElement) { |
| NSMutableArray *accessibilityStrings = [[NSMutableArray alloc] init]; |
| if ([super accessibilityLabel].length > 0) { |
| [accessibilityStrings addObject:[super accessibilityLabel]]; |
| } else if (self.placeholderLabel.accessibilityLabel.length > 0) { |
| [accessibilityStrings addObject:self.placeholderLabel.accessibilityLabel]; |
| } |
| if (self.leadingUnderlineLabel.accessibilityLabel.length > 0) { |
| [accessibilityStrings addObject:self.leadingUnderlineLabel.accessibilityLabel]; |
| } |
| if (self.trailingUnderlineLabel.accessibilityLabel.length > 0) { |
| [accessibilityStrings addObject:self.trailingUnderlineLabel.accessibilityLabel]; |
| } |
| return accessibilityStrings.count > 0 ? [accessibilityStrings componentsJoinedByString:@", "] |
| : nil; |
| } else { |
| return [super accessibilityLabel]; |
| } |
| } |
| |
| - (NSArray *)accessibilityElements { |
| if (self.isAccessibilityElement) { |
| return [super accessibilityElements]; |
| } else { |
| NSMutableArray *mutableElements = [super accessibilityElements] == nil |
| ? [[NSMutableArray alloc] init] |
| : [[super accessibilityElements] mutableCopy]; |
| |
| if (self.placeholderLabel.isAccessibilityElement) { |
| [mutableElements insertObject:self.placeholderLabel atIndex:0]; |
| } |
| |
| if (self.textView.isAccessibilityElement) { |
| [mutableElements addObject:self.textView]; |
| } |
| |
| if (self.leadingUnderlineLabel.isAccessibilityElement) { |
| [mutableElements addObject:self.leadingUnderlineLabel]; |
| } |
| |
| if (self.clearButton.isAccessibilityElement && self.isEditing) { |
| [mutableElements addObject:self.clearButton]; |
| } |
| |
| if (self.trailingUnderlineLabel.isAccessibilityElement) { |
| [mutableElements addObject:self.trailingUnderlineLabel]; |
| } |
| |
| return [mutableElements copy]; |
| } |
| } |
| |
| - (BOOL)mdc_adjustsFontForContentSizeCategory { |
| return _fundament.mdc_adjustsFontForContentSizeCategory; |
| } |
| |
| - (void)mdc_setAdjustsFontForContentSizeCategory:(BOOL)adjusts { |
| // Prior to iOS 9 RTL was not automatically applied, so we don't need to apply any fixes. |
| if ([self.textView respondsToSelector:@selector(setAdjustsFontForContentSizeCategory:)]) { |
| [self.textView setAdjustsFontForContentSizeCategory:adjusts]; |
| } |
| |
| [_fundament mdc_setAdjustsFontForContentSizeCategory:adjusts]; |
| } |
| |
| #pragma mark - Resource Bundle |
| |
| + (NSBundle *)bundle { |
| static NSBundle *bundle = nil; |
| static dispatch_once_t onceToken; |
| dispatch_once(&onceToken, ^{ |
| bundle = [NSBundle bundleWithPath:[self bundlePathWithName:kBundle]]; |
| }); |
| |
| return bundle; |
| } |
| |
| + (NSString *)bundlePathWithName:(NSString *)bundleName { |
| NSBundle *bundle = [NSBundle bundleForClass:[MDCMultilineTextField class]]; |
| NSString *resourcePath = [(nil == bundle ? [NSBundle mainBundle] : bundle) resourcePath]; |
| return [resourcePath stringByAppendingPathComponent:bundleName]; |
| } |
| |
| @end |