Created
December 2, 2016 19:12
-
-
Save qubblr/f37ceb6633af84ee4ee334793314bb1d to your computer and use it in GitHub Desktop.
Container controller that hides both tab and navigation bars when scrolling.
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
// | |
// TAMHidingBarsViewController.h | |
// | |
// Created by Vladislav Kartashov on 13/07/15. | |
// Copyright (c) 2015 Vladislav Kartashov. All rights reserved. | |
// | |
#import <UIKit/UIKit.h> | |
@interface TAMHidingBarsViewController : UIViewController <UINavigationControllerDelegate> | |
@property (nonatomic, readonly) BOOL navigationBarIsHidden; | |
@property (nonatomic) BOOL hideTabBar; | |
@property (nonatomic) BOOL enabled; | |
- (instancetype)initWithViewController:(UIViewController *)controller; | |
- (void)hideTabBarWithDuration:(NSTimeInterval)duration; | |
- (void)showNavigationBarWithDuration:(NSTimeInterval)duration; | |
- (void)showBars:(BOOL)animated; | |
- (void)hideBars:(BOOL)animated; | |
- (void)enableBarsHiding; | |
- (void)disableBarsHiding; | |
@property (nonatomic, copy) void(^willHideNavigationBarBlock)(); | |
@property (nonatomic, copy) void(^willShowNavigationBarBlock)(); | |
- (void)willHideNavigationBar; | |
- (void)didHideNavigationBar; | |
- (void)willShowNavigationBar; | |
- (void)didShowNavigationBar; | |
@end |
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
// | |
// TAMHidingBarsViewController.m | |
// | |
// Created by Vladislav Kartashov on 13/07/15. | |
// Copyright (c) 2015 Vladislav Kartashov. All rights reserved. | |
// | |
#import "TAMHidingBarsViewController.h" | |
#import "TAMAppearance.h" | |
#import "XOGlobalVars.h" | |
@interface TAMHidingBarsViewController () <UIGestureRecognizerDelegate> | |
@property (nonatomic) UIViewController *rootViewController; | |
@property (nonatomic, weak) IBOutlet UIView *containerView; | |
@property (nonatomic) CGRect navigationBarFrame; | |
@property (nonatomic) CGRect statusBarFrame; | |
@property (nonatomic) CGRect tabBarFrame; | |
@property (nonatomic) CGPoint velocity; | |
@property (nonatomic) UIView *barItemsHidingView; | |
// bars are customised differently when navigation controller performs push with hidden navigation bar | |
@property (nonatomic) BOOL isBeingPushedAwayWithHiddenNavigationBar; | |
@property (nonatomic) CGPoint initialVelocity; | |
@end | |
@implementation TAMHidingBarsViewController | |
- (instancetype)initWithViewController:(UIViewController *)controller { | |
self = [super initWithNibName:NSStringFromClass([self class]) bundle:nil]; | |
if (self) { | |
_rootViewController = controller; | |
} | |
return self; | |
} | |
- (void)setTitle:(NSString *)title { | |
// could not customize label's look via appearance proxy for some reason | |
// this is a hack and it should be rewritten later | |
UILabel *titleLabel = [[UILabel alloc] init]; | |
titleLabel.text = title; | |
titleLabel.font = [UIFont boldSystemFontOfSize:17.0f]; | |
titleLabel.textColor = [UIColor tam_navigationBarTintColor]; | |
[titleLabel sizeToFit]; | |
self.navigationItem.titleView = titleLabel; | |
} | |
- (void)viewWillAppear:(BOOL)animated { | |
[super viewWillAppear:animated]; | |
[self.navigationItem addObserver:self | |
forKeyPath:@"titleView" | |
options:NSKeyValueObservingOptionNew | |
context:nil]; | |
if ([XOGlobalVars sharedObject].aboutToShowModalProfile) { | |
[XOGlobalVars sharedObject].aboutToShowModalProfile = NO; | |
[self showBars:animated]; | |
} | |
} | |
- (void)viewWillDisappear:(BOOL)animated { | |
[self.navigationItem removeObserver:self forKeyPath:@"titleView"]; | |
if ([XOGlobalVars sharedObject].aboutToShowModalProfile) { | |
//[XOGlobalVars sharedObject].aboutToShowModalProfile = NO; | |
} | |
else { | |
[self showBars:animated]; | |
} | |
} | |
- (void)viewDidLoad { | |
[super viewDidLoad]; | |
if (self.rootViewController) { | |
[self addChildViewController:self.rootViewController]; | |
[self.containerView addSubview:self.rootViewController.view]; | |
[self.rootViewController didMoveToParentViewController:self]; | |
} | |
[self setVelocity:CGPointMake(1.0f, 1.0f)]; | |
[self setEnabled:YES]; | |
[self setupBars]; | |
UIPanGestureRecognizer *panGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(pan:)]; | |
[self.view addGestureRecognizer:panGesture]; | |
panGesture.delegate = self; | |
self.navigationController.delegate = self; | |
[self showNavigationBarWithDuration:0.0f]; | |
} | |
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { | |
if ([self navigationBarIsHidden]) { | |
self.navigationItem.titleView.alpha = 0.0f; | |
} | |
} | |
- (void)viewDidLayoutSubviews { | |
[super viewDidLayoutSubviews]; | |
self.rootViewController.view.frame = self.containerView.bounds; | |
self.barItemsHidingView.frame = self.navigationController.navigationBar.bounds; | |
[self updateContainerOffsets]; | |
} | |
- (void)updateContainerOffsets { | |
CGRect frame = self.containerView.frame; | |
CGFloat topOffset = self.navigationBarFrame.origin.y + self.navigationBarFrame.size.height; | |
CGFloat bottomOffset = self.view.bounds.size.height - self.tabBarFrame.origin.y; | |
if (self.isBeingPushedAwayWithHiddenNavigationBar) { | |
self.isBeingPushedAwayWithHiddenNavigationBar = NO; | |
frame.size.height = self.view.bounds.size.height - bottomOffset; | |
frame.origin.y = self.navigationBarFrame.origin.y; | |
} else { | |
frame.size.height = self.view.bounds.size.height - topOffset - (self.hideTabBar ? 0 : bottomOffset); | |
frame.origin.y = topOffset; | |
} | |
frame.size.width = self.view.bounds.size.width; | |
self.containerView.frame = frame; | |
[self.view layoutIfNeeded]; | |
} | |
- (void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated { | |
BOOL isBeingPushed = viewController != self; | |
if (isBeingPushed && self.navigationBarIsHidden) { | |
self.isBeingPushedAwayWithHiddenNavigationBar = YES; | |
[self showNavigationBarWithDuration:0.0f]; | |
// we hide tab bar to make sure system won't show it up. | |
// this is not (!) related to hidesBottomBarWhenPushed property | |
[self hideTabBarWithDuration:0.0f]; | |
} | |
} | |
- (void)setupBars { | |
self.extendedLayoutIncludesOpaqueBars = YES; | |
self.automaticallyAdjustsScrollViewInsets = NO; | |
self.edgesForExtendedLayout = UIRectEdgeTop | UIRectEdgeBottom; | |
self.barItemsHidingView = [[UIView alloc] initWithFrame:CGRectZero]; | |
self.barItemsHidingView.backgroundColor = [UIColor tam_navigationBarColor]; | |
self.barItemsHidingView.alpha = 0.0f; | |
[self.navigationController.navigationBar addSubview:self.barItemsHidingView]; | |
} | |
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer { | |
return YES; | |
} | |
#define VELOCITY_FACTOR 0.0025f | |
#define MIN_SHOW_VELOCITY 2.0f | |
#define MIN_HIDE_VELOCITY -1.0f | |
- (void)pan:(UIPanGestureRecognizer *)gesture { | |
if (!self.enabled) { | |
return; | |
} | |
self.velocity = [gesture velocityInView:self.view]; | |
CGPoint velocity = [gesture velocityInView:self.view]; | |
CGFloat offset = velocity.y * VELOCITY_FACTOR; | |
switch (gesture.state) { | |
case UIGestureRecognizerStateCancelled: | |
case UIGestureRecognizerStateEnded: { | |
CGPoint endVelocity = velocity; | |
if (self.initialVelocity.y < 0 && self.navigationBarIsHidden) { | |
endVelocity = self.initialVelocity; | |
} | |
[self endBarsAnimationWithVelocity:endVelocity]; | |
} | |
case UIGestureRecognizerStateBegan: | |
self.initialVelocity = velocity; | |
return; | |
case UIGestureRecognizerStateChanged: | |
if (self.initialVelocity.y > 0 && self.navigationBarIsHidden) { | |
return; | |
} | |
default: | |
break; | |
} | |
CGFloat statusBarHeight = [[UIApplication sharedApplication] statusBarFrame].size.height; | |
CGRect tabBarUpdatedFrame = self.tabBarFrame; | |
CGRect navBarUpdatedFrame = self.navigationController.navigationBar.frame; | |
tabBarUpdatedFrame.origin.y -= self.hideTabBar ? 0 : offset; | |
navBarUpdatedFrame.origin.y += offset; | |
if (navBarUpdatedFrame.origin.y < statusBarHeight && navBarUpdatedFrame.origin.y > 0 - navBarUpdatedFrame.size.height + statusBarHeight) { | |
self.navigationController.navigationBar.frame = navBarUpdatedFrame; | |
CGRect frame = self.containerView.frame; | |
frame.origin.y += offset; | |
frame.size.height -= self.hideTabBar ? offset : 0; | |
self.containerView.frame = frame; | |
CGFloat framePercentageHidden = ((self.statusBarFrame.size.height - self.navigationBarFrame.origin.y) / (self.navigationBarFrame.size.height - 1)); | |
[self setBarItemsOpacity:1 - framePercentageHidden]; | |
} | |
if (tabBarUpdatedFrame.origin.y < self.view.bounds.size.height && tabBarUpdatedFrame.origin.y > self.view.bounds.size.height - self.tabBarController.tabBar.bounds.size.height) { | |
self.tabBarController.tabBar.frame = tabBarUpdatedFrame; | |
CGRect frame = self.containerView.frame; | |
frame.size.height = self.view.bounds.size.height - (self.navigationBarFrame.origin.y + self.navigationBarFrame.size.height) - (self.view.bounds.size.height - self.tabBarFrame.origin.y); | |
self.containerView.frame = frame; | |
} | |
[self.view updateConstraints]; | |
} | |
- (void)setBarItemsOpacity:(CGFloat)opacity { | |
UINavigationItem *navigationItem = self.navigationController.navigationBar.topItem; | |
NSArray *leftButtonItems = navigationItem.leftBarButtonItems ? navigationItem.leftBarButtonItems : @[]; | |
NSArray *barButtonItems = [leftButtonItems arrayByAddingObjectsFromArray:navigationItem.rightBarButtonItems]; | |
for (UIBarButtonItem *item in barButtonItems) { | |
self.barItemsHidingView.alpha = 1 - opacity; | |
[item setTintColor:[item.tintColor colorWithAlphaComponent:opacity]]; | |
item.customView.alpha = opacity; | |
} | |
navigationItem.titleView.alpha = opacity; | |
} | |
#define MIN_VELOCITY 300.0f | |
#define DURATION 0.2f | |
- (void)endBarsAnimationWithVelocity:(CGPoint)velocity { | |
if (self.initialVelocity.y > 0 && self.navigationBarIsHidden && velocity.y < MIN_VELOCITY) { | |
return; | |
} | |
CGFloat duration = fabsf(DURATION); | |
if (velocity.y < 0) { | |
[self hideNavigationBarWithDuration:duration]; | |
[self hideTabBarWithDuration:duration]; | |
[UIView animateWithDuration:duration | |
delay:0.0f | |
options:UIViewAnimationOptionCurveLinear | UIViewAnimationOptionAllowUserInteraction | |
animations:^{ | |
[self updateContainerOffsets]; | |
} | |
completion:nil]; | |
} else { | |
[self showNavigationBarWithDuration:duration]; | |
[self showTabBarWithDuration:duration]; | |
[UIView animateWithDuration:duration | |
delay:0.0f | |
options:UIViewAnimationOptionCurveLinear | UIViewAnimationOptionAllowUserInteraction | |
animations:^{ | |
[self updateContainerOffsets]; | |
} | |
completion:nil]; | |
} | |
} | |
- (void)hideTabBarWithDuration:(NSTimeInterval)duration { | |
[UIView animateWithDuration:duration | |
delay:0.0f | |
options:UIViewAnimationOptionCurveLinear | |
animations:^{ | |
CGRect tabBarFrame = self.tabBarFrame; | |
tabBarFrame.origin.y = self.view.bounds.size.height; | |
self.tabBarController.tabBar.frame = tabBarFrame; | |
[self updateContainerOffsets]; | |
} | |
completion:nil]; | |
} | |
- (void)showTabBarWithDuration:(NSTimeInterval)duration { | |
[UIView animateWithDuration:duration | |
delay:0.0f | |
options:UIViewAnimationOptionCurveLinear | |
animations:^{ | |
CGRect tabBarFrame = self.tabBarFrame; | |
tabBarFrame.origin.y = self.view.bounds.size.height - tabBarFrame.size.height; | |
self.tabBarController.tabBar.frame = tabBarFrame; | |
[self updateContainerOffsets]; | |
} | |
completion:nil]; | |
} | |
- (void)hideNavigationBarWithDuration:(NSTimeInterval)duration { | |
[self willHideNavigationBar]; | |
[UIView animateWithDuration:duration | |
delay:0.0f | |
options:UIViewAnimationOptionCurveLinear | |
animations:^{ | |
CGRect navBarFrame = self.navigationController.navigationBar.frame; | |
navBarFrame.origin.y = self.statusBarFrame.size.height - self.navigationBarFrame.size.height; | |
[self.navigationController.navigationBar setFrame:navBarFrame]; | |
[self setBarItemsOpacity:0.0f]; | |
[self updateContainerOffsets]; | |
} | |
completion:^(BOOL finished) { | |
if (finished) { | |
[self didHideNavigationBar]; | |
} | |
}]; | |
} | |
- (void)showNavigationBarWithDuration:(NSTimeInterval)duration { | |
[self willShowNavigationBar]; | |
[UIView animateWithDuration:duration | |
delay:0.0f | |
options:UIViewAnimationOptionCurveLinear | |
animations:^{ | |
CGRect navBarFrame = self.navigationBarFrame; | |
navBarFrame.origin.y = self.statusBarFrame.size.height; | |
[self.navigationController.navigationBar setFrame:navBarFrame]; | |
// if items are visible when self is pushed away by navigation controller | |
// animations look a bit glitchy. so we keep them hidden | |
if (!self.isBeingPushedAwayWithHiddenNavigationBar) { | |
[self setBarItemsOpacity:1.0f]; | |
} | |
[self updateContainerOffsets]; | |
} | |
completion:^(BOOL finished) { | |
if (finished) { | |
[self didShowNavigationBar]; | |
} | |
}]; | |
} | |
- (void)showBars:(BOOL)animated { | |
if (self.navigationBarIsHidden) { | |
[self showNavigationBarWithDuration:animated ? DURATION : 0.0f]; | |
[self showTabBarWithDuration:animated ? DURATION : 0.0f]; | |
} | |
} | |
- (void)hideBars:(BOOL)animated { | |
if (!self.navigationBarIsHidden) { | |
[self hideNavigationBarWithDuration:animated ? DURATION : 0.0f]; | |
[self hideTabBarWithDuration:animated ? DURATION : 0.0f]; | |
} | |
} | |
- (CGRect)tabBarFrame { | |
return self.tabBarController.tabBar.frame; | |
} | |
- (CGRect)navigationBarFrame { | |
return self.navigationController.navigationBar.frame; | |
} | |
- (CGRect)statusBarFrame { | |
return [[UIApplication sharedApplication] statusBarFrame]; | |
} | |
- (void)enableBarsHiding { | |
self.enabled = YES; | |
} | |
- (void)disableBarsHiding { | |
// self.velocity.y > 0 ? [self showBars:NO] : [self hideBars:NO]; | |
self.enabled = NO; | |
} | |
- (BOOL)navigationBarIsHidden { | |
return self.navigationBarFrame.origin.y < self.statusBarFrame.size.height - self.navigationBarFrame.size.height * 0.5f; | |
} | |
- (void)willHideNavigationBar { | |
if (_willHideNavigationBarBlock) { | |
_willHideNavigationBarBlock(); | |
} | |
} | |
- (void)willShowNavigationBar { | |
if (_willShowNavigationBarBlock) { | |
_willShowNavigationBarBlock(); | |
} | |
} | |
- (void)didHideNavigationBar { | |
} | |
- (void)didShowNavigationBar { | |
} | |
@end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment