blob: 82e216c09358459252bcac5b2a42bbcc88558330 [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 "MDCBottomSheetPresentationController.h"
#import "MDCBottomSheetController.h"
#import "MaterialMath.h"
#import "private/MDCSheetContainerView.h"
static UIScrollView *MDCBottomSheetGetPrimaryScrollView(UIViewController *viewController) {
UIScrollView *scrollView = nil;
// Ensure the view is loaded - occasionally during non-animated transitions the view may not be
// loaded yet (but the scrollview is still needed for scroll-tracking to work properly).
if (![viewController isViewLoaded]) {
(void)viewController.view;
}
if ([viewController isKindOfClass:[MDCBottomSheetController class]]) {
viewController = ((MDCBottomSheetController *)viewController).contentViewController;
}
if ([viewController.view isKindOfClass:[UIScrollView class]]) {
scrollView = (UIScrollView *)viewController.view;
} else if ([viewController.view isKindOfClass:[UIWebView class]]) {
scrollView = ((UIWebView *)viewController.view).scrollView;
} else if ([viewController isKindOfClass:[UICollectionViewController class]]) {
scrollView = ((UICollectionViewController *)viewController).collectionView;
}
return scrollView;
}
@interface MDCBottomSheetPresentationController () <MDCSheetContainerViewDelegate>
@end
@implementation MDCBottomSheetPresentationController {
UIView *_dimmingView;
MDCSheetContainerView *_sheetView;
}
@synthesize delegate;
- (UIView *)presentedView {
return _sheetView;
}
- (CGRect)frameOfPresentedViewInContainerView {
CGSize containerSize = self.containerView.frame.size;
CGSize preferredSize = self.presentedViewController.preferredContentSize;
if (preferredSize.width > 0 && preferredSize.width < containerSize.width) {
// We only customize the width and not the height here. MDCSheetContainerView lays out the
// presentedView taking the preferred height in to account.
CGFloat width = preferredSize.width;
CGFloat leftPad = (containerSize.width - width) / 2;
return CGRectMake(leftPad, 0, width, containerSize.height);
} else {
return [super frameOfPresentedViewInContainerView];
}
}
- (void)presentationTransitionWillBegin {
id<MDCBottomSheetPresentationControllerDelegate> strongDelegate = self.delegate;
if ([strongDelegate respondsToSelector:@selector(prepareForBottomSheetPresentation:)]) {
[strongDelegate prepareForBottomSheetPresentation:self];
}
UIView *containerView = [self containerView];
_dimmingView = [[UIView alloc] initWithFrame:self.containerView.bounds];
_dimmingView.backgroundColor = [UIColor colorWithWhite:0 alpha:0.4f];
_dimmingView.translatesAutoresizingMaskIntoConstraints = NO;
_dimmingView.autoresizingMask =
UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
UIScrollView *scrollView = self.trackingScrollView;
if (scrollView == nil) {
scrollView = MDCBottomSheetGetPrimaryScrollView(self.presentedViewController);
}
CGRect sheetFrame = [self frameOfPresentedViewInContainerView];
_sheetView = [[MDCSheetContainerView alloc] initWithFrame:sheetFrame
contentView:self.presentedViewController.view
scrollView:scrollView];
_sheetView.delegate = self;
_sheetView.autoresizingMask = UIViewAutoresizingFlexibleHeight;
[containerView addSubview:_dimmingView];
[containerView addSubview:_sheetView];
[self updatePreferredSheetHeight];
// Add tap handler to dismiss the sheet.
UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self
action:
@selector(dismissPresentedControllerIfNecessary:)];
tapGesture.cancelsTouchesInView = NO;
containerView.userInteractionEnabled = YES;
[containerView addGestureRecognizer:tapGesture];
id <UIViewControllerTransitionCoordinator> transitionCoordinator =
[[self presentingViewController] transitionCoordinator];
// Fade in the dimming view during the transition.
_dimmingView.alpha = 0.0;
[transitionCoordinator animateAlongsideTransition:
^(__unused id<UIViewControllerTransitionCoordinatorContext> context) {
self->_dimmingView.alpha = 1.0;
} completion:nil];
}
- (void)presentationTransitionDidEnd:(BOOL)completed {
if (!completed) {
[_dimmingView removeFromSuperview];
}
}
- (void)dismissalTransitionWillBegin {
id <UIViewControllerTransitionCoordinator> transitionCoordinator =
[[self presentingViewController] transitionCoordinator];
[transitionCoordinator animateAlongsideTransition:
^(__unused id<UIViewControllerTransitionCoordinatorContext> context) {
self->_dimmingView.alpha = 0.0;
} completion:nil];
}
- (void)dismissalTransitionDidEnd:(BOOL)completed {
if (completed) {
[_dimmingView removeFromSuperview];
}
}
- (void)preferredContentSizeDidChangeForChildContentContainer:(id<UIContentContainer>)container {
[super preferredContentSizeDidChangeForChildContentContainer:container];
_sheetView.frame = [self frameOfPresentedViewInContainerView];
[_sheetView layoutIfNeeded];
[self updatePreferredSheetHeight];
}
- (void)viewWillTransitionToSize:(CGSize)size
withTransitionCoordinator:(id <UIViewControllerTransitionCoordinator>)coordinator {
[super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];
[coordinator animateAlongsideTransition:
^(__unused id<UIViewControllerTransitionCoordinatorContext> _Nonnull context) {
self->_sheetView.frame = [self frameOfPresentedViewInContainerView];
[self->_sheetView layoutIfNeeded];
[self updatePreferredSheetHeight];
} completion:nil];
}
- (void)updatePreferredSheetHeight {
CGFloat preferredContentHeight = self.presentedViewController.preferredContentSize.height;
// If |preferredSheetHeight| has not been specified, use half of the current height.
if (MDCCGFloatEqual(preferredContentHeight, 0)) {
preferredContentHeight = MDCRound(_sheetView.frame.size.height / 2);
}
_sheetView.preferredSheetHeight = preferredContentHeight;
}
- (void)dismissPresentedControllerIfNecessary:(UITapGestureRecognizer *)tapRecognizer {
if (!_dismissOnBackgroundTap) {
return;
}
// Only dismiss if the tap is outside of the presented view.
UIView *contentView = self.presentedViewController.view;
CGPoint pointInContentView = [tapRecognizer locationInView:contentView];
if ([contentView pointInside:pointInContentView withEvent:nil]) {
return;
}
[self.presentingViewController dismissViewControllerAnimated:YES completion:nil];
id<MDCBottomSheetPresentationControllerDelegate> strongDelegate = self.delegate;
if ([strongDelegate respondsToSelector:
@selector(bottomSheetPresentationControllerDidDismissBottomSheet:)]) {
[strongDelegate bottomSheetPresentationControllerDidDismissBottomSheet:self];
}
}
#pragma mark - MDCSheetContainerViewDelegate
- (void)sheetContainerViewDidHide:(nonnull __unused MDCSheetContainerView *)containerView {
[self.presentingViewController dismissViewControllerAnimated:YES completion:nil];
id<MDCBottomSheetPresentationControllerDelegate> strongDelegate = self.delegate;
if ([strongDelegate respondsToSelector:
@selector(bottomSheetPresentationControllerDidDismissBottomSheet:)]) {
[strongDelegate bottomSheetPresentationControllerDidDismissBottomSheet:self];
}
}
@end