Skip to content

Instantly share code, notes, and snippets.

@qubblr
Created December 2, 2016 19:12
Show Gist options
  • Save qubblr/f37ceb6633af84ee4ee334793314bb1d to your computer and use it in GitHub Desktop.
Save qubblr/f37ceb6633af84ee4ee334793314bb1d to your computer and use it in GitHub Desktop.
Container controller that hides both tab and navigation bars when scrolling.
//
// 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
//
// 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