blob: f824287536ece6c310a7f7aaefb6c918c067321c [file] [log] [blame]
// Copyright 2020-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 "MDCBaseTextFieldLayout.h"
#import "MaterialTextControlsPrivate+Shared.h"
#import "MDCTextControlTextFieldSideViewAlignment.h"
@interface MDCBaseTextFieldLayout ()
@end
@implementation MDCBaseTextFieldLayout
#pragma mark Object Lifecycle
- (instancetype)initWithTextFieldSize:(CGSize)textFieldSize
positioningReference:
(id<MDCTextControlVerticalPositioningReference>)positioningReference
horizontalPositioningReference:
(id<MDCTextControlHorizontalPositioning>)horizontalPositioningReference
text:(NSString *)text
font:(UIFont *)font
floatingFont:(UIFont *)floatingFont
label:(UILabel *)label
labelPosition:(MDCTextControlLabelPosition)labelPosition
labelBehavior:(MDCTextControlLabelBehavior)labelBehavior
sideViewAlignment:(MDCTextControlTextFieldSideViewAlignment)sideViewAlignment
leadingView:(UIView *)leadingView
leadingViewMode:(UITextFieldViewMode)leadingViewMode
trailingView:(UIView *)trailingView
trailingViewMode:(UITextFieldViewMode)trailingViewMode
clearButtonSideLength:(CGFloat)clearButtonSideLength
clearButtonMode:(UITextFieldViewMode)clearButtonMode
leadingAssistiveLabel:(nonnull UILabel *)leadingAssistiveLabel
trailingAssistiveLabel:(nonnull UILabel *)trailingAssistiveLabel
assistiveLabelDrawPriority:
(MDCTextControlAssistiveLabelDrawPriority)assistiveLabelDrawPriority
customAssistiveLabelDrawPriority:(CGFloat)customAssistiveLabelDrawPriority
isRTL:(BOOL)isRTL
isEditing:(BOOL)isEditing {
self = [super init];
if (self) {
[self calculateLayoutWithTextFieldSize:textFieldSize
positioningReference:positioningReference
horizontalPositioningReference:horizontalPositioningReference
text:text
font:font
floatingFont:floatingFont
label:label
labelPosition:labelPosition
labelBehavior:labelBehavior
sideViewAlignment:sideViewAlignment
leadingView:leadingView
leadingViewMode:leadingViewMode
trailingView:trailingView
trailingViewMode:trailingViewMode
clearButtonSideLength:clearButtonSideLength
clearButtonMode:clearButtonMode
leadingAssistiveLabel:leadingAssistiveLabel
trailingAssistiveLabel:trailingAssistiveLabel
assistiveLabelDrawPriority:assistiveLabelDrawPriority
customAssistiveLabelDrawPriority:customAssistiveLabelDrawPriority
isRTL:isRTL
isEditing:isEditing];
return self;
}
return nil;
}
#pragma mark Layout Calculation
- (void)calculateLayoutWithTextFieldSize:(CGSize)textFieldSize
positioningReference:
(id<MDCTextControlVerticalPositioningReference>)positioningReference
horizontalPositioningReference:
(id<MDCTextControlHorizontalPositioning>)horizontalPositioningReference
text:(NSString *)text
font:(UIFont *)font
floatingFont:(UIFont *)floatingFont
label:(UILabel *)label
labelPosition:(MDCTextControlLabelPosition)labelPosition
labelBehavior:(MDCTextControlLabelBehavior)labelBehavior
sideViewAlignment:(MDCTextControlTextFieldSideViewAlignment)sideViewAlignment
leadingView:(UIView *)leadingView
leadingViewMode:(UITextFieldViewMode)leadingViewMode
trailingView:(UIView *)trailingView
trailingViewMode:(UITextFieldViewMode)trailingViewMode
clearButtonSideLength:(CGFloat)clearButtonSideLength
clearButtonMode:(UITextFieldViewMode)clearButtonMode
leadingAssistiveLabel:(nonnull UILabel *)leadingAssistiveLabel
trailingAssistiveLabel:(nonnull UILabel *)trailingAssistiveLabel
assistiveLabelDrawPriority:
(MDCTextControlAssistiveLabelDrawPriority)assistiveLabelDrawPriority
customAssistiveLabelDrawPriority:(CGFloat)customAssistiveLabelDrawPriority
isRTL:(BOOL)isRTL
isEditing:(BOOL)isEditing {
UIView *leftView = isRTL ? trailingView : leadingView;
UIView *rightView = isRTL ? leadingView : trailingView;
UITextFieldViewMode leftViewMode = isRTL ? trailingViewMode : leadingViewMode;
UITextFieldViewMode rightViewMode = isRTL ? leadingViewMode : trailingViewMode;
BOOL displaysLeftView =
MDCTextControlShouldDisplaySideViewWithSideView(leftView, leftViewMode, isEditing);
BOOL displaysRightView =
MDCTextControlShouldDisplaySideViewWithSideView(rightView, rightViewMode, isEditing);
BOOL displaysClearButton = [self shouldDisplayClearButtonWithViewMode:clearButtonMode
isEditing:isEditing
text:text];
CGFloat leadingEdgePadding = horizontalPositioningReference.leadingEdgePadding;
CGFloat trailingEdgePadding = horizontalPositioningReference.trailingEdgePadding;
CGFloat leftEdgePadding = isRTL ? trailingEdgePadding : leadingEdgePadding;
CGFloat rightEdgePadding = isRTL ? leadingEdgePadding : trailingEdgePadding;
CGFloat horizontalInterItemPadding = horizontalPositioningReference.horizontalInterItemSpacing;
CGFloat leftViewWidth = CGRectGetWidth(leftView.frame);
CGFloat leftViewMinX = 0;
CGFloat leftViewMaxX = 0;
if (displaysLeftView) {
leftViewMinX = leftEdgePadding;
leftViewMaxX = leftViewMinX + leftViewWidth;
}
CGFloat textFieldWidth = textFieldSize.width;
CGFloat rightViewMinX = 0;
if (displaysRightView) {
CGFloat rightViewMaxX = textFieldWidth - rightEdgePadding;
rightViewMinX = rightViewMaxX - CGRectGetWidth(rightView.frame);
}
CGFloat clearButtonMinX = 0;
if (isRTL) {
clearButtonMinX =
displaysLeftView ? leftViewMaxX + horizontalInterItemPadding : leftEdgePadding;
} else {
CGFloat clearButtonMaxX = displaysRightView ? rightViewMinX - horizontalInterItemPadding
: textFieldWidth - rightEdgePadding;
clearButtonMinX = clearButtonMaxX - clearButtonSideLength;
}
CGFloat textRectMinX = 0;
CGFloat textRectMaxX = 0;
if (isRTL) {
if (displaysClearButton) {
CGFloat clearButtonMaxX = clearButtonMinX + clearButtonSideLength;
textRectMinX = clearButtonMaxX + horizontalInterItemPadding;
} else {
textRectMinX = displaysLeftView ? leftViewMaxX + horizontalInterItemPadding : leftEdgePadding;
}
if (displaysRightView) {
textRectMaxX = rightViewMinX - horizontalInterItemPadding;
} else {
textRectMaxX = textFieldWidth - rightEdgePadding;
}
} else {
textRectMinX = displaysLeftView ? leftViewMaxX + horizontalInterItemPadding : leftEdgePadding;
if (displaysClearButton) {
textRectMaxX = clearButtonMinX - horizontalInterItemPadding;
} else {
textRectMaxX = displaysRightView ? rightViewMinX - horizontalInterItemPadding
: textFieldWidth - rightEdgePadding;
}
}
BOOL shouldLayoutForFloatingLabel = MDCTextControlShouldLayoutForFloatingLabelWithLabelPosition(
labelPosition, labelBehavior, label.text);
CGFloat textRectMinYNormal = 0;
if (shouldLayoutForFloatingLabel) {
textRectMinYNormal = positioningReference.paddingBetweenContainerTopAndNormalLabel;
} else {
textRectMinYNormal = positioningReference.paddingAroundTextWhenNoFloatingLabel;
}
CGFloat textRectWidth = textRectMaxX - textRectMinX;
CGFloat textRectHeight = [self textHeightWithFont:font];
CGRect textRectNormal =
CGRectMake(textRectMinX, textRectMinYNormal, textRectWidth, textRectHeight);
CGFloat floatingLabelMinY = positioningReference.paddingBetweenContainerTopAndFloatingLabel;
CGFloat floatingLabelHeight = floatingFont.lineHeight;
CGFloat floatingLabelMaxY = floatingLabelMinY + floatingLabelHeight;
CGFloat textRectMinYWithFloatingLabel =
floatingLabelMaxY + positioningReference.paddingBetweenFloatingLabelAndEditingText;
CGFloat textRectCenterYWithFloatingLabel =
textRectMinYWithFloatingLabel + ((CGFloat)0.5 * textRectHeight);
CGFloat textRectMinYFloatingLabel =
(CGFloat)floor((double)(textRectCenterYWithFloatingLabel - (textRectHeight * (CGFloat)0.5)));
CGRect textRectFloating =
CGRectMake(textRectMinX, textRectMinYFloatingLabel, textRectWidth, textRectHeight);
CGFloat containerHeight = shouldLayoutForFloatingLabel
? positioningReference.containerHeightWithFloatingLabel
: positioningReference.containerHeightWithoutFloatingLabel;
CGFloat containerMidY = (CGFloat)0.5 * containerHeight;
BOOL isFloatingLabel = labelPosition == MDCTextControlLabelPositionFloating;
CGFloat textRectMidY =
isFloatingLabel ? CGRectGetMidY(textRectFloating) : CGRectGetMidY(textRectNormal);
CGFloat sideViewMidY = [self sideViewMidYWithSideViewAlignment:sideViewAlignment
containerMidY:containerMidY
textRectMidY:textRectMidY];
CGFloat leftViewHeight = CGRectGetHeight(leftView.frame);
CGFloat rightViewHeight = CGRectGetHeight(rightView.frame);
CGFloat leftViewMinY = [self minYForSubviewWithHeight:leftViewHeight centerY:sideViewMidY];
CGFloat rightViewMinY = [self minYForSubviewWithHeight:rightViewHeight centerY:sideViewMidY];
CGRect leftViewFrame = CGRectMake(leftViewMinX, leftViewMinY, leftViewWidth, leftViewHeight);
CGRect rightViewFrame =
CGRectMake(rightViewMinX, rightViewMinY, CGRectGetWidth(rightView.frame), rightViewHeight);
CGFloat clearButtonMinY = [self minYForSubviewWithHeight:clearButtonSideLength
centerY:sideViewMidY];
CGRect clearButtonFrame =
CGRectMake(clearButtonMinX, clearButtonMinY, clearButtonSideLength, clearButtonSideLength);
CGSize labelSizeNormal = MDCTextControlLabelSizeWith(label.text, textRectWidth, font);
CGSize labelSizeFloating = MDCTextControlLabelSizeWith(label.text, textRectWidth, floatingFont);
BOOL normalLabelWillTruncate = labelSizeNormal.height > font.lineHeight;
BOOL floatingLabelWillTruncate = labelSizeFloating.height > floatingFont.lineHeight;
if (normalLabelWillTruncate) {
labelSizeNormal.height = font.lineHeight;
labelSizeNormal.width = textRectWidth;
}
if (floatingLabelWillTruncate) {
labelSizeFloating.height = floatingFont.lineHeight;
labelSizeFloating.width = textRectWidth;
}
self.labelTruncationIsPresent = normalLabelWillTruncate || floatingLabelWillTruncate;
CGRect labelFrameNormal = [self labelFrameWithLabelSize:labelSizeNormal
labelPosition:MDCTextControlLabelPositionNormal
floatingLabelMinY:floatingLabelMinY
textRect:textRectNormal
isRTL:isRTL];
CGRect labelFrameFloating = [self labelFrameWithLabelSize:labelSizeFloating
labelPosition:MDCTextControlLabelPositionFloating
floatingLabelMinY:floatingLabelMinY
textRect:textRectNormal
isRTL:isRTL];
self.assistiveLabelViewLayout = [[MDCTextControlAssistiveLabelViewLayout alloc]
initWithWidth:textFieldWidth
leadingAssistiveLabel:leadingAssistiveLabel
trailingAssistiveLabel:trailingAssistiveLabel
assistiveLabelDrawPriority:assistiveLabelDrawPriority
customAssistiveLabelDrawPriority:customAssistiveLabelDrawPriority
leadingEdgePadding:leadingEdgePadding
trailingEdgePadding:trailingEdgePadding
paddingAboveAssistiveLabels:positioningReference.paddingAboveAssistiveLabels
paddingBelowAssistiveLabels:positioningReference.paddingBelowAssistiveLabels
isRTL:isRTL];
self.assistiveLabelViewFrame = CGRectMake(0, containerHeight, textFieldWidth,
self.assistiveLabelViewLayout.calculatedHeight);
self.leadingViewFrame = isRTL ? rightViewFrame : leftViewFrame;
self.trailingViewFrame = isRTL ? leftViewFrame : rightViewFrame;
self.displaysLeadingView = isRTL ? displaysRightView : displaysLeftView;
self.displaysTrailingView = isRTL ? displaysLeftView : displaysRightView;
self.clearButtonFrame = clearButtonFrame;
self.textRectFloating = textRectFloating;
self.textRectNormal = textRectNormal;
self.labelFrameFloating = labelFrameFloating;
self.labelFrameNormal = labelFrameNormal;
self.containerHeight = containerHeight;
}
- (CGFloat)sideViewMidYWithSideViewAlignment:
(MDCTextControlTextFieldSideViewAlignment)sideViewAlignment
containerMidY:(CGFloat)containerMidY
textRectMidY:(CGFloat)textRectMidY {
CGFloat sideViewMidY = containerMidY;
switch (sideViewAlignment) {
case MDCTextControlTextFieldSideViewAlignmentCenteredInContainer:
break;
case MDCTextControlTextFieldSideViewAlignmentAlignedWithText:
sideViewMidY = textRectMidY;
break;
default:
break;
}
return sideViewMidY;
}
- (CGFloat)minYForSubviewWithHeight:(CGFloat)height centerY:(CGFloat)centerY {
return (CGFloat)round((double)(centerY - ((CGFloat)0.5 * height)));
}
- (BOOL)shouldDisplayClearButtonWithViewMode:(UITextFieldViewMode)viewMode
isEditing:(BOOL)isEditing
text:(NSString *)text {
BOOL hasText = text.length > 0;
switch (viewMode) {
case UITextFieldViewModeWhileEditing:
return isEditing && hasText;
case UITextFieldViewModeUnlessEditing:
return !isEditing;
case UITextFieldViewModeAlways:
return YES;
case UITextFieldViewModeNever:
return NO;
default:
return NO;
}
}
- (CGRect)labelFrameWithLabelSize:(CGSize)size
labelPosition:(MDCTextControlLabelPosition)labelPosition
floatingLabelMinY:(CGFloat)floatingLabelMinY
textRect:(CGRect)textRect
isRTL:(BOOL)isRTL {
CGFloat labelMinX = CGRectGetMinX(textRect);
CGFloat labelMaxX = CGRectGetMaxX(textRect);
CGFloat textRectMidY = CGRectGetMidY(textRect);
CGRect rect = CGRectZero;
CGFloat originX = 0;
CGFloat originY = 0;
switch (labelPosition) {
case MDCTextControlLabelPositionNone:
break;
case MDCTextControlLabelPositionFloating:
originY = floatingLabelMinY;
if (isRTL) {
originX = labelMaxX - size.width;
} else {
originX = labelMinX;
}
rect = CGRectMake(originX, originY, size.width, size.height);
break;
case MDCTextControlLabelPositionNormal:
originY = textRectMidY - (0.5f * size.height);
if (isRTL) {
originX = labelMaxX - size.width;
} else {
originX = labelMinX;
}
rect = CGRectMake(originX, originY, size.width, size.height);
break;
default:
break;
}
return rect;
}
- (CGFloat)textHeightWithFont:(UIFont *)font {
return (CGFloat)ceil((double)font.lineHeight);
}
- (CGFloat)calculatedHeight {
CGFloat maxY = self.containerHeight;
CGFloat assistiveLabelViewMaxY = CGRectGetMaxY(self.assistiveLabelViewFrame);
if (assistiveLabelViewMaxY > maxY) {
maxY = assistiveLabelViewMaxY;
}
return ceil(maxY);
}
- (CGRect)labelFrameWithLabelPosition:(MDCTextControlLabelPosition)labelPosition {
if (labelPosition == MDCTextControlLabelPositionFloating) {
return self.labelFrameFloating;
} else if (labelPosition == MDCTextControlLabelPositionNormal) {
return self.labelFrameNormal;
} else {
return CGRectZero;
}
}
@end