Major overhaul

[NEW]: move to component based design.
[NEW]: implemented most of the things.
[DAMN]:  couldn't find a way to track scrollview's dragging state
This commit is contained in:
Mazyad Alabduljaleel 2014-06-13 18:36:58 +03:00
parent 9fa419e255
commit 6ad3b1d7a0
7 changed files with 304 additions and 225 deletions

View File

@ -0,0 +1,32 @@
//
// TLYShyNavBarController.h
// TLYShyNavBarDemo
//
// Created by Mazyad Alabduljaleel on 6/13/14.
// Copyright (c) 2014 Telly, Inc. All rights reserved.
//
#import <Foundation/Foundation.h>
/* CLASS DESCRIPTION:
* ==================
* Manages the relationship between a scrollView and a navigation
* controller.
*/
@interface TLYShyNavBarController : NSObject
@property (nonatomic, readonly) UIViewController *viewController;
@property (nonatomic, weak) UIScrollView *scrollView;
@property (nonatomic, strong) UIView *extensionView;
- (void)scrollViewDidEndScrolling;
@end
@interface UIViewController (ShyNavBar)
@property (nonatomic, strong) TLYShyNavBarController *shyNavBarController;
@end

View File

@ -0,0 +1,224 @@
//
// TLYShyNavBarController.m
// TLYShyNavBarDemo
//
// Created by Mazyad Alabduljaleel on 6/13/14.
// Copyright (c) 2014 Telly, Inc. All rights reserved.
//
#import "TLYShyNavBarController.h"
#import <objc/runtime.h>
// Thanks to SO user, MattDiPasquale
// http://stackoverflow.com/questions/12991935/how-to-programmatically-get-ios-status-bar-height/16598350#16598350
static inline CGFloat AACStatusBarHeight()
{
CGSize statusBarSize = [UIApplication sharedApplication].statusBarFrame.size;
return MIN(statusBarSize.width, statusBarSize.height);
}
@interface TLYShyNavBarController () <UIGestureRecognizerDelegate>
@property (nonatomic, readonly) UINavigationBar *navBar;
/* BAD: This is a cached value. Test when there is an active call, and try to compute this */
@property (nonatomic) CGPoint initialNavBarCenter;
@property (nonatomic) CGFloat previousYOffset;
@property (nonatomic, getter = isContracting) BOOL contracting;
@end
@implementation TLYShyNavBarController
- (instancetype)init
{
self = [super init];
if (self)
{
self.previousYOffset = NAN;
}
return self;
}
- (void)dealloc
{
[_scrollView removeObserver:self forKeyPath:@"contentOffset"];
}
#pragma mark - Properties
- (void)setExtensionView:(UIView *)extensionView
{
if (_extensionView.superview == self.viewController.navigationController.view)
{
[_extensionView removeFromSuperview];
}
_extensionView = extensionView;
CGRect frame = extensionView.frame;
frame.origin.y = CGRectGetMaxY(self.navBar.frame);
extensionView.frame = frame;
[self.viewController.navigationController.view addSubview:extensionView];
}
- (void)setViewController:(UIViewController *)viewController
{
_viewController = viewController;
self.initialNavBarCenter = self.navBar.center;
}
- (void)setScrollView:(UIScrollView *)scrollView
{
[_scrollView removeObserver:self forKeyPath:@"contentOffset"];
_scrollView = scrollView;
[_scrollView addObserver:self forKeyPath:@"contentOffset" options:0 context:NULL];
}
- (UINavigationBar *)navBar
{
return self.viewController.navigationController.navigationBar;
}
#pragma mark - Private methods
- (CGFloat)_contractionAmount
{
return CGRectGetHeight(self.navBar.bounds);
}
- (CGFloat)_updateNavigationBarToState:(BOOL)contract
{
static const CGFloat velocity = 140.f;
CGFloat newCenterY = (contract
? self.initialNavBarCenter.y - [self _contractionAmount]
: self.initialNavBarCenter.y);
CGFloat deltaY = newCenterY - self.navBar.center.y;
[UIView animateWithDuration:fabs(deltaY/velocity)
animations:^{
self.navBar.center = CGPointMake(self.navBar.center.x, newCenterY);
[self _updateSubviewsAlpha:self.isContracting ? FLT_EPSILON : 1.f];
}];
return deltaY;
}
- (void)_updateNavigationBarWithDeltaY:(CGFloat)deltaY
{
CGPoint newCenter = self.navBar.center;
newCenter.y = MAX(MIN(self.initialNavBarCenter.y, newCenter.y - deltaY),
self.initialNavBarCenter.y - [self _contractionAmount]);
self.navBar.center = newCenter;
CGFloat newAlpha = 1.f - (self.initialNavBarCenter.y - newCenter.y) / [self _contractionAmount];
newAlpha = MIN(MAX(FLT_EPSILON, newAlpha), 1.f);
NSMutableArray *navItems = [NSMutableArray array];
[navItems addObjectsFromArray:self.viewController.navigationItem.leftBarButtonItems];
[navItems addObjectsFromArray:self.viewController.navigationItem.rightBarButtonItems];
if (self.navBar.topItem.titleView)
{
[navItems addObject:self.navBar.topItem.titleView];
}
[self _updateSubviewsAlpha:newAlpha];
self.contracting = deltaY > 0;
}
- (void)_updateSubviewsAlpha:(CGFloat)alpha
{
for (UIView* view in self.navBar.subviews)
{
bool isBackgroundView = view == self.navBar.subviews[0];
bool isViewHidden = view.hidden || view.alpha < FLT_EPSILON;
if (isBackgroundView || isViewHidden)
continue;
view.alpha = alpha;
}
}
- (void)_handleScrolling
{
if (!isnan(self.previousYOffset))
{
CGFloat deltaY = (self.scrollView.contentOffset.y - self.previousYOffset);
CGFloat offset;
offset = -self.scrollView.contentInset.top;
if (self.scrollView.contentOffset.y - deltaY < offset)
{
deltaY = MAX(0, self.scrollView.contentOffset.y - deltaY + offset);
}
/* rounding to resolve a dumb issue with the contentOffset value */
offset = floorf(self.scrollView.contentSize.height - CGRectGetHeight(self.scrollView.bounds) - self.scrollView.contentInset.bottom - 0.5f);
if (self.scrollView.contentOffset.y + deltaY > offset)
{
deltaY = MAX(0, self.scrollView.contentOffset.y + deltaY + offset);
}
[self _updateNavigationBarWithDeltaY:deltaY];
}
self.previousYOffset = self.scrollView.contentOffset.y;
}
- (void)_handleScrollingEnded
{
CGFloat deltaY = [self _updateNavigationBarToState:self.isContracting];
CGPoint newContentOffset = self.scrollView.contentOffset;
newContentOffset.y -= deltaY;
// TODO: manually animate content offset to match navbar animation
[self.scrollView setContentOffset:newContentOffset animated:YES];
}
- (void)scrollViewDidEndScrolling
{
[self _handleScrollingEnded];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
if ([keyPath isEqual:NSStringFromSelector(@selector(contentOffset))])
{
[self _handleScrolling];
}
}
@end
static char shyNavBarControllerKey;
@implementation UIViewController (ShyNavBar)
- (void)setShyNavBarController:(TLYShyNavBarController *)shyNavBarController
{
shyNavBarController.viewController = self;
objc_setAssociatedObject(self, &shyNavBarControllerKey, shyNavBarController, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (TLYShyNavBarController *)shyNavBarController
{
return objc_getAssociatedObject(self, &shyNavBarControllerKey);
}
@end

View File

@ -1,17 +0,0 @@
//
// UIViewController+ShyNavBar.h
// TLYShyNavBarDemo
//
// Created by Mazyad Alabduljaleel on 6/12/14.
// Copyright (c) 2014 Telly, Inc. All rights reserved.
//
#import <UIKit/UIKit.h>
@interface UIViewController (ShyNavBar)
- (void)tly_scrollViewWillBeginDragging:(UIScrollView *)scrollView;
- (void)tly_scrollViewDidScroll:(UIScrollView *)scrollView;
- (void)tly_scrollViewDidEndDragging:(UIScrollView *)scrollView;
@end

View File

@ -1,188 +0,0 @@
//
// UIViewController+ShyNavBar.m
// TLYShyNavBarDemo
//
// Created by Mazyad Alabduljaleel on 6/12/14.
// Copyright (c) 2014 Telly, Inc. All rights reserved.
//
#import "UIViewController+ShyNavBar.h"
#import "TLYViewController.h"
#import <objc/runtime.h>
static char initialNavBarCenterKey;
static char previousYOffsetKey;
static char draggingKey;
static char contractingKey;
static const CGFloat contractionAmount = 44.f;
@interface UIViewController ()
@property (nonatomic, readonly) UINavigationBar *navBar;
@property (nonatomic) CGPoint initialNavBarCenter;
@property (nonatomic) CGFloat previousYOffset;
@property (nonatomic, getter = isDragging) BOOL dragging;
@property (nonatomic, getter = isContracting) BOOL contracting;
@end
@implementation UIViewController (ShyNavBar)
#pragma mark - Category properties
- (UINavigationBar *)navBar
{
return self.navigationController.navigationBar;
}
- (void)setInitialNavBarCenter:(CGPoint)initialNavBarCenter
{
objc_setAssociatedObject(self, &initialNavBarCenterKey, [NSValue valueWithCGPoint:initialNavBarCenter], OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (CGPoint)initialNavBarCenter
{
id value = objc_getAssociatedObject(self, &initialNavBarCenterKey);
CGPoint center = [value CGPointValue];
if (!value)
{
center = self.navigationController.navigationBar.center;
self.initialNavBarCenter = center;
}
return center;
}
- (void)setPreviousYOffset:(CGFloat)previousYOffset
{
objc_setAssociatedObject(self, &previousYOffsetKey, @(previousYOffset), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (CGFloat)previousYOffset
{
id number = objc_getAssociatedObject(self, &previousYOffsetKey);
return number ? [number floatValue] : NAN;
}
- (void)setDragging:(BOOL)dragging
{
objc_setAssociatedObject(self, &draggingKey, @(dragging), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (BOOL)isDragging
{
return [objc_getAssociatedObject(self, &draggingKey) boolValue];
}
- (void)setContracting:(BOOL)contracting
{
objc_setAssociatedObject(self, &contractingKey, @(contracting), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (BOOL)isContracting
{
return [objc_getAssociatedObject(self, &contractingKey) boolValue];
}
#pragma mark - Private methods
- (void)_updateNavigationBarWithDeltaY:(CGFloat)deltaY
{
CGPoint newCenter = self.navBar.center;
newCenter.y = MAX(MIN(self.initialNavBarCenter.y, newCenter.y - deltaY),
self.initialNavBarCenter.y - contractionAmount);
self.navBar.center = newCenter;
CGFloat newAlpha = 1.f - (self.initialNavBarCenter.y - newCenter.y) / contractionAmount;
newAlpha = MIN(MAX(FLT_EPSILON, newAlpha), 1.f);
NSMutableArray *navItems = [NSMutableArray array];
[navItems addObjectsFromArray:self.navigationItem.leftBarButtonItems];
[navItems addObjectsFromArray:self.navigationItem.rightBarButtonItems];
if (self.navBar.topItem.titleView)
{
[navItems addObject:self.navBar.topItem.titleView];
}
[self _updateSubviewsAlpha:newAlpha];
self.contracting = deltaY > 0;
}
- (void)_updateSubviewsAlpha:(CGFloat)alpha
{
for (UIView* view in self.navBar.subviews)
{
bool isBackgroundView = view == self.navBar.subviews[0];
bool isViewHidden = view.hidden || view.alpha < FLT_EPSILON;
if (isBackgroundView || isViewHidden)
continue;
view.alpha = alpha;
}
}
#pragma mark - Public methods
- (void)tly_scrollViewWillBeginDragging:(UIScrollView *)scrollView
{
self.dragging = YES;
}
- (void)tly_scrollViewDidScroll:(UIScrollView *)scrollView
{
if (!isnan(self.previousYOffset))
{
CGFloat deltaY = scrollView.contentOffset.y - self.previousYOffset;
CGFloat offset;
offset = -scrollView.contentInset.top;
if (scrollView.contentOffset.y - deltaY < offset)
{
deltaY = MAX(0, scrollView.contentOffset.y - deltaY + offset);
}
offset = scrollView.contentSize.height - CGRectGetHeight(scrollView.bounds) - scrollView.contentInset.bottom;
if (scrollView.contentOffset.y + deltaY > offset)
{
deltaY = MAX(0, scrollView.contentOffset.y + deltaY + offset);
}
[self _updateNavigationBarWithDeltaY:deltaY];
}
self.previousYOffset = scrollView.contentOffset.y;
}
- (void)tly_scrollViewDidEndDragging:(UIScrollView *)scrollView
{
static const CGFloat velocity = 140.f;
self.dragging = NO;
CGFloat newCenterY = (self.isContracting
? self.initialNavBarCenter.y - contractionAmount
: self.initialNavBarCenter.y);
CGFloat deltaY = newCenterY - self.navBar.center.y;
[UIView animateWithDuration:fabs(deltaY/velocity)
animations:^{
self.navBar.center = CGPointMake(self.navBar.center.x, newCenterY);
[self _updateSubviewsAlpha:self.isContracting ? FLT_EPSILON : 1.f];
}];
CGPoint newContentOffset = scrollView.contentOffset;
newContentOffset.y -= deltaY;
// TODO: manually animate content offset to match navbar animation
[scrollView setContentOffset:newContentOffset animated:YES];
}
@end

View File

@ -7,7 +7,7 @@
objects = {
/* Begin PBXBuildFile section */
8268F9F01949CC9D004EC0E4 /* UIViewController+ShyNavBar.m in Sources */ = {isa = PBXBuildFile; fileRef = 8268F9EF1949CC9D004EC0E4 /* UIViewController+ShyNavBar.m */; };
8268F9FF194AE04B004EC0E4 /* TLYShyNavBarController.m in Sources */ = {isa = PBXBuildFile; fileRef = 8268F9FE194AE04B004EC0E4 /* TLYShyNavBarController.m */; };
828F57201949C37B009EB8DD /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 828F571F1949C37B009EB8DD /* Foundation.framework */; };
828F57221949C37B009EB8DD /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 828F57211949C37B009EB8DD /* CoreGraphics.framework */; };
828F57241949C37B009EB8DD /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 828F57231949C37B009EB8DD /* UIKit.framework */; };
@ -35,8 +35,8 @@
/* End PBXContainerItemProxy section */
/* Begin PBXFileReference section */
8268F9EE1949CC9D004EC0E4 /* UIViewController+ShyNavBar.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIViewController+ShyNavBar.h"; sourceTree = "<group>"; };
8268F9EF1949CC9D004EC0E4 /* UIViewController+ShyNavBar.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIViewController+ShyNavBar.m"; sourceTree = "<group>"; };
8268F9FD194AE04B004EC0E4 /* TLYShyNavBarController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TLYShyNavBarController.h; sourceTree = "<group>"; };
8268F9FE194AE04B004EC0E4 /* TLYShyNavBarController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TLYShyNavBarController.m; sourceTree = "<group>"; };
828F571C1949C37B009EB8DD /* TLYShyNavBarDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = TLYShyNavBarDemo.app; sourceTree = BUILT_PRODUCTS_DIR; };
828F571F1949C37B009EB8DD /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; };
828F57211949C37B009EB8DD /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; };
@ -159,8 +159,8 @@
828F57541949C381009EB8DD /* TLYShyNavBar */ = {
isa = PBXGroup;
children = (
8268F9EE1949CC9D004EC0E4 /* UIViewController+ShyNavBar.h */,
8268F9EF1949CC9D004EC0E4 /* UIViewController+ShyNavBar.m */,
8268F9FD194AE04B004EC0E4 /* TLYShyNavBarController.h */,
8268F9FE194AE04B004EC0E4 /* TLYShyNavBarController.m */,
);
name = TLYShyNavBar;
path = ../TLYShyNavBar;
@ -265,8 +265,8 @@
buildActionMask = 2147483647;
files = (
828F57301949C37B009EB8DD /* TLYAppDelegate.m in Sources */,
8268F9F01949CC9D004EC0E4 /* UIViewController+ShyNavBar.m in Sources */,
828F57361949C37B009EB8DD /* TLYViewController.m in Sources */,
8268F9FF194AE04B004EC0E4 /* TLYShyNavBarController.m in Sources */,
828F572C1949C37B009EB8DD /* main.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;

View File

@ -14,5 +14,5 @@
#import <UIKit/UIKit.h>
#import <Foundation/Foundation.h>
#import "UIViewController+ShyNavBar.h"
#import "TLYShyNavBarController.h"
#endif

View File

@ -8,6 +8,24 @@
#import "TLYViewController.h"
@interface UIBarButtonItem (Telly)
+ (UIBarButtonItem *)tly_flexibleSpaceButtonItem;
@end
@implementation UIBarButtonItem (Telly)
+ (UIBarButtonItem *)tly_flexibleSpaceButtonItem
{
return [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace
target:nil
action:nil];
}
@end
@interface TLYViewController ()
@property (weak, nonatomic) IBOutlet UIScrollView *scrollView;
@ -22,7 +40,6 @@
self = [super initWithCoder:aDecoder];
if (self) {
self.title = @"WTFox Say";
self.toolbarItems = @[[[UIBarButtonItem alloc] initWithCustomView:[[UISegmentedControl alloc] initWithItems:@[@"One", @"Two"]]]];
}
return self;
}
@ -31,7 +48,20 @@
{
[super viewDidLoad];
self.navigationController.toolbarHidden = NO;
UIToolbar *toolbar = [[UIToolbar alloc] initWithFrame:CGRectMake(0, 0, CGRectGetWidth(self.view.bounds), 44.f)];
toolbar.autoresizingMask = UIViewAutoresizingFlexibleWidth|UIViewAutoresizingFlexibleBottomMargin;
toolbar.barTintColor = [UIColor whiteColor];
toolbar.items = @[[UIBarButtonItem tly_flexibleSpaceButtonItem],
[[UIBarButtonItem alloc] initWithTitle:@"One" style:UIBarButtonItemStyleBordered target:nil action:nil],
[UIBarButtonItem tly_flexibleSpaceButtonItem],
[[UIBarButtonItem alloc] initWithTitle:@"Two" style:UIBarButtonItemStyleBordered target:nil action:nil],
[UIBarButtonItem tly_flexibleSpaceButtonItem]];
TLYShyNavBarController *shyController = [TLYShyNavBarController new];
shyController.scrollView = self.scrollView;
shyController.extensionView = toolbar;
self.shyNavBarController = shyController;
}
- (void)viewDidLayoutSubviews
@ -43,19 +73,17 @@
#pragma mark - UIScrollViewDelegate methods
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView
{
[self tly_scrollViewWillBeginDragging:scrollView];
}
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
[self tly_scrollViewDidScroll:scrollView];
}
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate
{
[self tly_scrollViewDidEndDragging:scrollView];
if (!decelerate)
{
[self.shyNavBarController scrollViewDidEndScrolling];
}
}
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{
[self.shyNavBarController scrollViewDidEndScrolling];
}
@end