blob: 5c0e867f489337513289a39cc074915f3d8249bd [file] [log] [blame]
// Copyright 2019-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 "MDCTextControlStyleOutlined.h"
#import "MDCTextControl.h"
#import "MDCTextControlVerticalPositioningReferenceOutlined.h"
#import "UIBezierPath+MDCTextControlStyle.h"
static const CGFloat kOutlinedContainerStyleCornerRadius = (CGFloat)4.0;
static const CGFloat kFloatingLabelOutlineSidePadding = (CGFloat)5.0;
static const CGFloat kFilledFloatingLabelScaleFactor = (CGFloat)0.75;
@interface MDCTextControlStyleOutlined ()
@property(strong, nonatomic) CAShapeLayer *outlinedSublayer;
@property(strong, nonatomic) NSMutableDictionary<NSNumber *, UIColor *> *outlineColors;
@property(strong, nonatomic) NSMutableDictionary<NSNumber *, NSNumber *> *outlineLineWidths;
@end
@implementation MDCTextControlStyleOutlined
#pragma mark Object Lifecycle
- (instancetype)init {
self = [super init];
if (self) {
[self commonMDCTextControlStyleOutlinedInit];
}
return self;
}
#pragma mark Setup
- (void)commonMDCTextControlStyleOutlinedInit {
[self setUpOutlineColors];
[self setUpOutlineLineWidths];
[self setUpOutlineSublayer];
}
- (void)setUpOutlineColors {
self.outlineColors = [NSMutableDictionary new];
UIColor *outlineColor = [UIColor blackColor];
#if defined(__IPHONE_13_0) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0)
if (@available(iOS 13.0, *)) {
outlineColor = [UIColor labelColor];
}
#endif
self.outlineColors[@(MDCTextControlStateNormal)] = outlineColor;
self.outlineColors[@(MDCTextControlStateEditing)] = outlineColor;
self.outlineColors[@(MDCTextControlStateDisabled)] =
[outlineColor colorWithAlphaComponent:(CGFloat)0.60];
}
- (void)setUpOutlineLineWidths {
self.outlineLineWidths = [NSMutableDictionary new];
self.outlineLineWidths[@(MDCTextControlStateNormal)] = @(1);
self.outlineLineWidths[@(MDCTextControlStateEditing)] = @(2);
self.outlineLineWidths[@(MDCTextControlStateDisabled)] = @(1);
}
- (void)setUpOutlineSublayer {
self.outlinedSublayer = [[CAShapeLayer alloc] init];
self.outlinedSublayer.fillColor = [UIColor clearColor].CGColor;
self.outlinedSublayer.lineWidth =
(CGFloat)[self.outlineLineWidths[@(MDCTextControlStateNormal)] doubleValue];
}
#pragma mark Accessors
- (UIColor *)outlineColorForState:(MDCTextControlState)state {
return self.outlineColors[@(state)];
}
- (void)setOutlineColor:(nonnull UIColor *)outlineColor forState:(MDCTextControlState)state {
self.outlineColors[@(state)] = outlineColor;
}
#pragma mark MDCTextControlStyle
- (void)applyStyleToTextControl:(UIView<MDCTextControl> *)textControl
animationDuration:(NSTimeInterval)animationDuration {
CGRect labelFrame = textControl.labelFrame;
BOOL isLabelFloating = textControl.labelState == MDCTextControlLabelStateFloating;
CGFloat containerHeight = CGRectGetMaxY(textControl.containerFrame);
CGFloat lineWidth = (CGFloat)self.outlineLineWidths[@(textControl.textControlState)].doubleValue;
[self applyStyleTo:textControl
labelFrame:labelFrame
containerHeight:containerHeight
isLabelFloating:isLabelFloating
outlineLineWidth:lineWidth];
self.outlinedSublayer.strokeColor =
((UIColor *)self.outlineColors[@(textControl.textControlState)]).CGColor;
}
- (UIFont *)floatingFontWithNormalFont:(UIFont *)font {
CGFloat scaleFactor = kFilledFloatingLabelScaleFactor;
CGFloat floatingFontSize = font.pointSize * scaleFactor;
return [font fontWithSize:floatingFontSize];
}
- (void)removeStyleFrom:(id<MDCTextControl>)TextControl {
[self.outlinedSublayer removeFromSuperlayer];
}
- (id<MDCTextControlVerticalPositioningReference>)
positioningReferenceWithFloatingFontLineHeight:(CGFloat)floatingLabelHeight
normalFontLineHeight:(CGFloat)normalFontLineHeight
textRowHeight:(CGFloat)textRowHeight
numberOfTextRows:(CGFloat)numberOfTextRows
density:(CGFloat)density
preferredContainerHeight:(CGFloat)preferredContainerHeight {
return [[MDCTextControlVerticalPositioningReferenceOutlined alloc]
initWithFloatingFontLineHeight:floatingLabelHeight
normalFontLineHeight:normalFontLineHeight
textRowHeight:textRowHeight
numberOfTextRows:numberOfTextRows
density:density
preferredContainerHeight:preferredContainerHeight];
}
#pragma mark Internal Styling Methods
- (void)applyStyleTo:(UIView *)view
labelFrame:(CGRect)labelFrame
containerHeight:(CGFloat)containerHeight
isLabelFloating:(BOOL)isLabelFloating
outlineLineWidth:(CGFloat)outlineLineWidth {
UIBezierPath *path = [MDCTextControlStyleOutlined outlinePathWithViewBounds:view.bounds
labelFrame:labelFrame
containerHeight:containerHeight
lineWidth:outlineLineWidth
isLabelFloating:isLabelFloating];
[CATransaction begin];
[CATransaction setDisableActions:YES];
self.outlinedSublayer.path = path.CGPath;
self.outlinedSublayer.lineWidth = outlineLineWidth;
[CATransaction commit];
if (self.outlinedSublayer.superlayer != view.layer) {
[view.layer insertSublayer:self.outlinedSublayer atIndex:0];
}
}
+ (UIBezierPath *)outlinePathWithViewBounds:(CGRect)viewBounds
labelFrame:(CGRect)labelFrame
containerHeight:(CGFloat)containerHeight
lineWidth:(CGFloat)lineWidth
isLabelFloating:(BOOL)isLabelFloating {
UIBezierPath *path = [[UIBezierPath alloc] init];
CGFloat radius = kOutlinedContainerStyleCornerRadius;
CGFloat textFieldWidth = CGRectGetWidth(viewBounds);
CGFloat sublayerMinY = 0;
CGFloat sublayerMaxY = containerHeight;
CGPoint startingPoint = CGPointMake(radius, sublayerMinY);
CGPoint topRightCornerPoint1 = CGPointMake(textFieldWidth - radius, sublayerMinY);
[path moveToPoint:startingPoint];
if (isLabelFloating) {
CGFloat leftLineBreak = CGRectGetMinX(labelFrame) - kFloatingLabelOutlineSidePadding;
CGFloat rightLineBreak = CGRectGetMaxX(labelFrame) + kFloatingLabelOutlineSidePadding;
[path addLineToPoint:CGPointMake(leftLineBreak, sublayerMinY)];
[path moveToPoint:CGPointMake(rightLineBreak, sublayerMinY)];
[path addLineToPoint:CGPointMake(rightLineBreak, sublayerMinY)];
} else {
[path addLineToPoint:topRightCornerPoint1];
}
CGPoint topRightCornerPoint2 = CGPointMake(textFieldWidth, sublayerMinY + radius);
[path mdc_addTopRightCornerFromPoint:topRightCornerPoint1
toPoint:topRightCornerPoint2
withRadius:radius];
CGPoint bottomRightCornerPoint1 = CGPointMake(textFieldWidth, sublayerMaxY - radius);
CGPoint bottomRightCornerPoint2 = CGPointMake(textFieldWidth - radius, sublayerMaxY);
[path addLineToPoint:bottomRightCornerPoint1];
[path mdc_addBottomRightCornerFromPoint:bottomRightCornerPoint1
toPoint:bottomRightCornerPoint2
withRadius:radius];
CGPoint bottomLeftCornerPoint1 = CGPointMake(radius, sublayerMaxY);
CGPoint bottomLeftCornerPoint2 = CGPointMake(0, sublayerMaxY - radius);
[path addLineToPoint:bottomLeftCornerPoint1];
[path mdc_addBottomLeftCornerFromPoint:bottomLeftCornerPoint1
toPoint:bottomLeftCornerPoint2
withRadius:radius];
CGPoint topLeftCornerPoint1 = CGPointMake(0, sublayerMinY + radius);
CGPoint topLeftCornerPoint2 = CGPointMake(radius, sublayerMinY);
[path addLineToPoint:topLeftCornerPoint1];
[path mdc_addTopLeftCornerFromPoint:topLeftCornerPoint1
toPoint:topLeftCornerPoint2
withRadius:radius];
return path;
}
@end