blob: 740267583f22cf186505e3334e57b9b74795a550 [file]
/*
Copyright 2017-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 <CoreGraphics/CoreGraphics.h>
#import "MDCBottomAppBarView.h"
#import <MDFInternationalization/MDFInternationalization.h>
#import "MaterialNavigationBar.h"
#import "private/MDCBottomAppBarAttributes.h"
#import "private/MDCBottomAppBarLayer.h"
static NSString *kMDCBottomAppBarViewAnimKeyString = @"AnimKey";
static NSString *kMDCBottomAppBarViewPathString = @"path";
static NSString *kMDCBottomAppBarViewPositionString = @"position";
static const CGFloat kMDCBottomAppBarViewFloatingButtonElevationPrimary = 6.f;
static const CGFloat kMDCBottomAppBarViewFloatingButtonElevationSecondary = 4.f;
static const int kMDCButtonAnimationDuration = 200;
@interface MDCBottomAppBarCutView : UIView
@end
@implementation MDCBottomAppBarCutView
// Allows touch events to pass through so MDCBottomAppBarController can handle touch events.
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
UIView *view = [super hitTest:point withEvent:event];
return view == self ? nil : view;
}
@end
@interface MDCBottomAppBarView () <CAAnimationDelegate>
@property(nonatomic, assign) CGFloat bottomBarHeight;
@property(nonatomic, strong) MDCBottomAppBarCutView *cutView;
@property(nonatomic, strong) MDCBottomAppBarLayer *bottomBarLayer;
@property(nonatomic, strong) MDCNavigationBar *navBar;
@property(nonatomic, assign) UIUserInterfaceLayoutDirection layoutDirection;
@end
@implementation MDCBottomAppBarView
- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
[self commonMDCBottomAppBarViewInit];
}
return self;
}
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
self = [super initWithCoder:aDecoder];
if (self) {
[self commonMDCBottomAppBarViewInit];
}
return self;
}
- (void)commonMDCBottomAppBarViewInit {
self.cutView = [[MDCBottomAppBarCutView alloc] initWithFrame:self.bounds];
[self addSubview:self.cutView];
self.autoresizingMask = (UIViewAutoresizingFlexibleWidth |
UIViewAutoresizingFlexibleLeftMargin |
UIViewAutoresizingFlexibleRightMargin);
self.layoutDirection = self.mdf_effectiveUserInterfaceLayoutDirection;
[self addFloatingButton];
[self addBottomBarLayer];
[self addNavBar];
}
- (void)addFloatingButton {
MDCFloatingButton *floatingButton = [[MDCFloatingButton alloc] init];
[self setFloatingButton:floatingButton];
[self setFloatingButtonPosition:MDCBottomAppBarFloatingButtonPositionCenter];
[self setFloatingButtonElevation:MDCBottomAppBarFloatingButtonElevationPrimary];
[self setFloatingButtonHidden:NO];
}
- (void)addNavBar {
_navBar = [[MDCNavigationBar alloc] initWithFrame:CGRectZero];
[self addSubview:_navBar];
_navBar.backgroundColor = [UIColor clearColor];
_navBar.tintColor = [UIColor blackColor];
}
- (void)addBottomBarLayer {
if (_bottomBarLayer) {
[_bottomBarLayer removeFromSuperlayer];
}
_bottomBarLayer = [MDCBottomAppBarLayer layer];
[_cutView.layer addSublayer:_bottomBarLayer];
}
- (void)renderPathBasedOnFloatingButtonVisibitlityAnimated:(BOOL)animated {
if (!self.floatingButtonHidden) {
[self cutBottomAppBarViewAnimated:animated];
} else {
[self healBottomAppBarViewAnimated:animated];
}
}
- (CGPoint)getFloatingButtonCenterPositionForWidth:(CGFloat)width {
CGPoint floatingButtonPoint = CGPointZero;
CGFloat halfDefaultDimension = CGRectGetMidX(self.floatingButton.bounds);
CGFloat midX = width / 2;
switch (self.floatingButtonPosition) {
case MDCBottomAppBarFloatingButtonPositionLeading: {
if (self.layoutDirection == UIUserInterfaceLayoutDirectionLeftToRight) {
floatingButtonPoint = CGPointMake(kMDCBottomAppBarFloatingButtonPositionX,
halfDefaultDimension);
} else {
floatingButtonPoint = CGPointMake(width - kMDCBottomAppBarFloatingButtonPositionX,
halfDefaultDimension);
}
break;
}
case MDCBottomAppBarFloatingButtonPositionCenter: {
floatingButtonPoint = CGPointMake(midX, halfDefaultDimension);
break;
}
case MDCBottomAppBarFloatingButtonPositionTrailing: {
if (self.layoutDirection == UIUserInterfaceLayoutDirectionLeftToRight) {
floatingButtonPoint = CGPointMake(width - kMDCBottomAppBarFloatingButtonPositionX,
halfDefaultDimension);
} else {
floatingButtonPoint = CGPointMake(kMDCBottomAppBarFloatingButtonPositionX,
halfDefaultDimension);
}
break;
}
default:
break;
}
return floatingButtonPoint;
}
- (void)cutBottomAppBarViewAnimated:(BOOL)animated {
CGPathRef cutPath =
[self.bottomBarLayer pathWithCutFromRect:self.bounds
floatingButtonPosition:self.floatingButtonPosition
layoutDirection:self.layoutDirection];
if (animated) {
CABasicAnimation *pathAnimation =
[CABasicAnimation animationWithKeyPath:kMDCBottomAppBarViewPathString];
pathAnimation.duration = kMDCFloatingButtonExitDuration;
pathAnimation.fromValue = (id)self.bottomBarLayer.presentationLayer.path;
pathAnimation.toValue = (__bridge id _Nullable)(cutPath);
pathAnimation.fillMode = kCAFillModeForwards;
pathAnimation.removedOnCompletion = NO;
pathAnimation.delegate = self;
[pathAnimation setValue:kMDCBottomAppBarViewPathString
forKey:kMDCBottomAppBarViewAnimKeyString];
[self.bottomBarLayer addAnimation:pathAnimation forKey:kMDCBottomAppBarViewPathString];
} else {
self.bottomBarLayer.path = cutPath;
}
}
- (void)healBottomAppBarViewAnimated:(BOOL)animated {
CGPathRef withoutCutPath =
[self.bottomBarLayer pathWithoutCutFromRect:self.bounds
floatingButtonPosition:self.floatingButtonPosition
layoutDirection:self.layoutDirection];
if (animated) {
CABasicAnimation *pathAnimation =
[CABasicAnimation animationWithKeyPath:kMDCBottomAppBarViewPathString];
pathAnimation.duration = kMDCFloatingButtonEnterDuration;
pathAnimation.fromValue = (id)self.bottomBarLayer.presentationLayer.path;
pathAnimation.toValue = (__bridge id _Nullable)(withoutCutPath);
pathAnimation.fillMode = kCAFillModeForwards;
pathAnimation.removedOnCompletion = NO;
pathAnimation.delegate = self;
[pathAnimation setValue:kMDCBottomAppBarViewPathString
forKey:kMDCBottomAppBarViewAnimKeyString];
[self.bottomBarLayer addAnimation:pathAnimation forKey:kMDCBottomAppBarViewPathString];
} else {
self.bottomBarLayer.path = withoutCutPath;
}
}
- (void)moveFloatingButtonCenterAnimated:(BOOL)animated {
CGPoint endPoint = [self getFloatingButtonCenterPositionForWidth:CGRectGetWidth(self.bounds)];
if (animated) {
CABasicAnimation *animation =
[CABasicAnimation animationWithKeyPath:kMDCBottomAppBarViewPositionString];
animation.duration = kMDCFloatingButtonExitDuration;
animation.fromValue = [NSValue valueWithCGPoint:self.floatingButton.center];
animation.toValue = [NSValue valueWithCGPoint:endPoint];
animation.fillMode = kCAFillModeForwards;
animation.removedOnCompletion = NO;
animation.delegate = self;
[animation setValue:kMDCBottomAppBarViewPositionString
forKey:kMDCBottomAppBarViewAnimKeyString];
[self.floatingButton.layer addAnimation:animation forKey:kMDCBottomAppBarViewPositionString];
}
self.floatingButton.center = endPoint;
}
- (void)showBarButtonItemsWithFloatingButtonPosition:(MDCBottomAppBarFloatingButtonPosition)floatingButtonPosition {
switch (floatingButtonPosition) {
case MDCBottomAppBarFloatingButtonPositionCenter:
[self.navBar setLeadingBarButtonItems:_leadingBarButtonItems];
[self.navBar setTrailingBarButtonItems:_trailingBarButtonItems];
break;
case MDCBottomAppBarFloatingButtonPositionLeading:
[self.navBar setLeadingBarButtonItems:nil];
[self.navBar setTrailingBarButtonItems:_trailingBarButtonItems];
break;
case MDCBottomAppBarFloatingButtonPositionTrailing:
[self.navBar setLeadingBarButtonItems:_leadingBarButtonItems];
[self.navBar setTrailingBarButtonItems:nil];
break;
default:
break;
}
}
#pragma mark - UIView overrides
- (void)layoutSubviews {
[super layoutSubviews];
self.floatingButton.center =
[self getFloatingButtonCenterPositionForWidth:CGRectGetWidth(self.bounds)];
[self renderPathBasedOnFloatingButtonVisibitlityAnimated:NO];
CGRect navBarFrame = CGRectMake(0,
kMDCBottomAppBarYOffset,
CGRectGetWidth(self.bounds),
kMDCBottomAppBarHeight - kMDCBottomAppBarYOffset);
self.navBar.frame = navBarFrame;
}
- (UIEdgeInsets)mdc_safeAreaInsets {
UIEdgeInsets insets = UIEdgeInsetsZero;
#if defined(__IPHONE_11_0) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_11_0)
if (@available(iOS 11.0, *)) {
// Accommodate insets for iPhone X.
insets = self.safeAreaInsets;
}
#endif
return insets;
}
- (CGSize)sizeThatFits:(CGSize)size {
UIEdgeInsets insets = self.mdc_safeAreaInsets;
CGFloat heightWithInset = kMDCBottomAppBarHeight + insets.bottom;
CGSize insetSize = CGSizeMake(size.width, heightWithInset);
return insetSize;
}
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
// Make sure the floating button can always be tapped.
BOOL contains = CGRectContainsPoint(self.floatingButton.frame, point);
if (contains) {
return self.floatingButton;
}
UIView *view = [super hitTest:point withEvent:event];
return view;
}
#pragma mark - CAAnimationDelegate
- (void)animationDidStop:(CAAnimation *)animation finished:(BOOL)flag {
if (flag) {
[self renderPathBasedOnFloatingButtonVisibitlityAnimated:NO];
NSString *animValueForKeyString = [animation valueForKey:kMDCBottomAppBarViewAnimKeyString];
if ([animValueForKeyString isEqualToString:kMDCBottomAppBarViewPathString]) {
[self.bottomBarLayer removeAnimationForKey:kMDCBottomAppBarViewPathString];
} else if ([animValueForKeyString isEqualToString:kMDCBottomAppBarViewPositionString]) {
[self.floatingButton.layer removeAnimationForKey:kMDCBottomAppBarViewPositionString];
}
}
}
#pragma mark - Setters
- (void)setFloatingButton:(MDCFloatingButton *)floatingButton {
if (_floatingButton == floatingButton) {
return;
}
[_floatingButton removeFromSuperview];
_floatingButton = floatingButton;
_floatingButton.translatesAutoresizingMaskIntoConstraints = NO;
[_floatingButton sizeToFit];
}
- (void)setFloatingButtonElevation:(MDCBottomAppBarFloatingButtonElevation)floatingButtonElevation {
[self setFloatingButtonElevation:floatingButtonElevation animated:NO];
}
- (void)setFloatingButtonElevation:(MDCBottomAppBarFloatingButtonElevation)floatingButtonElevation
animated:(BOOL)animated {
if (_floatingButton.superview == self && _floatingButtonElevation == floatingButtonElevation) {
return;
}
_floatingButtonElevation = floatingButtonElevation;
CGFloat elevation = kMDCBottomAppBarViewFloatingButtonElevationPrimary;
NSInteger subViewIndex = 1;
if (floatingButtonElevation == MDCBottomAppBarFloatingButtonElevationSecondary) {
elevation = kMDCBottomAppBarViewFloatingButtonElevationSecondary;
subViewIndex = 0;
}
if (animated) {
[_floatingButton setElevation:1 forState:UIControlStateNormal];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, kMDCButtonAnimationDuration * NSEC_PER_MSEC),
dispatch_get_main_queue(), ^{
[self insertSubview:self.floatingButton atIndex:subViewIndex];
[self.floatingButton setElevation:elevation forState:UIControlStateNormal];
});
} else {
[self insertSubview:_floatingButton atIndex:subViewIndex];
[_floatingButton setElevation:elevation forState:UIControlStateNormal];
}
}
- (void)setFloatingButtonPosition:(MDCBottomAppBarFloatingButtonPosition)floatingButtonPosition {
[self setFloatingButtonPosition:floatingButtonPosition animated:NO];
}
- (void)setFloatingButtonPosition:(MDCBottomAppBarFloatingButtonPosition)floatingButtonPosition
animated:(BOOL)animated {
if (_floatingButtonPosition == floatingButtonPosition) {
return;
}
_floatingButtonPosition = floatingButtonPosition;
[self moveFloatingButtonCenterAnimated:animated];
[self renderPathBasedOnFloatingButtonVisibitlityAnimated:animated];
[self showBarButtonItemsWithFloatingButtonPosition:floatingButtonPosition];
}
- (void)setFloatingButtonHidden:(BOOL)floatingButtonHidden {
[self setFloatingButtonHidden:floatingButtonHidden animated:NO];
}
- (void)setFloatingButtonHidden:(BOOL)floatingButtonHidden animated:(BOOL)animated {
if (_floatingButtonHidden == floatingButtonHidden) {
return;
}
_floatingButtonHidden = floatingButtonHidden;
if (floatingButtonHidden) {
[self healBottomAppBarViewAnimated:animated];
[_floatingButton collapse:animated completion:^{
self.floatingButton.hidden = YES;
}];
} else {
_floatingButton.hidden = NO;
[self cutBottomAppBarViewAnimated:animated];
[_floatingButton expand:animated completion:nil];
}
}
- (void)setLeadingBarButtonItems:(NSArray<UIBarButtonItem *> *)leadingBarButtonItems {
_leadingBarButtonItems = [leadingBarButtonItems copy];
[self showBarButtonItemsWithFloatingButtonPosition:self.floatingButtonPosition];
}
- (void)setTrailingBarButtonItems:(NSArray<UIBarButtonItem *> *)trailingBarButtonItems {
_trailingBarButtonItems = [trailingBarButtonItems copy];
[self showBarButtonItemsWithFloatingButtonPosition:self.floatingButtonPosition];
}
- (void)setBarTintColor:(UIColor *)barTintColor {
_bottomBarLayer.fillColor = barTintColor.CGColor;
}
- (UIColor *)barTintColor {
return [UIColor colorWithCGColor:_bottomBarLayer.fillColor];
}
- (void)setShadowColor:(UIColor *)shadowColor {
_bottomBarLayer.shadowColor = shadowColor.CGColor;
}
- (UIColor *)shadowColor {
return [UIColor colorWithCGColor:_bottomBarLayer.shadowColor];
}
@end