material-components_materia.../components/TextFields/src/MDCTextInputControllerOutlined.m
Will Larche 1f10ae3e3e
[TextFields] Making leading and trailing view layout customizeable (#4434)
Closes the layout part of b/74174981

***Breaks screenshot tests.

Before:
![simulator screen shot - iphone x - 2018-06-24 at 17 39 27](https://user-images.githubusercontent.com/1271525/41823956-2742affe-77d6-11e8-9082-2ccb10071750.png)

After:
![simulator screen shot - iphone x - 2018-06-24 at 17 35 45](https://user-images.githubusercontent.com/1271525/41823875-13f44152-77d5-11e8-8ae8-f23840efe6e1.png)

Also corrects the x of the floating placeholder on a filled text field.
![simulator screen shot - iphone x - 2018-06-24 at 17 45 43](https://user-images.githubusercontent.com/1271525/41823974-7e8c5346-77d6-11e8-88ce-38f4adb32323.png)
2018-06-25 07:59:33 -07:00

313 lines
13 KiB
Objective-C

/*
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 "MDCTextInputControllerOutlined.h"
#import <MDFInternationalization/MDFInternationalization.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"
#pragma mark - Class Properties
static const CGFloat MDCTextInputOutlinedTextFieldFloatingPlaceholderPadding = 8.f;
static const CGFloat MDCTextInputOutlinedTextFieldFullPadding = 16.f;
static const CGFloat MDCTextInputOutlinedTextFieldNormalPlaceholderPadding = 20.f;
static const CGFloat MDCTextInputOutlinedTextFieldThreeQuartersPadding = 12.f;
static UIRectCorner _roundedCornersDefault = UIRectCornerAllCorners;
@interface MDCTextInputControllerOutlined ()
@property(nonatomic, strong) NSLayoutConstraint *placeholderCenterY;
@property(nonatomic, strong) NSLayoutConstraint *placeholderLeading;
@end
@implementation MDCTextInputControllerOutlined
- (instancetype)initWithTextInput:(UIView<MDCTextInput> *)input {
NSAssert(![input conformsToProtocol:@protocol(MDCMultilineTextInput)],
@"This design is meant for single-line text fields only. For a complementary multi-line "
@"style, see MDCTextInputControllerOutlinedTextArea.");
self = [super initWithTextInput:input];
if (self) {
input.textInsetsMode = MDCTextInputTextInsetsModeAlways;
}
return self;
}
#pragma mark - Properties Implementations
- (BOOL)isFloatingEnabled {
return YES;
}
- (void)setFloatingEnabled:(__unused BOOL)floatingEnabled {
// Unused. Floating is always enabled.
}
- (UIOffset)floatingPlaceholderOffset {
UIOffset offset = [super floatingPlaceholderOffset];
CGFloat textVerticalOffset = 0;
offset.vertical = textVerticalOffset;
return offset;
}
+ (UIRectCorner)roundedCornersDefault {
return _roundedCornersDefault;
}
+ (void)setRoundedCornersDefault:(UIRectCorner)roundedCornersDefault {
_roundedCornersDefault = roundedCornersDefault;
}
#pragma mark - MDCTextInputPositioningDelegate
- (CGRect)leadingViewRectForBounds:(CGRect)bounds defaultRect:(CGRect)defaultRect {
CGRect leadingViewRect = defaultRect;
CGFloat xOffset = (self.textInput.mdf_effectiveUserInterfaceLayoutDirection ==
UIUserInterfaceLayoutDirectionRightToLeft)
? -1 * MDCTextInputOutlinedTextFieldFullPadding
: MDCTextInputOutlinedTextFieldFullPadding;
leadingViewRect = CGRectOffset(leadingViewRect, xOffset, 0.f);
CGRect borderRect = [self borderRect];
leadingViewRect.origin.y = CGRectGetMinY(borderRect) + CGRectGetHeight(borderRect) / 2.f -
CGRectGetHeight(leadingViewRect) / 2.f;
return leadingViewRect;
}
- (CGFloat)leadingViewTrailingPaddingConstant {
return MDCTextInputOutlinedTextFieldFullPadding;
}
- (CGRect)trailingViewRectForBounds:(CGRect)bounds defaultRect:(CGRect)defaultRect {
CGRect trailingViewRect = defaultRect;
CGFloat xOffset = (self.textInput.mdf_effectiveUserInterfaceLayoutDirection ==
UIUserInterfaceLayoutDirectionRightToLeft)
? MDCTextInputOutlinedTextFieldThreeQuartersPadding
: -1 * MDCTextInputOutlinedTextFieldThreeQuartersPadding;
trailingViewRect = CGRectOffset(trailingViewRect, xOffset, 0.f);
CGRect borderRect = [self borderRect];
trailingViewRect.origin.y = CGRectGetMinY(borderRect) + CGRectGetHeight(borderRect) / 2.f -
CGRectGetHeight(trailingViewRect) / 2.f;
return trailingViewRect;
}
- (CGFloat)trailingViewTrailingPaddingConstant {
return MDCTextInputOutlinedTextFieldThreeQuartersPadding;
}
// 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.
This one is a little different because the placeholder crosses the top bordered area when floating.
The vertical layout is, at most complex, this form:
placeholderEstimatedHeight // Height of placeholder
MDCTextInputOutlinedTextFieldFullPadding // Padding
MDCCeil(MAX(self.textInput.font.lineHeight, // Text field or placeholder
self.textInput.placeholderLabel.font.lineHeight))
MDCTextInputControllerBaseDefaultPadding // Padding to bottom of border rect
underlineLabelsOffset // From super class.
*/
// clang-format on
- (UIEdgeInsets)textInsets:(UIEdgeInsets)defaultInsets {
UIEdgeInsets textInsets = [super textInsets:defaultInsets];
CGFloat textVerticalOffset = self.textInput.placeholderLabel.font.lineHeight * .5f;
CGFloat scale = UIScreen.mainScreen.scale;
CGFloat placeholderEstimatedHeight =
MDCCeil(self.textInput.placeholderLabel.font.lineHeight * scale) / scale;
textInsets.top = [self borderHeight] - MDCTextInputOutlinedTextFieldFullPadding -
placeholderEstimatedHeight + textVerticalOffset;
textInsets.left = MDCTextInputOutlinedTextFieldFullPadding;
textInsets.right = MDCTextInputOutlinedTextFieldFullPadding;
return textInsets;
}
#pragma mark - MDCTextInputControllerBase overrides
- (void)updateLayout {
[super updateLayout];
self.textInput.clipsToBounds = NO;
}
- (void)updateBorder {
[super updateBorder];
UIBezierPath *path;
if ([self isPlaceholderUp]) {
CGFloat placeholderWidth =
[self.textInput.placeholderLabel systemLayoutSizeFittingSize:UILayoutFittingCompressedSize]
.width * (CGFloat)self.floatingPlaceholderScale.floatValue;
placeholderWidth += MDCTextInputOutlinedTextFieldFloatingPlaceholderPadding;
path =
[self roundedPathFromRect:[self borderRect]
withTextSpace:placeholderWidth
leftOffset:MDCTextInputOutlinedTextFieldFullPadding -
MDCTextInputOutlinedTextFieldFloatingPlaceholderPadding / 2.0f];
} else {
CGSize cornerRadius = CGSizeMake(MDCTextInputControllerBaseDefaultBorderRadius,
MDCTextInputControllerBaseDefaultBorderRadius);
path = [UIBezierPath bezierPathWithRoundedRect:[self borderRect]
byRoundingCorners:self.roundedCorners
cornerRadii:cornerRadius];
}
self.textInput.borderPath = path;
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;
[self updatePlaceholder];
}
- (CGRect)borderRect {
CGRect pathRect = self.textInput.bounds;
pathRect.origin.y = pathRect.origin.y + self.textInput.placeholderLabel.font.lineHeight * .5f;
pathRect.size.height = [self borderHeight];
return pathRect;
}
- (UIBezierPath *)roundedPathFromRect:(CGRect)f
withTextSpace:(CGFloat)textSpace
leftOffset:(CGFloat)offset {
UIBezierPath *path = [[UIBezierPath alloc] init];
CGFloat radius = MDCTextInputControllerBaseDefaultBorderRadius;
CGFloat yOffset = f.origin.y;
CGFloat xOffset = f.origin.x;
// Draw the path
[path moveToPoint:CGPointMake(radius + xOffset, yOffset)];
[path addLineToPoint:CGPointMake(offset + xOffset, yOffset)];
[path moveToPoint:CGPointMake(textSpace + offset + xOffset, yOffset)];
[path addLineToPoint:CGPointMake(f.size.width - radius + xOffset, yOffset)];
[path addArcWithCenter:CGPointMake(f.size.width - radius + xOffset, radius + yOffset)
radius:radius
startAngle:- (CGFloat)(M_PI / 2)
endAngle:0
clockwise:YES];
[path addLineToPoint:CGPointMake(f.size.width + xOffset, f.size.height - radius + yOffset)];
[path addArcWithCenter:CGPointMake(f.size.width - radius + xOffset, f.size.height - radius + yOffset)
radius:radius
startAngle:0
endAngle:- (CGFloat)((M_PI * 3) / 2)
clockwise:YES];
[path addLineToPoint:CGPointMake(radius + xOffset, f.size.height + yOffset)];
[path addArcWithCenter:CGPointMake(radius + xOffset, f.size.height - radius + yOffset)
radius:radius
startAngle:- (CGFloat)((M_PI * 3) / 2)
endAngle:- (CGFloat)M_PI
clockwise:YES];
[path addLineToPoint:CGPointMake(xOffset, radius + yOffset)];
[path addArcWithCenter:CGPointMake(radius + xOffset, radius + yOffset)
radius:radius
startAngle:- (CGFloat)M_PI
endAngle:- (CGFloat)(M_PI / 2)
clockwise:YES];
return path;
}
- (void)updatePlaceholder {
[super updatePlaceholder];
CGFloat scale = UIScreen.mainScreen.scale;
CGFloat placeholderEstimatedHeight =
MDCCeil(self.textInput.placeholderLabel.font.lineHeight * scale) / scale;
CGFloat placeholderConstant =
([self borderHeight] / 2.f) - (placeholderEstimatedHeight / 2.f)
+ self.textInput.placeholderLabel.font.lineHeight * .5f;
if (!self.placeholderCenterY) {
self.placeholderCenterY = [NSLayoutConstraint constraintWithItem:self.textInput.placeholderLabel
attribute:NSLayoutAttributeTop
relatedBy:NSLayoutRelationEqual
toItem:self.textInput
attribute:NSLayoutAttributeTop
multiplier:1
constant:placeholderConstant];
self.placeholderCenterY.priority = UILayoutPriorityDefaultHigh;
self.placeholderCenterY.active = YES;
[self.textInput.placeholderLabel setContentHuggingPriority:UILayoutPriorityDefaultHigh + 1
forAxis:UILayoutConstraintAxisVertical];
}
self.placeholderCenterY.constant = placeholderConstant;
CGFloat placeholderLeadingConstant = MDCTextInputOutlinedTextFieldFullPadding;
if ([self.textInput conformsToProtocol:@protocol(MDCLeadingViewTextInput)]) {
UIView<MDCLeadingViewTextInput> *leadingViewInput =
(UIView<MDCLeadingViewTextInput> *)self.textInput;
if (leadingViewInput.leadingView.superview) {
placeholderLeadingConstant += CGRectGetWidth(leadingViewInput.leadingView.frame) +
[self leadingViewTrailingPaddingConstant];
}
}
if (!self.placeholderLeading) {
self.placeholderLeading = [NSLayoutConstraint constraintWithItem:self.textInput.placeholderLabel
attribute:NSLayoutAttributeLeading
relatedBy:NSLayoutRelationEqual
toItem:self.textInput
attribute:NSLayoutAttributeLeading
multiplier:1
constant:placeholderLeadingConstant];
self.placeholderLeading.priority = UILayoutPriorityDefaultHigh;
self.placeholderLeading.active = YES;
}
self.placeholderLeading.constant = placeholderLeadingConstant;
}
- (CGFloat)borderHeight {
CGFloat scale = UIScreen.mainScreen.scale;
CGFloat placeholderEstimatedHeight =
MDCCeil(self.textInput.placeholderLabel.font.lineHeight * scale) / scale;
return MDCTextInputOutlinedTextFieldNormalPlaceholderPadding + placeholderEstimatedHeight +
MDCTextInputOutlinedTextFieldNormalPlaceholderPadding;
}
@end