Created
August 8, 2014 06:01
-
-
Save josefdlange/1df07c011213a8da7001 to your computer and use it in GitHub Desktop.
Objective-C port of Swift SwipeUpViewController port
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // | |
| // RevealingViewController.m | |
| // | |
| // Created by Lange, Josef on 7/15/14. | |
| // | |
| #define SYSTEM_VERSION_EQUAL_TO(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] == NSOrderedSame) | |
| #define SYSTEM_VERSION_GREATER_THAN(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] == NSOrderedDescending) | |
| #define SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] != NSOrderedAscending) | |
| #define SYSTEM_VERSION_LESS_THAN(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] == NSOrderedAscending) | |
| #define SYSTEM_VERSION_LESS_THAN_OR_EQUAL_TO(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] != NSOrderedDescending) | |
| #import "RevealingViewController.h" | |
| @interface RevealingViewController () | |
| @property (nonatomic, strong) NSLayoutConstraint *topConstraint; | |
| @property (nonatomic, strong) NSLayoutConstraint *bottomConstraint; | |
| @property (nonatomic, strong) NSLayoutConstraint *leftConstraint; | |
| @property (nonatomic, strong) NSLayoutConstraint *rightConstraint; | |
| @property (nonatomic, strong) UIPanGestureRecognizer *gestureRecognizer; | |
| @property (nonatomic, readwrite) CGFloat gestureOrigin; | |
| @property (nonatomic, readwrite) CGFloat gesturePresent; | |
| @property (nonatomic, readwrite) BOOL revealed; | |
| @end | |
| @implementation RevealingViewController | |
| - (void)setBottomViewController: (UIViewController *)bottomViewController { | |
| _bottomViewController = bottomViewController; | |
| [self resetContainedViews]; | |
| } | |
| - (void)setTopViewController: (UIViewController *)topViewController { | |
| _topViewController = topViewController; | |
| [self resetContainedViews]; | |
| } | |
| - (void)viewDidLoad { | |
| [super viewDidLoad]; | |
| [self setupGesture]; | |
| self.revealed = NO; | |
| self.overhang = 100.0f; | |
| // Do any additional setup after loading the view, typically from a nib. | |
| } | |
| - (RVCSwipeDirection)direction { | |
| if(!_direction) { | |
| _direction = RVCSwipeDirectionUp; | |
| } | |
| return _direction; | |
| } | |
| - (void)resetContainedViews { | |
| if(self.bottomViewController) { | |
| if(![self.childViewControllers containsObject:self.bottomViewController]) { | |
| [self addChildViewController:self.bottomViewController]; | |
| } | |
| if([self.view.subviews containsObject:self.bottomViewController.view]) { | |
| [self.bottomViewController.view removeFromSuperview]; | |
| } | |
| [self.view addSubview:self.bottomViewController.view]; | |
| NSDictionary *viewLayoutMapping = @{ @"bottomView": self.bottomViewController.view }; | |
| NSArray *hConstraints = [NSLayoutConstraint constraintsWithVisualFormat:@"H:|-0-[bottomView]-0-|" | |
| options:NSLayoutFormatAlignAllCenterX | |
| metrics:nil | |
| views:viewLayoutMapping]; | |
| NSArray *vConstraints = [NSLayoutConstraint constraintsWithVisualFormat:@"V:|-0-[bottomView]-0-|" | |
| options:NSLayoutFormatAlignAllCenterY | |
| metrics:nil | |
| views:viewLayoutMapping]; | |
| [self.view addConstraints:[hConstraints arrayByAddingObjectsFromArray:vConstraints]]; | |
| } | |
| if(self.topViewController) { | |
| if(![self.childViewControllers containsObject:self.topViewController]) { | |
| [self addChildViewController:self.topViewController]; | |
| } | |
| if([self.view.subviews containsObject:self.topViewController.view]) { | |
| [self.topViewController.view removeFromSuperview]; | |
| } | |
| [self.view addSubview:self.topViewController.view]; | |
| NSDictionary *viewLayoutMapping = @{ @"topView": self.topViewController.view }; | |
| self.topConstraint = [[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-0-[topView]" | |
| options:NSLayoutFormatAlignAllTop | |
| metrics:nil | |
| views:viewLayoutMapping] firstObject]; | |
| self.bottomConstraint = [[NSLayoutConstraint constraintsWithVisualFormat:@"V:[topView]-0-|" | |
| options:NSLayoutFormatAlignAllBottom | |
| metrics:nil | |
| views:viewLayoutMapping] firstObject]; | |
| self.leftConstraint = [[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-0-[topView]" | |
| options:NSLayoutFormatAlignAllLeading | |
| metrics:nil | |
| views:viewLayoutMapping] firstObject]; | |
| self.rightConstraint = [[NSLayoutConstraint constraintsWithVisualFormat:@"H:[topView]-0-|" | |
| options:NSLayoutFormatAlignAllTrailing | |
| metrics:nil | |
| views:viewLayoutMapping] firstObject]; | |
| NSDictionary *views = @{ @"topView": self.topViewController.view, | |
| @"bottomView": self.view }; | |
| NSArray *hWidth = [NSLayoutConstraint constraintsWithVisualFormat:@"H:[topView(==bottomView)]" | |
| options:NSLayoutFormatAlignAllCenterX | |
| metrics:nil | |
| views:views]; | |
| NSArray *vHeight = [NSLayoutConstraint constraintsWithVisualFormat:@"V:[topView(==bottomView)]" | |
| options:NSLayoutFormatAlignAllCenterY | |
| metrics:nil | |
| views:views]; | |
| [self.view addConstraint:[self directionalConstraint]]; | |
| [self.view addConstraints:[self adjacentConstraints:[self directionalConstraint]]]; | |
| [self.view addConstraints:hWidth]; | |
| [self.view addConstraints:vHeight]; | |
| [self.topViewController.view addGestureRecognizer:self.gestureRecognizer]; | |
| } | |
| } | |
| - (void)setupGesture { | |
| self.gestureRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handlePan:)]; | |
| } | |
| - (NSLayoutConstraint *)directionalConstraint { | |
| switch (self.direction) { | |
| case RVCSwipeDirectionUp: | |
| return self.bottomConstraint; | |
| break; | |
| case RVCSwipeDirectionDown: | |
| return self.topConstraint; | |
| break; | |
| case RVCSwipeDirectionLeft: | |
| return self.leftConstraint; | |
| break; | |
| case RVCSwipeDirectionRight: | |
| return self.rightConstraint; | |
| break; | |
| default: | |
| return nil; | |
| break; | |
| } | |
| } | |
| - (NSArray *)adjacentConstraints:(NSLayoutConstraint *)constraint { | |
| if(constraint == self.rightConstraint || constraint == self.leftConstraint) { | |
| return [NSArray arrayWithObjects:self.topConstraint, self.bottomConstraint, nil]; | |
| } else if (constraint == self.topConstraint || constraint == self.bottomConstraint) { | |
| return [NSArray arrayWithObjects:self.leftConstraint, self.rightConstraint, nil]; | |
| } else { | |
| return nil; | |
| } | |
| } | |
| - (CGFloat)directionalTranslation:(UIPanGestureRecognizer *)recognizer { | |
| switch (self.direction) { | |
| case RVCSwipeDirectionUp: | |
| return [recognizer translationInView:self.topViewController.view].y; | |
| break; | |
| case RVCSwipeDirectionDown: | |
| return [recognizer translationInView:self.topViewController.view].y; | |
| break; | |
| case RVCSwipeDirectionLeft: | |
| return [recognizer translationInView:self.topViewController.view].x; | |
| break; | |
| case RVCSwipeDirectionRight: | |
| return [recognizer translationInView:self.topViewController.view].x; | |
| break; | |
| default: | |
| return 0.0; | |
| break; | |
| } | |
| } | |
| - (CGFloat)directionalVelocity:(UIPanGestureRecognizer *)recognizer { | |
| switch (self.direction) { | |
| case RVCSwipeDirectionUp: | |
| return [recognizer velocityInView:self.topViewController.view].y; | |
| break; | |
| case RVCSwipeDirectionDown: | |
| return [recognizer velocityInView:self.topViewController.view].y; | |
| break; | |
| case RVCSwipeDirectionLeft: | |
| return [recognizer velocityInView:self.topViewController.view].x; | |
| break; | |
| case RVCSwipeDirectionRight: | |
| return [recognizer velocityInView:self.topViewController.view].x; | |
| break; | |
| default: | |
| return 0.0; | |
| break; | |
| } | |
| } | |
| - (CGFloat)overhangedForSize:(CGSize)size { | |
| switch (self.direction) { | |
| case RVCSwipeDirectionUp: | |
| return size.height - self.overhang; | |
| break; | |
| case RVCSwipeDirectionDown: | |
| return size.height - self.overhang; | |
| break; | |
| case RVCSwipeDirectionLeft: | |
| return size.width - self.overhang; | |
| break; | |
| case RVCSwipeDirectionRight: | |
| return size.width - self.overhang; | |
| break; | |
| default: | |
| return 0.0; | |
| break; | |
| } | |
| } | |
| - (CGFloat)directionalFlip { | |
| if (self.direction == RVCSwipeDirectionUp || self.direction == RVCSwipeDirectionRight) { | |
| return -1.0; | |
| } else { | |
| return 1.0; | |
| } | |
| } | |
| - (void)handlePan:(UIPanGestureRecognizer *)recognizer { | |
| NSLayoutConstraint *constraint = [self directionalConstraint]; | |
| CGFloat flip = [self directionalFlip]; | |
| CGFloat translation = [self directionalTranslation:recognizer]; | |
| CGFloat velocity = [self directionalVelocity:recognizer]; | |
| if(recognizer.state == UIGestureRecognizerStateBegan) { | |
| self.gestureOrigin = constraint.constant; | |
| } | |
| CGFloat minimumBound = 0.0; | |
| CGFloat maximumBound = [self overhangedForSize:self.view.bounds.size]; | |
| CGFloat adjustedConstant = self.gestureOrigin + (flip * translation); | |
| if(minimumBound <= adjustedConstant && adjustedConstant <= maximumBound) { | |
| constraint.constant = adjustedConstant; | |
| [self.view setNeedsLayout]; | |
| } | |
| if(recognizer.state == UIGestureRecognizerStateEnded) { | |
| CGFloat destination = ((flip * velocity < 0)==(abs(velocity) > 500)) ? minimumBound : maximumBound; | |
| CGFloat distance = abs(destination - constraint.constant); | |
| CGFloat time = (distance / velocity); | |
| constraint.constant = destination; | |
| if(time > 1.0) { time = 0.3; } | |
| [UIView animateWithDuration:time delay:0.0 options:UIViewAnimationOptionCurveEaseOut animations:^(void) { | |
| [self.view layoutIfNeeded]; | |
| } completion:nil]; | |
| if(constraint.constant == maximumBound) { | |
| self.revealed = YES; | |
| } else { | |
| self.revealed = NO; | |
| } | |
| } | |
| } | |
| - (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration { | |
| if(SYSTEM_VERSION_LESS_THAN(@"8")) { | |
| if(self.revealed) { | |
| CGSize newSize = self.view.frame.size; | |
| if(UIInterfaceOrientationIsLandscape(toInterfaceOrientation)) { | |
| newSize = CGSizeMake(newSize.height,newSize.width); | |
| } | |
| CGFloat newConstraintConstant = [self overhangedForSize:newSize]; | |
| NSLayoutConstraint *constraint = [self directionalConstraint]; | |
| if(newConstraintConstant && constraint) { | |
| [self.view removeConstraint:constraint]; | |
| constraint.constant = newConstraintConstant; | |
| [self.view addConstraint:constraint]; | |
| [self.view layoutIfNeeded]; | |
| } | |
| } | |
| } | |
| } | |
| - (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator { | |
| [super viewWillTransitionToSize:size withTransitionCoordinator:coordinator]; | |
| if(self.revealed) { | |
| CGFloat newConstraintConstant = [self overhangedForSize:size]; | |
| NSLayoutConstraint *constraint = [self directionalConstraint]; | |
| if(newConstraintConstant && constraint) { | |
| [self.view removeConstraint:constraint]; | |
| constraint.constant = newConstraintConstant; | |
| [self.view addConstraint:constraint]; | |
| [self.view layoutIfNeeded]; | |
| } | |
| } | |
| } | |
| - (void)toggleRevealAnimated:(BOOL)animated { | |
| CGFloat newConstraint = (self.revealed) ? 0.0 : [self overhangedForSize:self.view.frame.size]; | |
| NSLayoutConstraint *constraint = [self directionalConstraint]; | |
| constraint.constant = newConstraint; | |
| if(animated) { | |
| [UIView animateWithDuration:0.5 animations:^(void) { | |
| [self.view layoutIfNeeded]; | |
| }]; | |
| } else { | |
| [self.view layoutIfNeeded]; | |
| } | |
| self.revealed = !self.revealed; | |
| } | |
| - (void)didReceiveMemoryWarning { | |
| [super didReceiveMemoryWarning]; | |
| // Dispose of any resources that can be recreated. | |
| } | |
| @end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment