123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564 |
- //
- // ICSDrawerController.m
- //
- // Created by Vito Modena
- //
- // Copyright (c) 2016 ice cream studios s.r.l. - http://icecreamstudios.com
- //
- // Permission is hereby granted, free of charge, to any person obtaining a copy of
- // this software and associated documentation files (the "Software"), to deal in
- // the Software without restriction, including without limitation the rights to
- // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
- // the Software, and to permit persons to whom the Software is furnished to do so,
- // subject to the following conditions:
- //
- // The above copyright notice and this permission notice shall be included in all
- // copies or substantial portions of the Software.
- //
- // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
- // FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
- // COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
- // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
- // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
- #import "ICSDrawerController.h"
- #import "ICSDropShadowView.h"
- static const CGFloat kICSDrawerControllerDrawerDepth = 260.0f;
- static const CGFloat kICSDrawerControllerLeftViewInitialOffset = -60.0f;
- static const NSTimeInterval kICSDrawerControllerAnimationDuration = 0.5;
- static const CGFloat kICSDrawerControllerOpeningAnimationSpringDamping = 0.7f;
- static const CGFloat kICSDrawerControllerOpeningAnimationSpringInitialVelocity = 0.1f;
- static const CGFloat kICSDrawerControllerClosingAnimationSpringDamping = 1.0f;
- static const CGFloat kICSDrawerControllerClosingAnimationSpringInitialVelocity = 0.5f;
- typedef NS_ENUM(NSUInteger, ICSDrawerControllerState)
- {
- ICSDrawerControllerStateClosed = 0,
- ICSDrawerControllerStateOpening,
- ICSDrawerControllerStateOpen,
- ICSDrawerControllerStateClosing
- };
- @interface ICSDrawerController () <UIGestureRecognizerDelegate>
- @property(nonatomic, strong, readwrite) UIViewController<ICSDrawerControllerChild, ICSDrawerControllerPresenting> *leftViewController;
- @property(nonatomic, strong, readwrite) UIViewController<ICSDrawerControllerChild, ICSDrawerControllerPresenting> *centerViewController;
- @property(nonatomic, strong) UIView *leftView;
- @property(nonatomic, strong) ICSDropShadowView *centerView;
- @property(nonatomic, strong) UITapGestureRecognizer *tapGestureRecognizer;
- @property(nonatomic, strong) UIPanGestureRecognizer *panGestureRecognizer;
- @property(nonatomic, assign) CGPoint panGestureStartLocation;
- @property(nonatomic, assign) ICSDrawerControllerState drawerState;
- @end
- @implementation ICSDrawerController
- - (id)initWithLeftViewController:(UIViewController<ICSDrawerControllerChild, ICSDrawerControllerPresenting> *)leftViewController
- centerViewController:(UIViewController<ICSDrawerControllerChild, ICSDrawerControllerPresenting> *)centerViewController
- {
- NSParameterAssert(leftViewController);
- NSParameterAssert(centerViewController);
-
- self = [super init];
- if (self) {
- _leftViewController = leftViewController;
- _centerViewController = centerViewController;
-
- if ([_leftViewController respondsToSelector:@selector(setDrawer:)]) {
- _leftViewController.drawer = self;
- }
- if ([_centerViewController respondsToSelector:@selector(setDrawer:)]) {
- _centerViewController.drawer = self;
- }
- }
-
- return self;
- }
- - (void)addCenterViewController
- {
- NSParameterAssert(self.centerViewController);
- NSParameterAssert(self.centerView);
-
- [self addChildViewController:self.centerViewController];
- self.centerViewController.view.frame = self.view.bounds;
- [self.centerView addSubview:self.centerViewController.view];
- [self.centerViewController didMoveToParentViewController:self];
- }
- #pragma mark - Managing the view
- - (void)viewDidLoad
- {
- [super viewDidLoad];
-
- self.view.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
-
- // Initialize left and center view containers
- self.leftView = [[UIView alloc] initWithFrame:self.view.bounds];
- self.centerView = [[ICSDropShadowView alloc] initWithFrame:self.view.bounds];
- self.leftView.autoresizingMask = self.view.autoresizingMask;
- self.centerView.autoresizingMask = self.view.autoresizingMask;
-
- // Add the center view container
- [self.view addSubview:self.centerView];
- // Add the center view controller to the container
- [self addCenterViewController];
- [self setupGestureRecognizers];
- }
- #pragma mark - Configuring the view’s layout behavior
- - (UIViewController *)childViewControllerForStatusBarHidden
- {
- NSParameterAssert(self.leftViewController);
- NSParameterAssert(self.centerViewController);
-
- if (self.drawerState == ICSDrawerControllerStateOpening) {
- return self.leftViewController;
- }
- return self.centerViewController;
- }
- - (UIViewController *)childViewControllerForStatusBarStyle
- {
- NSParameterAssert(self.leftViewController);
- NSParameterAssert(self.centerViewController);
-
- if (self.drawerState == ICSDrawerControllerStateOpening) {
- return self.leftViewController;
- }
- return self.centerViewController;
- }
- #pragma mark - Gesture recognizers
- - (void)setupGestureRecognizers
- {
- NSParameterAssert(self.centerView);
-
- self.tapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapGestureRecognized:)];
- self.panGestureRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(panGestureRecognized:)];
- self.panGestureRecognizer.maximumNumberOfTouches = 1;
- self.panGestureRecognizer.delegate = self;
-
- [self.centerView addGestureRecognizer:self.panGestureRecognizer];
- }
- - (void)addClosingGestureRecognizers
- {
- NSParameterAssert(self.centerView);
- NSParameterAssert(self.panGestureRecognizer);
-
- [self.centerView addGestureRecognizer:self.tapGestureRecognizer];
- }
- - (void)removeClosingGestureRecognizers
- {
- NSParameterAssert(self.centerView);
- NSParameterAssert(self.panGestureRecognizer);
- [self.centerView removeGestureRecognizer:self.tapGestureRecognizer];
- }
- #pragma mark Tap to close the drawer
- - (void)tapGestureRecognized:(UITapGestureRecognizer *)tapGestureRecognizer
- {
- if (tapGestureRecognizer.state == UIGestureRecognizerStateEnded) {
- [self close];
- }
- }
- #pragma mark Pan to open/close the drawer
- - (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer
- {
- NSParameterAssert([gestureRecognizer isKindOfClass:[UIPanGestureRecognizer class]]);
- CGPoint velocity = [(UIPanGestureRecognizer *)gestureRecognizer velocityInView:self.view];
-
- if (self.drawerState == ICSDrawerControllerStateClosed && velocity.x > 0.0f) {
- return YES;
- }
- else if (self.drawerState == ICSDrawerControllerStateOpen && velocity.x < 0.0f) {
- return YES;
- }
-
- return NO;
- }
- - (void)panGestureRecognized:(UIPanGestureRecognizer *)panGestureRecognizer
- {
- NSParameterAssert(self.leftView);
- NSParameterAssert(self.centerView);
-
- UIGestureRecognizerState state = panGestureRecognizer.state;
- CGPoint location = [panGestureRecognizer locationInView:self.view];
- CGPoint velocity = [panGestureRecognizer velocityInView:self.view];
-
- switch (state) {
- case UIGestureRecognizerStateBegan:
- self.panGestureStartLocation = location;
- if (self.drawerState == ICSDrawerControllerStateClosed) {
- [self willOpen];
- }
- else {
- [self willClose];
- }
- break;
-
- case UIGestureRecognizerStateChanged:
- {
- CGFloat delta = 0.0f;
- if (self.drawerState == ICSDrawerControllerStateOpening) {
- delta = location.x - self.panGestureStartLocation.x;
- }
- else if (self.drawerState == ICSDrawerControllerStateClosing) {
- delta = kICSDrawerControllerDrawerDepth - (self.panGestureStartLocation.x - location.x);
- }
-
- CGRect l = self.leftView.frame;
- CGRect c = self.centerView.frame;
- if (delta > kICSDrawerControllerDrawerDepth) {
- l.origin.x = 0.0f;
- c.origin.x = kICSDrawerControllerDrawerDepth;
- }
- else if (delta < 0.0f) {
- l.origin.x = kICSDrawerControllerLeftViewInitialOffset;
- c.origin.x = 0.0f;
- }
- else {
- // While the centerView can move up to kICSDrawerControllerDrawerDepth points, to achieve a parallax effect
- // the leftView has move no more than kICSDrawerControllerLeftViewInitialOffset points
- l.origin.x = kICSDrawerControllerLeftViewInitialOffset
- - (delta * kICSDrawerControllerLeftViewInitialOffset) / kICSDrawerControllerDrawerDepth;
- c.origin.x = delta;
- }
-
- self.leftView.frame = l;
- self.centerView.frame = c;
-
- break;
- }
-
- case UIGestureRecognizerStateEnded:
- if (self.drawerState == ICSDrawerControllerStateOpening) {
- CGFloat centerViewLocation = self.centerView.frame.origin.x;
- if (centerViewLocation == kICSDrawerControllerDrawerDepth) {
- // Open the drawer without animation, as it has already being dragged in its final position
- [self setNeedsStatusBarAppearanceUpdate];
- [self didOpen];
- }
- else if (centerViewLocation > self.view.bounds.size.width / 3
- && velocity.x > 0.0f) {
- // Animate the drawer opening
- [self animateOpening];
- }
- else {
- // Animate the drawer closing, as the opening gesture hasn't been completed or it has
- // been reverted by the user
- [self didOpen];
- [self willClose];
- [self animateClosing];
- }
- } else if (self.drawerState == ICSDrawerControllerStateClosing) {
- CGFloat centerViewLocation = self.centerView.frame.origin.x;
- if (centerViewLocation == 0.0f) {
- // Close the drawer without animation, as it has already being dragged in its final position
- [self setNeedsStatusBarAppearanceUpdate];
- [self didClose];
- }
- else if (centerViewLocation < (2 * self.view.bounds.size.width) / 3
- && velocity.x < 0.0f) {
- // Animate the drawer closing
- [self animateClosing];
- }
- else {
- // Animate the drawer opening, as the opening gesture hasn't been completed or it has
- // been reverted by the user
- [self didClose];
- // Here we save the current position for the leftView since
- // we want the opening animation to start from the current position
- // and not the one that is set in 'willOpen'
- CGRect l = self.leftView.frame;
- [self willOpen];
- self.leftView.frame = l;
-
- [self animateOpening];
- }
- }
- break;
-
- default:
- break;
- }
- }
- #pragma mark - Animations
- #pragma mark Opening animation
- - (void)animateOpening
- {
- NSParameterAssert(self.drawerState == ICSDrawerControllerStateOpening);
- NSParameterAssert(self.leftView);
- NSParameterAssert(self.centerView);
-
- // Calculate the final frames for the container views
- CGRect leftViewFinalFrame = self.view.bounds;
- CGRect centerViewFinalFrame = self.view.bounds;
- centerViewFinalFrame.origin.x = kICSDrawerControllerDrawerDepth;
-
- [UIView animateWithDuration:kICSDrawerControllerAnimationDuration
- delay:0
- usingSpringWithDamping:kICSDrawerControllerOpeningAnimationSpringDamping
- initialSpringVelocity:kICSDrawerControllerOpeningAnimationSpringInitialVelocity
- options:UIViewAnimationOptionCurveLinear
- animations:^{
- self.centerView.frame = centerViewFinalFrame;
- self.leftView.frame = leftViewFinalFrame;
-
- [self setNeedsStatusBarAppearanceUpdate];
- }
- completion:^(BOOL finished) {
- [self didOpen];
- }];
- }
- #pragma mark Closing animation
- - (void)animateClosing
- {
- NSParameterAssert(self.drawerState == ICSDrawerControllerStateClosing);
- NSParameterAssert(self.leftView);
- NSParameterAssert(self.centerView);
-
- // Calculate final frames for the container views
- CGRect leftViewFinalFrame = self.leftView.frame;
- leftViewFinalFrame.origin.x = kICSDrawerControllerLeftViewInitialOffset;
- CGRect centerViewFinalFrame = self.view.bounds;
-
- [UIView animateWithDuration:kICSDrawerControllerAnimationDuration
- delay:0
- usingSpringWithDamping:kICSDrawerControllerClosingAnimationSpringDamping
- initialSpringVelocity:kICSDrawerControllerClosingAnimationSpringInitialVelocity
- options:UIViewAnimationOptionCurveLinear
- animations:^{
- self.centerView.frame = centerViewFinalFrame;
- self.leftView.frame = leftViewFinalFrame;
-
- [self setNeedsStatusBarAppearanceUpdate];
- }
- completion:^(BOOL finished) {
- [self didClose];
- }];
- }
- #pragma mark - Opening the drawer
- - (void)open
- {
- NSParameterAssert(self.drawerState == ICSDrawerControllerStateClosed);
- [self willOpen];
-
- [self animateOpening];
- }
- - (void)willOpen
- {
- NSParameterAssert(self.drawerState == ICSDrawerControllerStateClosed);
- NSParameterAssert(self.leftView);
- NSParameterAssert(self.centerView);
- NSParameterAssert(self.leftViewController);
- NSParameterAssert(self.centerViewController);
-
- // Keep track that the drawer is opening
- self.drawerState = ICSDrawerControllerStateOpening;
-
- // Position the left view
- CGRect f = self.view.bounds;
- f.origin.x = kICSDrawerControllerLeftViewInitialOffset;
- NSParameterAssert(f.origin.x < 0.0f);
- self.leftView.frame = f;
-
- // Start adding the left view controller to the container
- [self addChildViewController:self.leftViewController];
- self.leftViewController.view.frame = self.leftView.bounds;
- [self.leftView addSubview:self.leftViewController.view];
- // Add the left view to the view hierarchy
- [self.view insertSubview:self.leftView belowSubview:self.centerView];
-
- // Notify the child view controllers that the drawer is about to open
- if ([self.leftViewController respondsToSelector:@selector(drawerControllerWillOpen:)]) {
- [self.leftViewController drawerControllerWillOpen:self];
- }
- if ([self.centerViewController respondsToSelector:@selector(drawerControllerWillOpen:)]) {
- [self.centerViewController drawerControllerWillOpen:self];
- }
- }
- - (void)didOpen
- {
- NSParameterAssert(self.drawerState == ICSDrawerControllerStateOpening);
- NSParameterAssert(self.leftViewController);
- NSParameterAssert(self.centerViewController);
-
- // Complete adding the left controller to the container
- [self.leftViewController didMoveToParentViewController:self];
-
- [self addClosingGestureRecognizers];
-
- // Keep track that the drawer is open
- self.drawerState = ICSDrawerControllerStateOpen;
-
- // Notify the child view controllers that the drawer is open
- if ([self.leftViewController respondsToSelector:@selector(drawerControllerDidOpen:)]) {
- [self.leftViewController drawerControllerDidOpen:self];
- }
- if ([self.centerViewController respondsToSelector:@selector(drawerControllerDidOpen:)]) {
- [self.centerViewController drawerControllerDidOpen:self];
- }
- }
- #pragma mark - Closing the drawer
- - (void)close
- {
- NSParameterAssert(self.drawerState == ICSDrawerControllerStateOpen);
- [self willClose];
- [self animateClosing];
- }
- - (void)willClose
- {
- NSParameterAssert(self.drawerState == ICSDrawerControllerStateOpen);
- NSParameterAssert(self.leftViewController);
- NSParameterAssert(self.centerViewController);
-
- // Start removing the left controller from the container
- [self.leftViewController willMoveToParentViewController:nil];
-
- // Keep track that the drawer is closing
- self.drawerState = ICSDrawerControllerStateClosing;
-
- // Notify the child view controllers that the drawer is about to close
- if ([self.leftViewController respondsToSelector:@selector(drawerControllerWillClose:)]) {
- [self.leftViewController drawerControllerWillClose:self];
- }
- if ([self.centerViewController respondsToSelector:@selector(drawerControllerWillClose:)]) {
- [self.centerViewController drawerControllerWillClose:self];
- }
- }
- - (void)didClose
- {
- NSParameterAssert(self.drawerState == ICSDrawerControllerStateClosing);
- NSParameterAssert(self.leftView);
- NSParameterAssert(self.centerView);
- NSParameterAssert(self.leftViewController);
- NSParameterAssert(self.centerViewController);
-
- // Complete removing the left view controller from the container
- [self.leftViewController.view removeFromSuperview];
- [self.leftViewController removeFromParentViewController];
-
- // Remove the left view from the view hierarchy
- [self.leftView removeFromSuperview];
-
- [self removeClosingGestureRecognizers];
-
- // Keep track that the drawer is closed
- self.drawerState = ICSDrawerControllerStateClosed;
-
- // Notify the child view controllers that the drawer is closed
- if ([self.leftViewController respondsToSelector:@selector(drawerControllerDidClose:)]) {
- [self.leftViewController drawerControllerDidClose:self];
- }
- if ([self.centerViewController respondsToSelector:@selector(drawerControllerDidClose:)]) {
- [self.centerViewController drawerControllerDidClose:self];
- }
- }
- #pragma mark - Reloading/Replacing the center view controller
- - (void)reloadCenterViewControllerUsingBlock:(void (^)(void))reloadBlock
- {
- NSParameterAssert(self.drawerState == ICSDrawerControllerStateOpen);
- NSParameterAssert(self.centerViewController);
-
- [self willClose];
-
- CGRect f = self.centerView.frame;
- f.origin.x = self.view.bounds.size.width;
-
- [UIView animateWithDuration: kICSDrawerControllerAnimationDuration / 2
- animations:^{
- self.centerView.frame = f;
- }
- completion:^(BOOL finished) {
- // The center view controller is now out of sight
- if (reloadBlock) {
- reloadBlock();
- }
- // Finally, close the drawer
- [self animateClosing];
- }];
- }
- - (void)replaceCenterViewControllerWithViewController:(UIViewController<ICSDrawerControllerChild, ICSDrawerControllerPresenting> *)viewController
- {
- NSParameterAssert(self.drawerState == ICSDrawerControllerStateOpen);
- NSParameterAssert(viewController);
- NSParameterAssert(self.centerView);
- NSParameterAssert(self.centerViewController);
-
- [self willClose];
-
- CGRect f = self.centerView.frame;
- f.origin.x = self.view.bounds.size.width;
-
- [self.centerViewController willMoveToParentViewController:nil];
- [UIView animateWithDuration: kICSDrawerControllerAnimationDuration / 2
- animations:^{
- self.centerView.frame = f;
- }
- completion:^(BOOL finished) {
- // The center view controller is now out of sight
-
- // Remove the current center view controller from the container
- if ([self.centerViewController respondsToSelector:@selector(setDrawer:)]) {
- self.centerViewController.drawer = nil;
- }
- [self.centerViewController.view removeFromSuperview];
- [self.centerViewController removeFromParentViewController];
-
- // Set the new center view controller
- self.centerViewController = viewController;
- if ([self.centerViewController respondsToSelector:@selector(setDrawer:)]) {
- self.centerViewController.drawer = self;
- }
-
- // Add the new center view controller to the container
- [self addCenterViewController];
-
- // Finally, close the drawer
- [self animateClosing];
- }];
- }
- @end
|