Skip to content

Instantly share code, notes, and snippets.

@josefdlange
Created August 8, 2014 06:01
Show Gist options
  • Select an option

  • Save josefdlange/1df07c011213a8da7001 to your computer and use it in GitHub Desktop.

Select an option

Save josefdlange/1df07c011213a8da7001 to your computer and use it in GitHub Desktop.
Objective-C port of Swift SwipeUpViewController port
//
// 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