blob: e26cd0bb1c9b832c5cac30ea667832d4cfb02976 [file]
/*
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 "MDCTextInputControllerOutlinedTextArea.h"
#import "MDCTextInput.h"
#import "MDCTextInputBorderView.h"
#import "MDCTextInputController.h"
#import "MDCTextInputControllerBase.h"
#import "MDCTextInputControllerFloatingPlaceholder.h"
#import "MDCTextInputUnderlineView.h"
#import "private/MDCTextInputControllerBase+Subclassing.h"
#import "MaterialMath.h"
/**
Note: Right now this is a subclass of MDCTextInputControllerBase since they share a vast
majority of code. If the designs diverge further, this would make a good candidate for its own
class.
*/
#pragma mark - Constants
static const CGFloat MDCTextInputTextFieldOutlinedTextAreaFullPadding = 16.f;
static const CGFloat MDCTextInputTextFieldOutlinedTextAreaHalfPadding = 8.f;
// The guidelines have 8 points of padding but since the fonts on iOS are slightly smaller, we need
// to add points to keep the versions at the same height.
static const CGFloat MDCTextInputTextFieldOutlinedTextAreaPaddingAdjustment = 1.f;
#pragma mark - Class Properties
static UIRectCorner _roundedCornersDefault = UIRectCornerAllCorners;
@interface MDCTextInputControllerOutlinedTextArea ()
@property(nonatomic, strong) NSLayoutConstraint *placeholderTop;
@end
@implementation MDCTextInputControllerOutlinedTextArea
- (instancetype)initWithTextInput:(UIView<MDCTextInput> *)input {
NSAssert([input conformsToProtocol:@protocol(MDCMultilineTextInput)],
@"This design is meant for multi-line text fields only.");
self = [super initWithTextInput:input];
if (self) {
}
return self;
}
#pragma mark - Properties Implementations
- (BOOL)isFloatingEnabled {
return YES;
}
- (void)setFloatingEnabled:(__unused BOOL)floatingEnabled {
// Unused. Floating is always enabled.
}
+ (UIRectCorner)roundedCornersDefault {
return _roundedCornersDefault;
}
+ (void)setRoundedCornersDefault:(UIRectCorner)roundedCornersDefault {
_roundedCornersDefault = roundedCornersDefault;
}
#pragma mark - MDCTextInputPositioningDelegate
// clang-format off
/**
textInsets: is the source of truth for vertical layout. It's used to figure out the proper
height and also where to place the placeholder / text field.
NOTE: It's applied before the textRect is flipped for RTL. So all calculations are done here à la
LTR.
The vertical layout is, at most complex, this form:
MDCTextInputTextFieldOutlinedTextAreaHalfPadding + // Small padding
MDCTextInputTextFieldOutlinedTextAreaPaddingAdjustment // Additional point (iOS specific)
placeholderEstimatedHeight // Height of placeholder
MDCTextInputTextFieldOutlinedTextAreaHalfPadding + // Small padding
MDCTextInputTextFieldOutlinedTextAreaPaddingAdjustment // Additional point (iOS specific)
MDCCeil(MAX(self.textInput.font.lineHeight, // Text field or placeholder
self.textInput.placeholderLabel.font.lineHeight))
underlineOffset // Small Padding +
// underlineLabelsOffset From super class.
*/
// clang-format on
- (UIEdgeInsets)textInsets:(UIEdgeInsets)defaultInsets {
UIEdgeInsets textInsets = [super textInsets:defaultInsets];
textInsets.top = MDCTextInputTextFieldOutlinedTextAreaHalfPadding +
MDCTextInputTextFieldOutlinedTextAreaPaddingAdjustment +
MDCRint(self.textInput.placeholderLabel.font.lineHeight *
(CGFloat)self.floatingPlaceholderScale.floatValue) +
MDCTextInputTextFieldOutlinedTextAreaHalfPadding +
MDCTextInputTextFieldOutlinedTextAreaPaddingAdjustment;
// .bottom = underlineOffset + the half padding above the line but below the text field and any
// space needed for the labels and / or line.
// Legacy has an additional half padding here but this version does not.
CGFloat underlineOffset = [self underlineOffset];
textInsets.bottom = underlineOffset;
textInsets.left = MDCTextInputTextFieldOutlinedTextAreaFullPadding;
textInsets.right = MDCTextInputTextFieldOutlinedTextAreaFullPadding;
return textInsets;
}
#pragma mark - Layout
- (void)updateBorder {
[super updateBorder];
UIColor *borderColor = self.textInput.isEditing ? self.activeColor : self.normalColor;
self.textInput.borderView.borderStrokeColor =
(self.isDisplayingCharacterCountError || self.isDisplayingErrorText) ? self.errorColor
: borderColor;
self.textInput.borderView.borderPath.lineWidth = self.textInput.isEditing ? 2 : 1;
}
- (void)updateLayout {
[super updateLayout];
if (!self.textInput) {
return;
}
NSAssert([self.textInput conformsToProtocol:@protocol(MDCMultilineTextInput)],
@"This design is meant for multi-line text fields only.");
if (![self.textInput conformsToProtocol:@protocol(MDCMultilineTextInput)]) {
return;
}
((UIView<MDCMultilineTextInput> *)self.textInput).expandsOnOverflow = NO;
((UIView<MDCMultilineTextInput> *)self.textInput).minimumLines = 5;
self.textInput.underline.alpha = 0;
if (!self.placeholderTop) {
self.placeholderTop =
[NSLayoutConstraint constraintWithItem:self.textInput.placeholderLabel
attribute:NSLayoutAttributeTop
relatedBy:NSLayoutRelationEqual
toItem:self.textInput
attribute:NSLayoutAttributeTop
multiplier:1
constant:MDCTextInputTextFieldOutlinedTextAreaFullPadding];
self.placeholderTop.priority = UILayoutPriorityDefaultHigh;
self.placeholderTop.active = YES;
}
}
// The measurement from bottom to underline center Y.
- (CGFloat)underlineOffset {
// The amount of space underneath the underline depends on whether there is content in the
// underline labels.
CGFloat underlineLabelsOffset = 0;
CGFloat scale = UIScreen.mainScreen.scale;
if (self.textInput.leadingUnderlineLabel.text.length) {
underlineLabelsOffset =
MDCCeil(self.textInput.leadingUnderlineLabel.font.lineHeight * scale) / scale;
}
if (self.textInput.trailingUnderlineLabel.text.length || self.characterCountMax) {
underlineLabelsOffset =
MAX(underlineLabelsOffset,
MDCCeil(self.textInput.trailingUnderlineLabel.font.lineHeight * scale) / scale);
}
CGFloat underlineOffset = underlineLabelsOffset;
underlineOffset += MDCTextInputTextFieldOutlinedTextAreaHalfPadding;
if (!MDCCGFloatEqual(underlineLabelsOffset, 0)) {
underlineOffset += MDCTextInputTextFieldOutlinedTextAreaHalfPadding;
}
return underlineOffset;
}
@end