blob: 4a3f2511a699a489a39b753785c76d2c5b4efd04 [file]
/*
Copyright 2018-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 "MDCCard.h"
#import "MaterialMath.h"
#import "MaterialShapes.h"
static NSString *const MDCCardBackgroundColorsKey = @"MDCCardBackgroundColorsKey";
static NSString *const MDCCardBorderColorsKey = @"MDCCardBorderColorsKey";
static NSString *const MDCCardBorderWidthsKey = @"MDCCardBorderWidthsKey";
static NSString *const MDCCardCornerRadiusKey = @"MDCCardCornerRadiusKey";
static NSString *const MDCCardInkViewKey = @"MDCCardInkViewKey";
static NSString *const MDCCardShadowColorsKey = @"MDCCardShadowColorsKey";
static NSString *const MDCCardShadowElevationsKey = @"MDCCardShadowElevationsKey";
static const CGFloat MDCCardShadowElevationNormal = 1.f;
static const CGFloat MDCCardShadowElevationHighlighted = 8.f;
static const CGFloat MDCCardCornerRadiusDefault = 4.f;
@interface MDCCard ()
@property(nonatomic, readonly, strong) MDCShapedShadowLayer *layer;
@end
@implementation MDCCard {
NSMutableDictionary<NSNumber *, NSNumber *> *_shadowElevations;
NSMutableDictionary<NSNumber *, UIColor *> *_shadowColors;
NSMutableDictionary<NSNumber *, NSNumber *> *_borderWidths;
NSMutableDictionary<NSNumber *, UIColor *> *_borderColors;
UIColor *_backgroundColor;
CGPoint _lastTouch;
}
@dynamic layer;
+ (Class)layerClass {
return [MDCShapedShadowLayer class];
}
- (instancetype)initWithCoder:(NSCoder *)coder {
self = [super initWithCoder:coder];
if (self) {
_shadowElevations = [coder decodeObjectOfClass:[NSMutableDictionary class]
forKey:MDCCardShadowElevationsKey];
_shadowColors = [coder decodeObjectOfClass:[NSMutableDictionary class]
forKey:MDCCardShadowColorsKey];
_borderWidths = [coder decodeObjectOfClass:[NSMutableDictionary class]
forKey:MDCCardBorderWidthsKey];
_borderColors = [coder decodeObjectOfClass:[NSMutableDictionary class]
forKey:MDCCardBorderColorsKey];
_inkView = [coder decodeObjectOfClass:[MDCInkView class] forKey:MDCCardInkViewKey];
if ([coder containsValueForKey:MDCCardCornerRadiusKey]) {
self.layer.cornerRadius = (CGFloat)[coder decodeDoubleForKey:MDCCardCornerRadiusKey];
} else {
self.layer.cornerRadius = MDCCardCornerRadiusDefault;
}
if ([coder containsValueForKey:MDCCardBackgroundColorsKey]) {
[self.layer setShapedBackgroundColor:[coder decodeObjectOfClass:[UIColor class]
forKey:MDCCardBackgroundColorsKey]];
}
[self commonMDCCardInit];
}
return self;
}
- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
self.layer.cornerRadius = MDCCardCornerRadiusDefault;
[self commonMDCCardInit];
}
return self;
}
- (void)commonMDCCardInit {
if (_inkView == nil) {
_inkView = [[MDCInkView alloc] initWithFrame:self.bounds];
_inkView.autoresizingMask = (UIViewAutoresizingFlexibleWidth |
UIViewAutoresizingFlexibleHeight);
_inkView.usesLegacyInkRipple = NO;
_inkView.layer.zPosition = FLT_MAX;
[self addSubview:_inkView];
}
if (_shadowElevations == nil) {
_shadowElevations = [NSMutableDictionary dictionary];
_shadowElevations[@(UIControlStateNormal)] = @(MDCCardShadowElevationNormal);
_shadowElevations[@(UIControlStateHighlighted)] = @(MDCCardShadowElevationHighlighted);
}
if (_shadowColors == nil) {
_shadowColors = [NSMutableDictionary dictionary];
_shadowColors[@(UIControlStateNormal)] = UIColor.blackColor;
}
if (_borderColors == nil) {
_borderColors = [NSMutableDictionary dictionary];
}
if (_borderWidths == nil) {
_borderWidths = [NSMutableDictionary dictionary];
}
if (_backgroundColor == nil) {
_backgroundColor = UIColor.whiteColor;
}
[self updateShadowElevation];
[self updateShadowColor];
[self updateBorderWidth];
[self updateBorderColor];
[self updateBackgroundColor];
}
- (void)encodeWithCoder:(NSCoder *)coder {
[super encodeWithCoder:coder];
[coder encodeObject:_shadowElevations forKey:MDCCardShadowElevationsKey];
[coder encodeObject:_shadowColors forKey:MDCCardShadowColorsKey];
[coder encodeObject:_borderWidths forKey:MDCCardBorderWidthsKey];
[coder encodeObject:_borderColors forKey:MDCCardBorderColorsKey];
[coder encodeObject:_inkView forKey:MDCCardInkViewKey];
[coder encodeDouble:self.layer.cornerRadius forKey:MDCCardCornerRadiusKey];
[coder encodeObject:self.layer.shapedBackgroundColor forKey:MDCCardBackgroundColorsKey];
}
- (void)layoutSubviews {
[super layoutSubviews];
if (!self.layer.shapeGenerator) {
self.layer.shadowPath = [self boundingPath].CGPath;
}
}
- (void)setCornerRadius:(CGFloat)cornerRadius {
self.layer.cornerRadius = cornerRadius;
[self setNeedsLayout];
}
- (CGFloat)cornerRadius {
return self.layer.cornerRadius;
}
- (UIBezierPath *)boundingPath {
CGFloat cornerRadius = self.cornerRadius;
return [UIBezierPath bezierPathWithRoundedRect:self.bounds cornerRadius:cornerRadius];
}
- (MDCShadowElevation)shadowElevationForState:(UIControlState)state {
NSNumber *elevation = _shadowElevations[@(state)];
if (state != UIControlStateNormal && elevation == nil) {
elevation = _shadowElevations[@(UIControlStateNormal)];
}
if (elevation != nil) {
return (CGFloat)[elevation doubleValue];
}
return 0;
}
- (void)setShadowElevation:(MDCShadowElevation)shadowElevation forState:(UIControlState)state {
_shadowElevations[@(state)] = @(shadowElevation);
[self updateShadowElevation];
}
- (void)updateShadowElevation {
CGFloat elevation = [self shadowElevationForState:self.state];
if (!MDCCGFloatEqual(((MDCShadowLayer *)self.layer).elevation, elevation)) {
if (!self.layer.shapeGenerator) {
self.layer.shadowPath = [self boundingPath].CGPath;
}
[(MDCShadowLayer *)self.layer setElevation:elevation];
}
}
- (void)setBorderWidth:(CGFloat)borderWidth forState:(UIControlState)state {
_borderWidths[@(state)] = @(borderWidth);
[self updateBorderWidth];
}
- (void)updateBorderWidth {
CGFloat borderWidth = [self borderWidthForState:self.state];
self.layer.shapedBorderWidth = borderWidth;
}
- (CGFloat)borderWidthForState:(UIControlState)state {
NSNumber *borderWidth = _borderWidths[@(state)];
if (state != UIControlStateNormal && borderWidth == nil) {
borderWidth = _borderWidths[@(UIControlStateNormal)];
}
if (borderWidth != nil) {
return (CGFloat)[borderWidth doubleValue];
}
return 0;
}
- (void)setBorderColor:(UIColor *)borderColor forState:(UIControlState)state {
_borderColors[@(state)] = borderColor;
[self updateBorderColor];
}
- (void)updateBorderColor {
UIColor *borderColor = [self borderColorForState:self.state];
self.layer.shapedBorderColor = borderColor;
}
- (UIColor *)borderColorForState:(UIControlState)state {
UIColor *borderColor = _borderColors[@(state)];
if (state != UIControlStateNormal && borderColor == nil) {
borderColor = _borderColors[@(UIControlStateNormal)];
}
return borderColor;
}
- (void)setShadowColor:(UIColor *)shadowColor forState:(UIControlState)state {
_shadowColors[@(state)] = shadowColor;
[self updateShadowColor];
}
- (void)updateShadowColor {
CGColorRef shadowColor = [self shadowColorForState:self.state].CGColor;
self.layer.shadowColor = shadowColor;
}
- (UIColor *)shadowColorForState:(UIControlState)state {
UIColor *shadowColor = _shadowColors[@(state)];
if (state != UIControlStateNormal && shadowColor == nil) {
shadowColor = _shadowColors[@(UIControlStateNormal)];
}
if (shadowColor != nil) {
return shadowColor;
}
return [UIColor blackColor];
}
- (void)setHighlighted:(BOOL)highlighted {
if (highlighted && !self.highlighted) {
[self.inkView startTouchBeganAnimationAtPoint:_lastTouch completion:nil];
} else if (!highlighted && self.highlighted) {
[self.inkView startTouchEndedAnimationAtPoint:_lastTouch completion:nil];
}
[super setHighlighted:highlighted];
[self updateShadowElevation];
[self updateBorderColor];
[self updateBorderWidth];
[self updateShadowColor];
}
- (BOOL)beginTrackingWithTouch:(UITouch *)touch withEvent:(nullable UIEvent *)event {
BOOL beginTracking = [super beginTrackingWithTouch:touch withEvent:event];
CGPoint location = [touch locationInView:self];
_lastTouch = location;
return beginTracking;
}
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
if (self.layer.shapeGenerator) {
if (CGPathContainsPoint(self.layer.shapeLayer.path, nil, point, true)) {
return self;
} else {
return nil;
}
}
return [super hitTest:point withEvent:event];
}
- (void)setShapeGenerator:(id<MDCShapeGenerating>)shapeGenerator {
if (shapeGenerator) {
self.layer.shadowPath = nil;
} else {
self.layer.shadowPath = [self boundingPath].CGPath;
}
self.layer.shapeGenerator = shapeGenerator;
self.layer.shadowMaskEnabled = NO;
[self updateBackgroundColor];
[self updateInkForShape];
}
- (id<MDCShapeGenerating>)shapeGenerator {
return self.layer.shapeGenerator;
}
- (void)updateInkForShape {
CGRect boundingBox = CGPathGetBoundingBox(self.layer.shapeLayer.path);
self.inkView.maxRippleRadius =
(CGFloat)(MDCHypot(CGRectGetHeight(boundingBox), CGRectGetWidth(boundingBox)) / 2 + 10.f);
self.inkView.layer.masksToBounds = NO;
}
- (void)setBackgroundColor:(UIColor *)backgroundColor {
_backgroundColor = backgroundColor;
[self updateBackgroundColor];
}
- (UIColor *)backgroundColor {
return _backgroundColor;
}
- (void)updateBackgroundColor {
self.layer.shapedBackgroundColor = _backgroundColor;
}
@end