blob: 70f67bee3bd67dc683599b0d08f8e0bb522c8ca1 [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 "MDCBaseTextAreaLayout.h"
#import "MDCTextControlAssistiveLabelViewLayout.h"
#import "MDCTextControlHorizontalPositioning.h"
#import "MDCTextControlLabelSupport.h"
#import "MDCTextControlSideViewSupport.h"
#import "MDCTextControlVerticalPositioningReference.h"
static const CGFloat kGradientBlurLength = (CGFloat)4.0;
@interface MDCBaseTextAreaLayout ()
@property(nonatomic, assign) CGRect labelFrameFloating;
@property(nonatomic, assign) CGRect labelFrameNormal;
@property(nonatomic, assign) CGRect textViewFrame;
@property(nonatomic, assign) CGRect assistiveLabelViewFrame;
@property(nonatomic, strong, nonnull)
MDCTextControlAssistiveLabelViewLayout *assistiveLabelViewLayout;
@property(nonatomic) CGFloat containerHeight;
@property(nonatomic, strong, nonnull) NSArray<NSNumber *> *verticalGradientLocations;
@property(nonatomic, strong, nonnull) NSArray<NSNumber *> *horizontalGradientLocations;
@property(nonatomic, assign) BOOL placeholderLabelHidden;
@property(nonatomic, assign) CGRect placeholderLabelFrame;
@end
@implementation MDCBaseTextAreaLayout
- (nonnull instancetype)initWithSize:(CGSize)size
verticalPositioningReference:
(nonnull id<MDCTextControlVerticalPositioningReference>)verticalPositioningReference
horizontalPositioningReference:
(nonnull id<MDCTextControlHorizontalPositioning>)horizontalPositioningReference
text:(nullable NSString *)text
font:(nonnull UIFont *)font
floatingFont:(nonnull UIFont *)floatingFont
label:(nonnull UILabel *)label
labelPosition:(MDCTextControlLabelPosition)labelPosition
labelBehavior:(MDCTextControlLabelBehavior)labelBehavior
placeholderLabel:(nonnull UILabel *)placeholderLabel
leadingView:(nullable UIView *)leadingView
leadingViewMode:(UITextFieldViewMode)leadingViewMode
trailingView:(nullable UIView *)trailingView
trailingViewMode:(UITextFieldViewMode)trailingViewMode
leadingAssistiveLabel:(UILabel *)leadingAssistiveLabel
trailingAssistiveLabel:(UILabel *)trailingAssistiveLabel
assistiveLabelDrawPriority:
(MDCTextControlAssistiveLabelDrawPriority)assistiveLabelDrawPriority
customAssistiveLabelDrawPriority:(CGFloat)normalizedCustomAssistiveLabelDrawPriority
isRTL:(BOOL)isRTL
isEditing:(BOOL)isEditing {
self = [super init];
if (self) {
[self calculateLayoutWithSize:size
verticalPositioningReference:verticalPositioningReference
horizontalPositioningReference:horizontalPositioningReference
text:text
font:font
floatingFont:floatingFont
label:label
labelPosition:labelPosition
labelBehavior:labelBehavior
placeholderLabel:placeholderLabel
leadingView:leadingView
leadingViewMode:leadingViewMode
trailingView:trailingView
trailingViewMode:trailingViewMode
leadingAssistiveLabel:leadingAssistiveLabel
trailingAssistiveLabel:trailingAssistiveLabel
assistiveLabelDrawPriority:assistiveLabelDrawPriority
customAssistiveLabelDrawPriority:normalizedCustomAssistiveLabelDrawPriority
isRTL:isRTL
isEditing:isEditing];
}
return self;
}
- (void)calculateLayoutWithSize:(CGSize)size
verticalPositioningReference:
(nonnull id<MDCTextControlVerticalPositioningReference>)verticalPositioningReference
horizontalPositioningReference:
(nonnull id<MDCTextControlHorizontalPositioning>)horizontalPositioningReference
text:(NSString *)text
font:(UIFont *)font
floatingFont:(UIFont *)floatingFont
label:(UILabel *)label
labelPosition:(MDCTextControlLabelPosition)labelPosition
labelBehavior:(MDCTextControlLabelBehavior)labelBehavior
placeholderLabel:(UILabel *)placeholderLabel
leadingView:(UIView *)leadingView
leadingViewMode:(UITextFieldViewMode)leadingViewMode
trailingView:(UIView *)trailingView
trailingViewMode:(UITextFieldViewMode)trailingViewMode
leadingAssistiveLabel:(UILabel *)leadingAssistiveLabel
trailingAssistiveLabel:(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);
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 textAreaWidth = size.width;
CGFloat rightViewMinX = 0;
if (displaysRightView) {
CGFloat rightViewMaxX = textAreaWidth - rightEdgePadding;
rightViewMinX = rightViewMaxX - CGRectGetWidth(rightView.frame);
}
CGFloat textRectMinX =
displaysLeftView ? leftViewMaxX + horizontalInterItemPadding : leftEdgePadding;
CGFloat textRectMaxX = displaysRightView ? rightViewMinX - horizontalInterItemPadding
: textAreaWidth - rightEdgePadding;
CGFloat textRectWidth = textRectMaxX - textRectMinX;
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 floatingLabelFrame =
[self floatingLabelFrameWithFloatingLabelSize:labelSizeFloating
textRectMinX:textRectMinX
textRectMaxX:textRectMaxX
paddingBetweenContainerTopAndFloatingLabel:verticalPositioningReference
.paddingBetweenContainerTopAndFloatingLabel
isRTL:isRTL];
CGFloat floatingLabelMaxY = CGRectGetMaxY(floatingLabelFrame);
CGRect normalLabelFrame =
[self normalLabelFrameWithNormalLabelSize:labelSizeNormal
textRectMinX:textRectMinX
textRectMaxX:textRectMaxX
paddingBetweenContainerTopAndNormalLabel:verticalPositioningReference
.paddingBetweenContainerTopAndNormalLabel
isRTL:isRTL];
CGFloat halfOfNormalLineHeight = (CGFloat)0.5 * font.lineHeight;
CGFloat textViewMinY = 0;
CGFloat bottomPadding = 0;
CGFloat containerHeight = 0;
BOOL shouldLayoutForFloatingLabel = MDCTextControlShouldLayoutForFloatingLabelWithLabelPosition(
labelPosition, labelBehavior, label.text);
if (shouldLayoutForFloatingLabel) {
CGFloat textViewMinYNormal = CGRectGetMidY(normalLabelFrame) - halfOfNormalLineHeight;
if (labelPosition == MDCTextControlLabelPositionFloating) {
textViewMinY = floatingLabelMaxY +
verticalPositioningReference.paddingBetweenFloatingLabelAndEditingText;
} else {
textViewMinY = textViewMinYNormal;
}
bottomPadding = verticalPositioningReference.paddingBetweenEditingTextAndContainerBottom;
containerHeight = verticalPositioningReference.containerHeightWithFloatingLabel;
} else {
textViewMinY = verticalPositioningReference.paddingAroundTextWhenNoFloatingLabel;
bottomPadding = verticalPositioningReference.paddingAroundTextWhenNoFloatingLabel;
containerHeight = verticalPositioningReference.containerHeightWithoutFloatingLabel;
normalLabelFrame.origin.y = textViewMinY;
}
CGFloat textViewHeight = containerHeight - bottomPadding - textViewMinY;
CGRect textViewFrame = CGRectMake(textRectMinX, textViewMinY, textRectWidth, textViewHeight);
self.placeholderLabelHidden = placeholderLabel.attributedText == nil;
if (self.placeholderLabelHidden) {
self.placeholderLabelFrame = CGRectZero;
} else {
CGSize sizeThatFits = [placeholderLabel sizeThatFits:textViewFrame.size];
CGFloat placeholderLabelMinX = 0;
if (isRTL) {
placeholderLabelMinX = textRectWidth - sizeThatFits.width;
}
self.placeholderLabelFrame =
CGRectMake(placeholderLabelMinX, 0, sizeThatFits.width, sizeThatFits.height);
}
CGRect leftViewFrame = CGRectZero;
if (displaysLeftView) {
leftViewFrame = [self sideViewFrameWithSideView:leftView
minX:leftViewMinX
containerHeight:containerHeight];
}
CGRect rightViewFrame = CGRectZero;
if (displaysRightView) {
rightViewFrame = [self sideViewFrameWithSideView:rightView
minX:rightViewMinX
containerHeight:containerHeight];
}
self.leadingViewFrame = isRTL ? rightViewFrame : leftViewFrame;
self.trailingViewFrame = isRTL ? leftViewFrame : rightViewFrame;
self.displaysLeadingView = isRTL ? displaysRightView : displaysLeftView;
self.displaysTrailingView = isRTL ? displaysLeftView : displaysRightView;
self.assistiveLabelViewLayout = [[MDCTextControlAssistiveLabelViewLayout alloc]
initWithWidth:size.width
leadingAssistiveLabel:leadingAssistiveLabel
trailingAssistiveLabel:trailingAssistiveLabel
assistiveLabelDrawPriority:assistiveLabelDrawPriority
customAssistiveLabelDrawPriority:customAssistiveLabelDrawPriority
leadingEdgePadding:leadingEdgePadding
trailingEdgePadding:trailingEdgePadding
paddingAboveAssistiveLabels:verticalPositioningReference.paddingAboveAssistiveLabels
paddingBelowAssistiveLabels:verticalPositioningReference.paddingBelowAssistiveLabels
isRTL:isRTL];
self.assistiveLabelViewFrame =
CGRectMake(0, containerHeight, size.width, self.assistiveLabelViewLayout.calculatedHeight);
self.containerHeight = containerHeight;
self.textViewFrame = textViewFrame;
self.labelFrameFloating = floatingLabelFrame;
self.labelFrameNormal = normalLabelFrame;
self.horizontalGradientLocations =
[self determineHorizontalGradientLocationsWithLeftFadeStart:0
leftFadeEnd:kGradientBlurLength
rightFadeStart:size.width - kGradientBlurLength
rightFadeEnd:size.width
viewWidth:size.width];
if (shouldLayoutForFloatingLabel) {
CGFloat topFadeStart = floatingLabelMaxY;
CGFloat topFadeEnd = floatingLabelMaxY + kGradientBlurLength;
CGFloat bottomFadeStart =
verticalPositioningReference.containerHeightWithFloatingLabel - bottomPadding;
CGFloat bottomFadeEnd = bottomFadeStart + kGradientBlurLength;
self.verticalGradientLocations = [self
determineVerticalGradientLocationsWithTopFadeStart:topFadeStart
topFadeEnd:topFadeEnd
bottomFadeStart:bottomFadeStart
bottomFadeEnd:bottomFadeEnd
containerHeight:verticalPositioningReference
.containerHeightWithFloatingLabel];
} else {
CGFloat topFadeEnd = verticalPositioningReference.paddingAroundTextWhenNoFloatingLabel;
CGFloat topFadeStart = topFadeEnd - kGradientBlurLength;
CGFloat bottomFadeStart = verticalPositioningReference.containerHeightWithoutFloatingLabel -
verticalPositioningReference.paddingAroundTextWhenNoFloatingLabel;
CGFloat bottomFadeEnd = bottomFadeStart + kGradientBlurLength;
self.verticalGradientLocations =
[self determineVerticalGradientLocationsWithTopFadeStart:topFadeStart
topFadeEnd:topFadeEnd
bottomFadeStart:bottomFadeStart
bottomFadeEnd:bottomFadeEnd
containerHeight:
verticalPositioningReference
.containerHeightWithoutFloatingLabel];
}
}
- (CGFloat)calculatedHeight {
CGFloat maxY = self.containerHeight;
CGFloat assistiveLabelViewMaxY = CGRectGetMaxY(self.assistiveLabelViewFrame);
if (assistiveLabelViewMaxY > maxY) {
maxY = assistiveLabelViewMaxY;
}
return maxY;
}
- (CGRect)sideViewFrameWithSideView:(UIView *)sideView
minX:(CGFloat)minX
containerHeight:(CGFloat)containerHeight {
CGFloat sideViewHeight = CGRectGetHeight(sideView.bounds);
CGFloat minY = (0.5f * containerHeight) - (0.5f * sideViewHeight);
return CGRectMake(minX, minY, CGRectGetWidth(sideView.bounds), sideViewHeight);
}
- (CGRect)normalLabelFrameWithNormalLabelSize:(CGSize)normalLabelSize
textRectMinX:(CGFloat)textRectMinX
textRectMaxX:(CGFloat)textRectMaxX
paddingBetweenContainerTopAndNormalLabel:(CGFloat)paddingBetweenContainerTopAndNormalLabel
isRTL:(BOOL)isRTL {
CGFloat normalLabelMinX = textRectMinX;
if (isRTL) {
normalLabelMinX = textRectMaxX - normalLabelSize.width;
}
CGFloat normalLabelMinY = paddingBetweenContainerTopAndNormalLabel;
return CGRectMake(normalLabelMinX, normalLabelMinY, normalLabelSize.width,
normalLabelSize.height);
}
- (CGRect)floatingLabelFrameWithFloatingLabelSize:(CGSize)floatingLabelSize
textRectMinX:(CGFloat)textRectMinX
textRectMaxX:(CGFloat)textRectMaxX
paddingBetweenContainerTopAndFloatingLabel:
(CGFloat)paddingBetweenContainerTopAndFloatingLabel
isRTL:(BOOL)isRTL {
CGFloat textMinY = paddingBetweenContainerTopAndFloatingLabel;
CGFloat textMinX = textRectMinX;
if (isRTL) {
textMinX = textRectMaxX - floatingLabelSize.width;
}
return CGRectMake(textMinX, textMinY, floatingLabelSize.width, floatingLabelSize.height);
}
- (CGFloat)textHeightWithFont:(UIFont *)font {
return (CGFloat)ceil((double)font.lineHeight);
}
- (CGSize)textSizeWithText:(NSString *)text font:(UIFont *)font maxWidth:(CGFloat)maxWidth {
CGSize fittingSize = CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX);
NSDictionary *attributes = @{NSFontAttributeName : font};
CGRect rect = [text boundingRectWithSize:fittingSize
options:NSStringDrawingUsesLineFragmentOrigin
attributes:attributes
context:nil];
CGFloat maxTextFieldHeight = font.lineHeight;
CGFloat textFieldWidth = CGRectGetWidth(rect);
CGFloat textFieldHeight = CGRectGetHeight(rect);
if (textFieldWidth > maxWidth) {
textFieldWidth = maxWidth;
}
if (textFieldHeight > maxTextFieldHeight) {
textFieldHeight = maxTextFieldHeight;
}
rect.size.width = textFieldWidth;
rect.size.height = textFieldHeight;
return rect.size;
}
- (NSArray<NSNumber *> *)
determineHorizontalGradientLocationsWithLeftFadeStart:(CGFloat)leftFadeStart
leftFadeEnd:(CGFloat)leftFadeEnd
rightFadeStart:(CGFloat)rightFadeStart
rightFadeEnd:(CGFloat)rightFadeEnd
viewWidth:(CGFloat)viewWidth {
CGFloat leftFadeStartLocation = leftFadeStart / viewWidth;
if (leftFadeStartLocation < 0) {
leftFadeStartLocation = 0;
}
CGFloat leftFadeEndLocation = leftFadeEnd / viewWidth;
if (leftFadeEndLocation < 0) {
leftFadeEndLocation = 0;
}
CGFloat rightFadeStartLocation = rightFadeStart / viewWidth;
if (rightFadeStartLocation >= 1) {
rightFadeStartLocation = 1;
}
CGFloat rightFadeEndLocation = rightFadeEnd / viewWidth;
if (rightFadeEndLocation >= 1) {
rightFadeEndLocation = 1;
}
return @[
@(0),
@(leftFadeStartLocation),
@(leftFadeEndLocation),
@(rightFadeStartLocation),
@(rightFadeEndLocation),
@(1),
];
}
- (NSArray<NSNumber *> *)determineVerticalGradientLocationsWithTopFadeStart:(CGFloat)topFadeStart
topFadeEnd:(CGFloat)topFadeEnd
bottomFadeStart:(CGFloat)bottomFadeStart
bottomFadeEnd:(CGFloat)bottomFadeEnd
containerHeight:
(CGFloat)containerHeight {
CGFloat topFadeStartLocation = topFadeStart / containerHeight;
if (topFadeStartLocation <= 0) {
topFadeStartLocation = 0;
}
CGFloat topFadeEndLocation = topFadeEnd / containerHeight;
if (topFadeEndLocation <= 0) {
topFadeEndLocation = 0;
}
CGFloat bottomFadeStartLocation = bottomFadeStart / containerHeight;
if (bottomFadeStartLocation >= 1) {
bottomFadeStartLocation = 1;
}
CGFloat bottomFadeEndLocation = bottomFadeEnd / containerHeight;
if (bottomFadeEndLocation >= 1) {
bottomFadeEndLocation = 1;
}
return @[
@(0),
@(topFadeStartLocation),
@(topFadeEndLocation),
@(bottomFadeStartLocation),
@(bottomFadeEndLocation),
@(1),
];
}
- (CGRect)labelFrameWithLabelPosition:(MDCTextControlLabelPosition)labelPosition {
if (labelPosition == MDCTextControlLabelPositionFloating) {
return self.labelFrameFloating;
} else if (labelPosition == MDCTextControlLabelPositionNormal) {
return self.labelFrameNormal;
} else {
return CGRectZero;
}
}
@end