blob: 45958ceabd528957c25bed233ea16e06a4a83e67 [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 "MDCAvailability.h"
#include "MaterialAvailability.h"
#import "MDCTextControlLabelBehavior.h"
#import "MDCTextControlState.h"
#import "MDCTextControlVerticalPositioningReferenceOutlined.h"
#import "MDCTextControl.h"
#import "MDCTextControlHorizontalPositioningReference.h"
#import "MDCTextControlLabelSupport.h"
#import "UIBezierPath+MDCTextControlStyle.h"
static const CGFloat kDefaultOutlinedContainerStyleCornerRadius = (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.outlineCornerRadius = kDefaultOutlinedContainerStyleCornerRadius;
[self setUpOutlineColors];
[self setUpOutlineLineWidths];
[self setUpOutlineSublayer];
}
- (void)setUpOutlineColors {
self.outlineColors = [NSMutableDictionary new];
UIColor *outlineColor = [UIColor labelColor];
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 normalLabelFrame = textControl.normalLabelFrame;
CGRect floatingLabelFrame = textControl.floatingLabelFrame;
CGFloat containerHeight = CGRectGetHeight(textControl.containerFrame);
CGFloat lineWidth = (CGFloat)self.outlineLineWidths[@(textControl.textControlState)].doubleValue;
UIBezierPath *outlinePath =
[MDCTextControlStyleOutlined outlinePathWithViewBounds:textControl.bounds
normalLabelFrame:normalLabelFrame
floatingLabelFrame:floatingLabelFrame
containerHeight:containerHeight
lineWidth:lineWidth
cornerRadius:self.outlineCornerRadius
labelBehavior:textControl.labelBehavior
labelPosition:textControl.labelPosition];
CGColorRef strokeColor = (self.outlineColors[@(textControl.textControlState)]).CGColor;
if (!CGColorEqualToColor(self.outlinedSublayer.strokeColor, strokeColor)) {
animationDuration = 0;
self.outlinedSublayer.strokeColor = strokeColor;
}
[self applyOutlineTo:textControl
outlinePath:outlinePath
outlineLineWidth:lineWidth
animationDuration:animationDuration];
}
- (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
isMultilineTextControl:(BOOL)isMultilineTextControl {
return [[MDCTextControlVerticalPositioningReferenceOutlined alloc]
initWithFloatingFontLineHeight:floatingLabelHeight
normalFontLineHeight:normalFontLineHeight
textRowHeight:textRowHeight
numberOfTextRows:numberOfTextRows
density:density
preferredContainerHeight:preferredContainerHeight
isMultilineTextControl:isMultilineTextControl];
}
- (MDCTextControlHorizontalPositioningReference *)horizontalPositioningReference {
return [[MDCTextControlHorizontalPositioningReference alloc] init];
}
#pragma mark Internal Styling Methods
- (void)applyOutlineTo:(UIView *)view
outlinePath:(UIBezierPath *)outlinePath
outlineLineWidth:(CGFloat)outlineLineWidth
animationDuration:(NSTimeInterval)animationDuration {
if (self.outlinedSublayer.superlayer != view.layer) {
[view.layer insertSublayer:self.outlinedSublayer atIndex:0];
}
if (animationDuration <= 0) {
[self.outlinedSublayer removeAllAnimations];
self.outlinedSublayer.path = outlinePath.CGPath;
self.outlinedSublayer.lineWidth = outlineLineWidth;
return;
}
CABasicAnimation *existingAnimation =
[self.outlinedSublayer animationForKey:self.class.outlineAnimationKey];
BOOL needsAnimation = NO;
if (existingAnimation) {
if (!CGPathEqualToPath(self.outlinedSublayer.path, outlinePath.CGPath)) {
[self.outlinedSublayer removeAnimationForKey:self.class.outlineAnimationKey];
needsAnimation = YES;
}
} else {
needsAnimation = YES;
}
if (needsAnimation) {
[CATransaction begin];
[CATransaction setAnimationDuration:animationDuration];
self.outlinedSublayer.path = outlinePath.CGPath;
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"path"];
animation.timingFunction =
[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
animation.repeatCount = 0;
animation.removedOnCompletion = YES;
animation.fillMode = kCAFillModeForwards;
[self.outlinedSublayer addAnimation:animation forKey:self.class.outlineAnimationKey];
[CATransaction commit];
}
}
+ (NSString *)outlineAnimationKey {
return @"outlineAnimationKey";
}
+ (UIBezierPath *)outlinePathWithViewBounds:(CGRect)viewBounds
normalLabelFrame:(CGRect)normalLabelFrame
floatingLabelFrame:(CGRect)floatingLabelFrame
containerHeight:(CGFloat)containerHeight
lineWidth:(CGFloat)lineWidth
cornerRadius:(CGFloat)radius
labelBehavior:(MDCTextControlLabelBehavior)labelBehavior
labelPosition:(MDCTextControlLabelPosition)labelPosition {
UIBezierPath *path = [[UIBezierPath alloc] init];
CGFloat textFieldWidth = CGRectGetWidth(viewBounds);
CGFloat halfOfFloatingLabelHeight = CGRectGetHeight(floatingLabelFrame) * 0.5f;
CGFloat sublayerMinY = halfOfFloatingLabelHeight;
CGFloat sublayerMaxY = containerHeight;
if (labelBehavior == MDCTextControlLabelBehaviorDisappears ||
labelPosition == MDCTextControlLabelPositionNone) {
sublayerMinY = 0.0f;
}
CGPoint startingPoint = CGPointMake(radius, sublayerMinY);
CGPoint topRightCornerPoint1 = CGPointMake(textFieldWidth - radius, sublayerMinY);
[path moveToPoint:startingPoint];
if (labelPosition == MDCTextControlLabelPositionFloating) {
CGFloat leftLineBreak = CGRectGetMinX(floatingLabelFrame) - kFloatingLabelOutlineSidePadding;
CGFloat rightLineBreak = CGRectGetMaxX(floatingLabelFrame) + 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