diff --git a/README.md b/README.md index f4fb5a6..6b7ece7 100644 --- a/README.md +++ b/README.md @@ -53,6 +53,10 @@ This component helps you mimick the navigation bar auto scrolling that you see i ![](resources/ShyNavBar-7.gif) ++ Sticky navigation bar (Thanks [@TiagoVeloso](https://github.com/TiagoVeloso) !) + +![](resources/ShyNavBar-9.gif) + + Fade the entire navbar (Thanks [__@longsview__](https://github.com/longsview) !) ![](resources/ShyNavBar-8.gif) diff --git a/TLYShyNavBar/TLYShyNavBarManager.h b/TLYShyNavBar/TLYShyNavBarManager.h index 3a44f0a..7c8d495 100644 --- a/TLYShyNavBar/TLYShyNavBarManager.h +++ b/TLYShyNavBar/TLYShyNavBarManager.h @@ -52,7 +52,14 @@ typedef NS_ENUM(NSInteger, TLYShyNavBarFade) { */ @property (nonatomic, readonly) CGRect extensionViewBounds; -/* Sticky extension view +/* Make the navigation bar stick to the top without collapsing + * Deatuls to NO + */ +@property (nonatomic) BOOL stickyNavigationBar; + +/* Make the extension view stick to the bottom of the navbar without + * collapsing + * Defaults to NO */ @property (nonatomic) BOOL stickyExtensionView; diff --git a/TLYShyNavBar/TLYShyNavBarManager.m b/TLYShyNavBar/TLYShyNavBarManager.m index b098759..1519a1f 100644 --- a/TLYShyNavBar/TLYShyNavBarManager.m +++ b/TLYShyNavBar/TLYShyNavBarManager.m @@ -33,8 +33,11 @@ static inline CGFloat AACStatusBarHeight(UIViewController *viewController) UIView *view = viewController.view; CGRect frame = [view.superview convertRect:view.frame toView:view.window]; + BOOL viewOverlapsStatusBar = frame.origin.y < statusBarHeight; - if (!viewOverlapsStatusBar) { + + if (!viewOverlapsStatusBar) + { return 0.f; } @@ -60,10 +63,38 @@ static void * const kTLYShyNavBarManagerKVOContext = (void*)&kTLYShyNavBarManage @end + +@interface TLYShyStatusBarController : NSObject + +@property (nonatomic, weak) UIViewController *viewController; + +@end + +@implementation TLYShyStatusBarController + +- (CGFloat)viewMaxY +{ + CGFloat statusBarHeight = AACStatusBarHeight(self.viewController); + /* The standard status bar is 20 pixels. The navigation bar extends 20 pixels up so it is overlapped by the status bar. + * When there is a larger than 20 pixel status bar (e.g. a phone call is in progress or GPS is active), the center needs + * to shift up 20 pixels to avoid this 'dead space' being visible above the usual nav bar. + */ + if (statusBarHeight > 20) + { + statusBarHeight -= 20; + } + + return statusBarHeight; +} + +@end + + #pragma mark - TLYShyNavBarManager class @interface TLYShyNavBarManager () +@property (nonatomic, strong) id statusBarController; @property (nonatomic, strong) TLYShyViewController *navBarController; @property (nonatomic, strong) TLYShyViewController *extensionController; @@ -104,28 +135,10 @@ static void * const kTLYShyNavBarManagerKVOContext = (void*)&kTLYShyNavBarManage self.previousScrollInsets = UIEdgeInsetsZero; self.previousYOffset = NAN; - self.navBarController = [[TLYShyViewController alloc] init]; - - __weak __typeof(self) weakSelf = self; - - self.navBarController.expandedCenter = ^(UIView *view) - { - CGFloat statusBarHeight = AACStatusBarHeight(weakSelf.viewController); - /* The standard status bar is 20 pixels. The navigation bar extends 20 pixels up so it is overlapped by the status bar. - * When there is a larger than 20 pixel status bar (e.g. a phone call is in progress or GPS is active), the center needs - * to shift up 20 pixels to avoid this 'dead space' being visible above the usual nav bar. - */ - if (statusBarHeight > 20) - statusBarHeight -= 20; - - return CGPointMake(CGRectGetMidX(view.bounds), - CGRectGetMidY(view.bounds) + statusBarHeight); - }; + self.statusBarController = [[TLYShyStatusBarController alloc] init]; - self.navBarController.contractionAmount = ^(UIView *view) - { - return CGRectGetHeight(view.bounds); - }; + self.navBarController = [[TLYShyViewController alloc] init]; + self.navBarController.parent = self.statusBarController; self.extensionViewContainer = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 100.f, 0.f)]; self.extensionViewContainer.backgroundColor = [UIColor clearColor]; @@ -133,17 +146,8 @@ static void * const kTLYShyNavBarManagerKVOContext = (void*)&kTLYShyNavBarManage self.extensionController = [[TLYShyViewController alloc] init]; self.extensionController.view = self.extensionViewContainer; - self.extensionController.contractionAmount = ^(UIView *view) - { - return CGRectGetHeight(view.bounds); - }; - - self.extensionController.expandedCenter = ^(UIView *view) - { - return CGPointMake(CGRectGetMidX(view.bounds), - CGRectGetMidY(view.bounds) + weakSelf.viewController.tly_topLayoutGuide.length); - }; - + self.extensionController.parent = self.navBarController; + self.navBarController.child = self.extensionController; [[NSNotificationCenter defaultCenter] addObserver:self @@ -234,13 +238,24 @@ static void * const kTLYShyNavBarManagerKVOContext = (void*)&kTLYShyNavBarManage } } +- (BOOL)stickyNavigationBar +{ + return self.navBarController.sticky; +} + +- (void)setStickyNavigationBar:(BOOL)stickyNavigationBar +{ + self.navBarController.sticky = stickyNavigationBar; +} + +- (BOOL)stickyExtensionView +{ + return self.extensionController.sticky; +} + - (void)setStickyExtensionView:(BOOL)stickyExtensionView { - _stickyExtensionView = stickyExtensionView; - - if (self.navBarController) { - self.navBarController.stickyExtensionView = _stickyExtensionView; - } + self.extensionController.sticky = stickyExtensionView; } @@ -320,6 +335,7 @@ static void * const kTLYShyNavBarManagerKVOContext = (void*)&kTLYShyNavBarManage // 6 - Update the navigation bar shyViewController self.navBarController.fadeBehavior = (TLYShyNavViewControllerFade)self.fadeBehavior; + [self.navBarController updateYOffset:deltaY]; } @@ -401,7 +417,7 @@ static void * const kTLYShyNavBarManagerKVOContext = (void*)&kTLYShyNavBarManage - (void)layoutViews { UIEdgeInsets scrollInsets = self.scrollView.contentInset; - scrollInsets.top = CGRectGetHeight(self.extensionViewContainer.bounds) + self.viewController.tly_topLayoutGuide.length; + scrollInsets.top = self.extensionController.viewMaxY; if (UIEdgeInsetsEqualToEdgeInsets(scrollInsets, self.previousScrollInsets)) { diff --git a/TLYShyNavBar/TLYShyViewController.h b/TLYShyNavBar/TLYShyViewController.h index d8f2542..d4bbb56 100644 --- a/TLYShyNavBar/TLYShyViewController.h +++ b/TLYShyNavBar/TLYShyViewController.h @@ -25,6 +25,12 @@ typedef NS_ENUM(NSInteger, TLYShyNavViewControllerFade) { TLYShyNavViewControllerFadeNavbar, }; +@protocol TLYShyViewControllerParent + +@property (nonatomic, readonly) CGFloat viewMaxY; + +@end + /* CLASS DESCRIPTION: * ================== * A shy view is a view that contracts when a scrolling event is @@ -39,20 +45,19 @@ typedef NS_ENUM(NSInteger, TLYShyNavViewControllerFade) { @interface TLYShyViewController : NSObject @property (nonatomic, weak) TLYShyViewController *child; +@property (nonatomic, weak) id parent; @property (nonatomic, weak) UIView *view; -@property (nonatomic, copy) TLYShyViewControllerExpandedCenterBlock expandedCenter; -@property (nonatomic, copy) TLYShyViewControllerContractionAmountBlock contractionAmount; - @property (nonatomic) TLYShyNavViewControllerFade fadeBehavior; @property (nonatomic, readonly) CGFloat totalHeight; -/* Sticky extension view +/* Sticky means it will always stay in expanded state */ -@property (nonatomic) BOOL stickyExtensionView; +@property (nonatomic) BOOL sticky; - (CGFloat)updateYOffset:(CGFloat)deltaY; +- (void)offsetCenterBy:(CGPoint)deltaPoint; - (CGFloat)snap:(BOOL)contract; @@ -60,3 +65,7 @@ typedef NS_ENUM(NSInteger, TLYShyNavViewControllerFade) { - (CGFloat)contract; @end + + +@interface TLYShyViewController (AsParent) +@end diff --git a/TLYShyNavBar/TLYShyViewController.m b/TLYShyNavBar/TLYShyViewController.m index c1ff271..eb66e93 100644 --- a/TLYShyNavBar/TLYShyViewController.m +++ b/TLYShyNavBar/TLYShyViewController.m @@ -8,7 +8,16 @@ #import "TLYShyViewController.h" -const CGFloat contractionVelocity = 300.f; + +@implementation TLYShyViewController (AsParent) + +- (CGFloat)viewMaxY +{ + return CGRectGetMaxY(self.view.frame); +} + +@end + @interface TLYShyViewController () @@ -29,12 +38,17 @@ const CGFloat contractionVelocity = 300.f; // convenience - (CGPoint)expandedCenterValue { - return self.expandedCenter(self.view); + CGPoint center = CGPointMake(CGRectGetMidX(self.view.bounds), + CGRectGetMidY(self.view.bounds)); + + center.y += self.parent.viewMaxY; + + return center; } - (CGFloat)contractionAmountValue { - return self.contractionAmount(self.view); + return self.sticky ? 0.f : CGRectGetHeight(self.view.bounds); } - (CGPoint)contractedCenterValue @@ -61,6 +75,13 @@ const CGFloat contractionVelocity = 300.f; - (void)_onAlphaUpdate:(CGFloat)alpha { + if (self.sticky) + { + self.view.alpha = 1.f; + [self _updateSubviewsAlpha:1.f]; + return; + } + switch (self.fadeBehavior) { case TLYShyNavViewControllerFadeDisabled: @@ -96,6 +117,15 @@ const CGFloat contractionVelocity = 300.f; } } +- (void)_updateCenter:(CGPoint)newCenter +{ + CGPoint currentCenter = self.view.center; + CGPoint deltaPoint = CGPointMake(newCenter.x - currentCenter.x, + newCenter.y - currentCenter.y); + + [self offsetCenterBy:deltaPoint]; +} + #pragma mark - Public methods - (void)setFadeBehavior:(TLYShyNavViewControllerFade)fadeBehavior @@ -108,26 +138,26 @@ const CGFloat contractionVelocity = 300.f; } } -- (CGFloat)updateYOffset:(CGFloat)deltaY +- (void)offsetCenterBy:(CGPoint)deltaPoint { - if (self.child && deltaY < 0 && !self.stickyExtensionView) + [self.child offsetCenterBy:deltaPoint]; + + self.view.center = CGPointMake(self.view.center.x + deltaPoint.x, + self.view.center.y + deltaPoint.y); +} + +- (CGFloat)updateYOffset:(CGFloat)deltaY +{ + if (self.child && deltaY < 0) { deltaY = [self.child updateYOffset:deltaY]; - self.child.view.hidden = deltaY < 0; + self.child.view.hidden = (!self.child.sticky && deltaY < 0); } CGFloat newYOffset = self.view.center.y + deltaY; CGFloat newYCenter = MAX(MIN(self.expandedCenterValue.y, newYOffset), self.contractedCenterValue.y); - self.view.center = CGPointMake(self.expandedCenterValue.x, newYCenter); - - if (self.stickyExtensionView) - { - CGFloat newChildYOffset = self.child.view.center.y + deltaY; - CGFloat newChildYCenter = MAX(MIN(self.child.expandedCenterValue.y, newChildYOffset), self.child.contractedCenterValue.y); - - self.child.view.center = CGPointMake(self.child.expandedCenterValue.x, newChildYCenter); - } + [self _updateCenter:CGPointMake(self.expandedCenterValue.x, newYCenter)]; CGFloat newAlpha = 1.f - (self.expandedCenterValue.y - self.view.center.y) / self.contractionAmountValue; newAlpha = MIN(MAX(FLT_EPSILON, newAlpha), 1.f); @@ -136,10 +166,14 @@ const CGFloat contractionVelocity = 300.f; CGFloat residual = newYOffset - newYCenter; - if (self.child && deltaY > 0 && residual > 0 && !self.stickyExtensionView) + if (self.child && deltaY > 0 && residual > 0) { residual = [self.child updateYOffset:residual]; - self.child.view.hidden = residual - (newYOffset - newYCenter) > FLT_EPSILON; + self.child.view.hidden = (!self.child.sticky && residual - (newYOffset - newYCenter) > FLT_EPSILON); + } + else if (self.child.sticky && deltaY > 0) + { + [self.child updateYOffset:deltaY]; } return residual; @@ -182,7 +216,7 @@ const CGFloat contractionVelocity = 300.f; CGFloat amountToMove = self.expandedCenterValue.y - self.view.center.y; - self.view.center = self.expandedCenterValue; + [self _updateCenter:self.expandedCenterValue]; [self.child expand]; return amountToMove; @@ -192,7 +226,7 @@ const CGFloat contractionVelocity = 300.f; { CGFloat amountToMove = self.contractedCenterValue.y - self.view.center.y; - self.view.center = self.contractedCenterValue; + [self _updateCenter:self.contractedCenterValue]; [self.child contract]; return amountToMove; diff --git a/TLYShyNavBarDemo/TLYShyNavBarDemo/Base.lproj/Main.storyboard b/TLYShyNavBarDemo/TLYShyNavBarDemo/Base.lproj/Main.storyboard index 6b27d7d..809e862 100644 --- a/TLYShyNavBarDemo/TLYShyNavBarDemo/Base.lproj/Main.storyboard +++ b/TLYShyNavBarDemo/TLYShyNavBarDemo/Base.lproj/Main.storyboard @@ -86,9 +86,29 @@ - + + + + + + + + + + + + + + + @@ -106,8 +126,28 @@ - - + + + + + + + + + + + + + + + + @@ -126,8 +166,8 @@ - - + + @@ -143,8 +183,8 @@ - - + + @@ -241,7 +281,7 @@ - + @@ -301,13 +341,129 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + + + diff --git a/TLYShyNavBarDemo/TLYShyNavBarDemo/TLYMenuTableViewController.m b/TLYShyNavBarDemo/TLYShyNavBarDemo/TLYMenuTableViewController.m index 398772c..c18e3f2 100644 --- a/TLYShyNavBarDemo/TLYShyNavBarDemo/TLYMenuTableViewController.m +++ b/TLYShyNavBarDemo/TLYShyNavBarDemo/TLYMenuTableViewController.m @@ -29,16 +29,13 @@ - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { - return 5; + return 7; } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { NSString *cellId = [@(indexPath.row) stringValue]; - UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellId - forIndexPath:indexPath]; - - return cell; + return [tableView dequeueReusableCellWithIdentifier:cellId forIndexPath:indexPath]; } @end diff --git a/TLYShyNavBarDemo/TLYShyNavBarDemo/TLYViewController.m b/TLYShyNavBarDemo/TLYShyNavBarDemo/TLYViewController.m index 2cf4d77..e5567d0 100644 --- a/TLYShyNavBarDemo/TLYShyNavBarDemo/TLYViewController.m +++ b/TLYShyNavBarDemo/TLYShyNavBarDemo/TLYViewController.m @@ -10,7 +10,8 @@ @interface TLYViewController () -/* we set this in the xib as a runtime property */ +@property (nonatomic, assign) IBInspectable BOOL disableExtensionView; +@property (nonatomic, assign) IBInspectable BOOL stickyNavigationBar; @property (nonatomic, assign) IBInspectable BOOL stickyExtensionView; @property (nonatomic, assign) IBInspectable NSInteger fadeBehavior; @@ -26,6 +27,9 @@ self = [super initWithCoder:aDecoder]; if (self) { + self.disableExtensionView = NO; + self.stickyNavigationBar = NO; + self.stickyExtensionView = NO; self.fadeBehavior = TLYShyNavBarFadeSubviews; self.title = @"WTFox Say"; @@ -37,13 +41,20 @@ { [super viewDidLoad]; - UIView *view = [[UIView alloc] initWithFrame:CGRectMake(0, 0, CGRectGetWidth(self.view.bounds), 44.f)]; - view.backgroundColor = [UIColor redColor]; + UIView *view = nil; + + if (!self.disableExtensionView) + { + view = [[UIView alloc] initWithFrame:CGRectMake(0, 0, CGRectGetWidth(self.view.bounds), 44.f)]; + view.backgroundColor = [UIColor redColor]; + } /* Library code */ self.shyNavBarManager.scrollView = self.scrollView; /* Can then be remove by setting the ExtensionView to nil */ [self.shyNavBarManager setExtensionView:view]; + /* Make navbar stick to the top */ + [self.shyNavBarManager setStickyNavigationBar:self.stickyNavigationBar]; /* Make the extension view stick to the top */ [self.shyNavBarManager setStickyExtensionView:self.stickyExtensionView]; /* Navigation bar fade behavior */ diff --git a/resources/ShyNavBar-9.gif b/resources/ShyNavBar-9.gif new file mode 100644 index 0000000..a6f5811 Binary files /dev/null and b/resources/ShyNavBar-9.gif differ diff --git a/resources/features-testing.png b/resources/features-testing.png index f29a517..14a3dd9 100644 Binary files a/resources/features-testing.png and b/resources/features-testing.png differ