blob: 1989c5cab81800924608beb45a0ccd020179bfed [file] [log] [blame]
// Copyright 2016-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 "MDCTextField.h"
#import <MDFInternationalization/MDFInternationalization.h>
#import "MDCTextFieldPositioningDelegate.h"
#import "MDCTextInputBorderView.h"
#import "MDCTextInputUnderlineView.h"
#import "private/MDCTextField+Testing.h"
#import "private/MDCTextInputCommonFundament.h"
#import "MaterialMath.h"
#import "MaterialTypography.h"
NSString *const MDCTextFieldTextDidSetTextNotification = @"MDCTextFieldTextDidSetTextNotification";
NSString *const MDCTextInputDidToggleEnabledNotification =
@"MDCTextInputDidToggleEnabledNotification";
// The image we use for the clear button has a little too much air around it. So we have to shrink
// by this amount on each side.
static const CGFloat MDCTextInputClearButtonImageBuiltInPadding = (CGFloat)-2.5;
static const CGFloat MDCTextInputEditingRectRightViewPaddingCorrection = -2;
static const CGFloat MDCTextInputTextRectYCorrection = 1;
@interface MDCTextField () {
UIColor *_cursorColor;
UILabel *_inputLayoutStrut;
}
@property(nonatomic, strong) MDCTextInputCommonFundament *fundament;
/**
Constraint for center Y of the underline view.
Default constant: self.top + font line height + MDCTextInputHalfPadding.
eg: ~4 pts below the input rect.
*/
@property(nonatomic, strong) NSLayoutConstraint *underlineY;
@end
@implementation MDCTextField
@dynamic borderStyle;
@synthesize mdc_elevationDidChangeBlock = _mdc_elevationDidChangeBlock;
@synthesize mdc_overrideBaseElevation = _mdc_overrideBaseElevation;
- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
_fundament = [[MDCTextInputCommonFundament alloc] initWithTextInput:self];
[self commonMDCTextFieldInitialization];
}
return self;
}
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
self = [super initWithCoder:aDecoder];
if (self) {
NSString *interfaceBuilderPlaceholder = super.placeholder;
_fundament = [[MDCTextInputCommonFundament alloc] initWithTextInput:self];
[self commonMDCTextFieldInitialization];
if (interfaceBuilderPlaceholder.length) {
self.placeholder = interfaceBuilderPlaceholder;
}
self.placeholderLabel.backgroundColor = self.backgroundColor;
[self setNeedsLayout];
}
return self;
}
- (void)dealloc {
NSNotificationCenter *defaultCenter = [NSNotificationCenter defaultCenter];
[defaultCenter removeObserver:self];
}
- (instancetype)copyWithZone:(__unused NSZone *)zone {
MDCTextField *copy = [[[self class] alloc] initWithFrame:self.frame];
copy.cursorColor = self.cursorColor;
copy.fundament = [self.fundament copy];
copy.enabled = self.isEnabled;
if ([self.leadingView conformsToProtocol:@protocol(NSCopying)]) {
copy.leadingView = [self.leadingView copy];
}
copy.leadingViewMode = self.leadingViewMode;
copy.placeholder = [self.placeholder copy];
copy.text = [self.text copy];
copy.clearButton.tintColor = self.clearButton.tintColor;
if ([self.trailingView conformsToProtocol:@protocol(NSCopying)]) {
copy.trailingView = [self.trailingView copy];
}
copy.trailingViewMode = self.trailingViewMode;
return copy;
}
- (void)commonMDCTextFieldInitialization {
[super setBorderStyle:UITextBorderStyleNone];
// Set the clear button color to black with 54% opacity.
self.clearButton.tintColor = [UIColor colorWithWhite:0 alpha:[MDCTypography captionFontOpacity]];
_cursorColor = MDCTextInputCursorColor();
[self applyCursorColor];
[self setupUnderlineConstraints];
[self setupInputLayoutStrut];
NSNotificationCenter *defaultCenter = [NSNotificationCenter defaultCenter];
[defaultCenter addObserver:self
selector:@selector(textFieldDidBeginEditing:)
name:UITextFieldTextDidBeginEditingNotification
object:self];
[defaultCenter addObserver:self
selector:@selector(textFieldDidChange:)
name:UITextFieldTextDidChangeNotification
object:self];
[self setContentCompressionResistancePriority:UILayoutPriorityDefaultHigh + 1
forAxis:UILayoutConstraintAxisVertical];
_mdc_overrideBaseElevation = -1;
}
#pragma mark - Underline View Implementation
- (void)setupUnderlineConstraints {
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;
_underlineY =
[NSLayoutConstraint constraintWithItem:self.underline
attribute:NSLayoutAttributeBottom
relatedBy:NSLayoutRelationEqual
toItem:self
attribute:NSLayoutAttributeTop
multiplier:1
constant:[self textInsets].top + [self estimatedTextHeight] +
MDCTextInputHalfPadding];
_underlineY.priority = UILayoutPriorityDefaultLow;
_underlineY.active = YES;
}
- (CGFloat)underlineYConstant {
return [self textInsets].top + [self estimatedTextHeight] + MDCTextInputHalfPadding;
}
- (BOOL)needsUpdateUnderlinePosition {
return !MDCCGFloatEqual(self.underlineY.constant, [self underlineYConstant]);
}
- (void)updateUnderlinePosition {
self.underlineY.constant = [self underlineYConstant];
[self invalidateIntrinsicContentSize];
}
#pragma mark - Border Implementation
- (UIBezierPath *)defaultBorderPath {
CGRect borderBound = self.bounds;
borderBound.size.height = CGRectGetMaxY(self.underline.frame);
return [UIBezierPath
bezierPathWithRoundedRect:borderBound
byRoundingCorners:UIRectCornerTopLeft | UIRectCornerTopRight
cornerRadii:CGSizeMake(MDCTextInputBorderRadius, MDCTextInputBorderRadius)];
}
- (void)updateBorder {
self.borderView.borderPath = self.borderPath;
}
#pragma mark - Input Layout Strut Implementation
- (void)setupInputLayoutStrut {
self.inputLayoutStrut.hidden = YES;
self.inputLayoutStrut.numberOfLines = 1;
[self addSubview:self.inputLayoutStrut];
}
- (void)updateInputLayoutStrut {
self.inputLayoutStrut.font = self.font;
self.inputLayoutStrut.text = self.text;
UIEdgeInsets insets = [self textInsets];
self.inputLayoutStrut.frame =
CGRectMake(insets.left, insets.top, CGRectGetWidth(self.bounds) - insets.right,
self.inputLayoutStrut.intrinsicContentSize.height);
}
#pragma mark - Applying Color
- (void)applyCursorColor {
self.tintColor = self.cursorColor;
}
#pragma mark - MDCLeadingViewTextInput Implementation
- (void)setLeadingView:(UIView *)leadingView {
if ([self shouldManuallyEnforceRightToLeftLayoutForOverlayViews] &&
self.mdf_effectiveUserInterfaceLayoutDirection == UIUserInterfaceLayoutDirectionRightToLeft) {
self.rightView = leadingView;
} else {
self.leftView = leadingView;
}
}
- (UITextFieldViewMode)leadingViewMode {
if ([self shouldManuallyEnforceRightToLeftLayoutForOverlayViews] &&
self.mdf_effectiveUserInterfaceLayoutDirection == UIUserInterfaceLayoutDirectionRightToLeft) {
return self.rightViewMode;
}
return self.leftViewMode;
}
- (void)setLeadingViewMode:(UITextFieldViewMode)leadingViewMode {
if ([self shouldManuallyEnforceRightToLeftLayoutForOverlayViews] &&
self.mdf_effectiveUserInterfaceLayoutDirection == UIUserInterfaceLayoutDirectionRightToLeft) {
self.rightViewMode = leadingViewMode;
} else {
self.leftViewMode = leadingViewMode;
}
}
#pragma mark - Properties Implementation
- (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 _fundament.clearButton;
}
- (UIColor *)cursorColor {
return _cursorColor ?: MDCTextInputCursorColor();
}
- (void)setCursorColor:(UIColor *)cursorColor {
_cursorColor = cursorColor;
[self applyCursorColor];
}
- (BOOL)hidesPlaceholderOnInput {
return _fundament.hidesPlaceholderOnInput;
}
- (void)setHidesPlaceholderOnInput:(BOOL)hidesPlaceholderOnInput {
_fundament.hidesPlaceholderOnInput = hidesPlaceholderOnInput;
}
- (UILabel *)inputLayoutStrut {
if (!_inputLayoutStrut) {
_inputLayoutStrut = [[UILabel alloc] initWithFrame:CGRectZero];
}
return _inputLayoutStrut;
}
- (UILabel *)leadingUnderlineLabel {
return _fundament.leadingUnderlineLabel;
}
- (UILabel *)placeholderLabel {
return _fundament.placeholderLabel;
}
- (id<MDCTextInputPositioningDelegate>)positioningDelegate {
return _fundament.positioningDelegate;
}
- (void)setPositioningDelegate:(id<MDCTextInputPositioningDelegate>)positioningDelegate {
_fundament.positioningDelegate = positioningDelegate;
}
- (UIColor *)textColor {
return _fundament.textColor;
}
- (void)setTextColor:(UIColor *)textColor {
// This identity check was added in
// https://github.com/material-components/material-components-ios/pull/9480 in response to
// b/148159587
if (textColor != self.textColor) {
[super setTextColor:textColor];
_fundament.textColor = textColor;
}
}
- (UIEdgeInsets)textInsets {
return self.fundament.textInsets;
}
- (CGFloat)sizeThatFitsWidthHint {
return self.fundament.sizeThatFitsWidthHint;
}
- (void)setSizeThatFitsWidthHint:(CGFloat)sizeThatFitsWidthHint {
self.fundament.sizeThatFitsWidthHint = sizeThatFitsWidthHint;
}
- (MDCTextInputTextInsetsMode)textInsetsMode {
return self.fundament.textInsetsMode;
}
- (void)setTextInsetsMode:(MDCTextInputTextInsetsMode)textInsetsMode {
self.fundament.textInsetsMode = textInsetsMode;
}
- (UILabel *)trailingUnderlineLabel {
return _fundament.trailingUnderlineLabel;
}
// In iOS 8, .leftView and .rightView are not swapped in RTL so we have to do that manually.
- (UIView *)trailingView {
if ([self shouldManuallyEnforceRightToLeftLayoutForOverlayViews] &&
self.mdf_effectiveUserInterfaceLayoutDirection == UIUserInterfaceLayoutDirectionRightToLeft) {
return self.leftView;
}
return self.rightView;
}
- (void)setTrailingView:(UIView *)trailingView {
if ([self shouldManuallyEnforceRightToLeftLayoutForOverlayViews] &&
self.mdf_effectiveUserInterfaceLayoutDirection == UIUserInterfaceLayoutDirectionRightToLeft) {
self.leftView = trailingView;
} else {
self.rightView = trailingView;
}
}
- (UITextFieldViewMode)trailingViewMode {
if ([self shouldManuallyEnforceRightToLeftLayoutForOverlayViews] &&
self.mdf_effectiveUserInterfaceLayoutDirection == UIUserInterfaceLayoutDirectionRightToLeft) {
return self.leftViewMode;
}
return self.rightViewMode;
}
- (void)setTrailingViewMode:(UITextFieldViewMode)trailingViewMode {
if ([self shouldManuallyEnforceRightToLeftLayoutForOverlayViews] &&
self.mdf_effectiveUserInterfaceLayoutDirection == UIUserInterfaceLayoutDirectionRightToLeft) {
self.leftViewMode = trailingViewMode;
} else {
self.rightViewMode = trailingViewMode;
}
}
- (MDCTextInputUnderlineView *)underline {
return _fundament.underline;
}
- (BOOL)hasTextContent {
return self.text.length > 0;
}
- (void)clearText {
self.text = nil;
}
#pragma mark - UITextField Property Overrides
#if defined(__IPHONE_10_0) && (__IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_10_0)
- (void)setAdjustsFontForContentSizeCategory:(BOOL)adjustsFontForContentSizeCategory {
[super setAdjustsFontForContentSizeCategory:adjustsFontForContentSizeCategory];
[self mdc_setAdjustsFontForContentSizeCategory:adjustsFontForContentSizeCategory];
}
#endif
- (NSAttributedString *)attributedPlaceholder {
return _fundament.attributedPlaceholder;
}
- (void)setAttributedPlaceholder:(NSAttributedString *)attributedPlaceholder {
[super setAttributedPlaceholder:attributedPlaceholder];
_fundament.attributedPlaceholder = attributedPlaceholder;
}
- (void)setBackgroundColor:(UIColor *)backgroundColor {
[super setBackgroundColor:backgroundColor];
self.placeholderLabel.backgroundColor = backgroundColor;
}
- (UITextFieldViewMode)clearButtonMode {
return _fundament.clearButtonMode;
}
- (void)setClearButtonMode:(UITextFieldViewMode)clearButtonMode {
_fundament.clearButtonMode = clearButtonMode;
}
- (void)setFont:(UIFont *)font {
UIFont *previousFont = self.font;
[super setFont:font];
[_fundament didSetFont:previousFont];
}
- (void)setEnabled:(BOOL)enabled {
[super setEnabled:enabled];
_fundament.enabled = enabled;
[[NSNotificationCenter defaultCenter]
postNotificationName:MDCTextInputDidToggleEnabledNotification
object:self];
}
// In iOS 8, .leftView and .rightView are not swapped in RTL so we have to do that manually.
- (UIView *)leadingView {
if ([self shouldManuallyEnforceRightToLeftLayoutForOverlayViews] &&
self.mdf_effectiveUserInterfaceLayoutDirection == UIUserInterfaceLayoutDirectionRightToLeft) {
return self.rightView;
}
return self.leftView;
}
- (NSString *)placeholder {
return self.fundament.placeholder;
}
- (void)setPlaceholder:(NSString *)placeholder {
[super setPlaceholder:placeholder];
[self.fundament setPlaceholder:placeholder];
}
// Note: this is also called by the internals of UITextField when editing ends (iOS 8 to 10).
- (void)setText:(NSString *)text {
[super setText:text];
[_fundament didSetText];
if (!self.isFirstResponder) {
[[NSNotificationCenter defaultCenter]
postNotificationName:MDCTextFieldTextDidSetTextNotification
object:self];
}
}
- (void)setAttributedText:(NSAttributedString *)attributedText {
[super setAttributedText:attributedText];
[_fundament didSetText];
if (!self.isFirstResponder) {
[[NSNotificationCenter defaultCenter]
postNotificationName:MDCTextFieldTextDidSetTextNotification
object:self];
}
}
#pragma mark - UITextField Overrides
// This method doesn't have a positioning delegate mirror per se. But it uses the
// textInsets' value that the positioning delegate can return to inset this text rect.
- (CGRect)textRectForBounds:(CGRect)bounds {
CGRect textRect = bounds;
// Standard textRect calculation
UIEdgeInsets textInsets = self.textInsets;
if (self.mdf_effectiveUserInterfaceLayoutDirection == UIUserInterfaceLayoutDirectionRightToLeft) {
textRect.origin.x += textInsets.right;
} else {
textRect.origin.x += textInsets.left;
}
textRect.size.width -= textInsets.left + textInsets.right;
// Adjustments for .leftView, .rightView
// When in RTL mode, the .rightView is presented using the leftViewRectForBounds frame and the
// .leftView is presented using the rightViewRectForBounds frame.
// To keep things simple, we correct this so .leftView gets the value for leftViewRectForBounds
// and .rightView gets the value for rightViewRectForBounds.
CGFloat leadingViewPadding = 0;
if ([self.positioningDelegate respondsToSelector:@selector(leadingViewTrailingPaddingConstant)]) {
leadingViewPadding = [self.positioningDelegate leadingViewTrailingPaddingConstant];
}
CGFloat trailingViewPadding = 0;
if ([self.positioningDelegate
respondsToSelector:@selector(trailingViewTrailingPaddingConstant)]) {
trailingViewPadding = [self.positioningDelegate trailingViewTrailingPaddingConstant];
}
CGFloat leftViewWidth =
self.mdf_effectiveUserInterfaceLayoutDirection == UIUserInterfaceLayoutDirectionRightToLeft
? CGRectGetWidth([self rightViewRectForBounds:bounds])
: CGRectGetWidth([self leftViewRectForBounds:bounds]);
leftViewWidth +=
self.mdf_effectiveUserInterfaceLayoutDirection == UIUserInterfaceLayoutDirectionRightToLeft
? trailingViewPadding
: leadingViewPadding;
CGFloat rightViewWidth =
self.mdf_effectiveUserInterfaceLayoutDirection == UIUserInterfaceLayoutDirectionRightToLeft
? CGRectGetWidth([self leftViewRectForBounds:bounds])
: CGRectGetWidth([self rightViewRectForBounds:bounds]);
rightViewWidth +=
self.mdf_effectiveUserInterfaceLayoutDirection == UIUserInterfaceLayoutDirectionRightToLeft
? leadingViewPadding
: trailingViewPadding;
if (self.leftView.superview) {
textRect.origin.x += leftViewWidth;
textRect.size.width -= leftViewWidth;
}
if (self.rightView.superview) {
textRect.size.width -= rightViewWidth;
// If there is a rightView, the clearButton will not be shown.
} else {
CGFloat clearButtonWidth = CGRectGetWidth(self.clearButton.bounds);
clearButtonWidth += 2 * MDCTextInputClearButtonImageBuiltInPadding;
// Clear buttons are only shown if there is entered text or programatically set text to clear.
if (self.hasTextContent) {
switch (self.clearButtonMode) {
case UITextFieldViewModeAlways:
case UITextFieldViewModeUnlessEditing:
textRect.size.width -= clearButtonWidth;
break;
default:
break;
}
}
}
// UITextFields have a centerY based layout. And you can change EITHER the height or the Y. Not
// both. Don't know why. So, we have to leave the text rect as big as the bounds and move it to a
// Y that works.
CGFloat actualY =
(CGRectGetHeight(bounds) / 2) - rint(MAX(self.font.lineHeight,
self.placeholderLabel.font.lineHeight) /
2); // Text field or placeholder
actualY = textInsets.top - actualY + MDCTextInputTextRectYCorrection;
textRect.origin.y = actualY;
if (self.mdf_effectiveUserInterfaceLayoutDirection == UIUserInterfaceLayoutDirectionRightToLeft) {
// Now that the text field is laid out as if it were LTR, we can flip it if necessary.
textRect = MDFRectFlippedHorizontally(textRect, CGRectGetWidth(bounds));
}
return textRect;
}
- (CGRect)editingRectForBounds:(CGRect)bounds {
// First the textRect is loaded. Then it's shaved for cursor and/or clear button.
CGRect editingRect = [self textRectForBounds:bounds];
// The textRect comes to us flipped for RTL (if RTL) so we flip it back before adjusting.
if (self.mdf_effectiveUserInterfaceLayoutDirection == UIUserInterfaceLayoutDirectionRightToLeft) {
editingRect = MDFRectFlippedHorizontally(editingRect, CGRectGetWidth(bounds));
}
// UITextFields show EITHER the clear button or the rightView. If the rightView has a superview,
// then it's being shown and the clear button isn't.
if (self.rightView.superview) {
editingRect.size.width += MDCTextInputEditingRectRightViewPaddingCorrection;
} else {
if (self.hasTextContent) {
CGFloat clearButtonWidth = CGRectGetWidth(self.clearButton.bounds);
// The width is adjusted by the padding twice: once for the right side, once for left.
clearButtonWidth += 2 * MDCTextInputClearButtonImageBuiltInPadding;
// The clear button's width is already subtracted from the textRect.width if .always or
// .unlessEditing.
switch (self.clearButtonMode) {
case UITextFieldViewModeUnlessEditing:
editingRect.size.width += clearButtonWidth;
break;
case UITextFieldViewModeWhileEditing:
editingRect.size.width -= clearButtonWidth;
break;
default:
break;
}
}
}
if (self.mdf_effectiveUserInterfaceLayoutDirection == UIUserInterfaceLayoutDirectionRightToLeft) {
editingRect = MDFRectFlippedHorizontally(editingRect, CGRectGetWidth(bounds));
}
if ([self.fundament.positioningDelegate respondsToSelector:@selector(editingRectForBounds:
defaultRect:)]) {
editingRect = [self.fundament.positioningDelegate editingRectForBounds:bounds
defaultRect:editingRect];
}
return editingRect;
}
- (CGRect)clearButtonRectForBounds:(__unused CGRect)bounds {
return self.clearButton.frame;
}
// In RTL, the OS assigns this to the .rightView.
- (CGRect)leftViewRectForBounds:(CGRect)bounds {
CGRect leftViewRect = [super leftViewRectForBounds:bounds];
leftViewRect.origin.y = [self centerYForOverlayViews:CGRectGetHeight(leftViewRect)];
if ((self.mdf_effectiveUserInterfaceLayoutDirection ==
UIUserInterfaceLayoutDirectionRightToLeft) &&
[self.positioningDelegate respondsToSelector:@selector(trailingViewRectForBounds:
defaultRect:)]) {
leftViewRect = [self.positioningDelegate trailingViewRectForBounds:bounds
defaultRect:leftViewRect];
} else if ((self.mdf_effectiveUserInterfaceLayoutDirection ==
UIUserInterfaceLayoutDirectionLeftToRight) &&
[self.positioningDelegate respondsToSelector:@selector(leadingViewRectForBounds:
defaultRect:)]) {
leftViewRect = [self.positioningDelegate leadingViewRectForBounds:bounds
defaultRect:leftViewRect];
}
return leftViewRect;
}
// In RTL, the OS assigns this to the .leftView.
- (CGRect)rightViewRectForBounds:(CGRect)bounds {
CGRect rightViewRect = [super rightViewRectForBounds:bounds];
rightViewRect.origin.y = [self centerYForOverlayViews:CGRectGetHeight(rightViewRect)];
if ((self.mdf_effectiveUserInterfaceLayoutDirection ==
UIUserInterfaceLayoutDirectionRightToLeft) &&
[self.positioningDelegate respondsToSelector:@selector(leadingViewRectForBounds:
defaultRect:)]) {
rightViewRect = [self.positioningDelegate leadingViewRectForBounds:bounds
defaultRect:rightViewRect];
} else if ((self.mdf_effectiveUserInterfaceLayoutDirection ==
UIUserInterfaceLayoutDirectionLeftToRight) &&
[self.positioningDelegate respondsToSelector:@selector(trailingViewRectForBounds:
defaultRect:)]) {
rightViewRect = [self.positioningDelegate trailingViewRectForBounds:bounds
defaultRect:rightViewRect];
}
return rightViewRect;
}
- (CGFloat)centerYForOverlayViews:(CGFloat)heightOfView {
CGFloat centerY =
self.textInsets.top + (self.placeholderLabel.font.lineHeight / 2) - (heightOfView / 2);
return centerY;
}
#pragma mark - UITextField Draw Overrides
- (void)drawPlaceholderInRect:(__unused CGRect)rect {
// We implement our own placeholder that is managed by the fundament. However, to observe normal
// VO placeholder behavior, we still set the placeholder on the UITextField, and need to not draw
// it here.
}
#pragma mark - Layout (Custom)
- (CGFloat)estimatedTextHeight {
CGFloat scale = UIScreen.mainScreen.scale;
CGFloat estimatedTextHeight = ceil(self.font.lineHeight * scale) / scale;
return estimatedTextHeight;
}
#pragma mark - Layout (UIView)
- (CGSize)intrinsicContentSize {
CGSize boundingSize = CGSizeZero;
boundingSize.width = UIViewNoIntrinsicMetric;
boundingSize.height =
[self textInsets].top + [self estimatedTextHeight] + [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)layoutSubviews {
[super layoutSubviews];
[_fundament layoutSubviewsOfInput];
if ([self needsUpdateUnderlinePosition]) {
[self setNeedsUpdateConstraints];
}
[self updateBorder];
[self applyCursorColor];
[self updateInputLayoutStrut];
if ([self.positioningDelegate respondsToSelector:@selector(textInputDidLayoutSubviews)]) {
[self.positioningDelegate textInputDidLayoutSubviews];
}
}
- (void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection {
[super traitCollectionDidChange:previousTraitCollection];
if (self.traitCollectionDidChangeBlock) {
self.traitCollectionDidChangeBlock(self, previousTraitCollection);
}
}
- (CGFloat)mdc_currentElevation {
return 0;
}
- (void)updateConstraints {
[_fundament updateConstraintsOfInput];
[self updateUnderlinePosition];
[super updateConstraints];
if ([self.positioningDelegate respondsToSelector:@selector(textInputDidUpdateConstraints)]) {
[self.positioningDelegate textInputDidUpdateConstraints];
}
}
+ (BOOL)requiresConstraintBasedLayout {
return YES;
}
- (UIView *)viewForFirstBaselineLayout {
return self.inputLayoutStrut;
}
- (UIView *)viewForLastBaselineLayout {
return self.inputLayoutStrut;
}
#pragma mark - UITextField Notification Observation
- (void)textFieldDidBeginEditing:(__unused NSNotification *)note {
[_fundament didBeginEditing];
}
- (void)textFieldDidChange:(__unused NSNotification *)note {
[_fundament didChange];
}
- (void)textFieldDidEndEditing:(__unused NSNotification *)note {
[_fundament didEndEditing];
}
#pragma mark - RTL
// TODO: (larche) remove when we drop iOS 8
// Prior to iOS 9 RTL was not automatically applied, so we need to apply fixes manually.
- (BOOL)shouldManuallyEnforceRightToLeftLayoutForOverlayViews {
NSOperatingSystemVersion iOS9Version = {9, 0, 0};
NSProcessInfo *processInfo = [NSProcessInfo processInfo];
return ![processInfo isOperatingSystemAtLeastVersion:iOS9Version];
}
#pragma mark - Accessibility
- (BOOL)mdc_adjustsFontForContentSizeCategory {
return _fundament.mdc_adjustsFontForContentSizeCategory;
}
// TODO: (larche) remove when we drop iOS 9
- (void)mdc_setAdjustsFontForContentSizeCategory:(BOOL)adjusts {
// Prior to iOS 10 dynamic type was not automatically applied.
if ([super respondsToSelector:@selector(setAdjustsFontForContentSizeCategory:)]) {
[super setAdjustsFontForContentSizeCategory:adjusts];
}
[_fundament mdc_setAdjustsFontForContentSizeCategory:adjusts];
}
/*
Returns a combination of the following:
- The superclass `accessibilityLabel` value
- The placeholder label.
- The leading underline label (if not nil).
- The trailing underline label (if not nil).
*/
- (NSString *)accessibilityLabel {
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;
}
- (NSString *)accessibilityValue {
// If there is no text, return nothing. If there is placeholder text, we don't want it returning
// that as the `accessibilityValue`. Instead, we should only return user-input text.
if (self.text.length > 0) {
return [super accessibilityValue];
}
// Returning nil here causes iOS to default to [super accessibilityValue], which results in both
// accessibilityValue and accessibilityLabel being read out by VoiceOver, so we return the empty
// string instead.
return @"";
}
#pragma mark - Testing
- (void)clearButtonDidTouch {
[_fundament clearButtonDidTouch];
}
@end