123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253 |
- /* This file is adapted from GTScrollNavigationBar - https://github.com/luugiathuy/GTScrollNavigationBar
- * 3-clause BSD License
- *
- * Copyright (c) 2013, Luu Gia Thuy
- *
- * Redistribution and use in source and binary forms, with or without modification,
- * are permitted provided that the following conditions are met:
- *
- * * Redistributions of source code must retain the above copyright notice, this
- * list of conditions and the following disclaimer.
- *
- * * Redistributions in binary form must reproduce the above copyright notice, this
- * list of conditions and the following disclaimer in the documentation and/or
- * other materials provided with the distribution.
- *
- * * Neither the name of the {organization} nor the names of its
- * contributors may be used to endorse or promote products derived from
- * this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
- * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
- * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
- * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
- * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
- * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
- * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
- * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
- * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
- #import "GTScrollNavigationBar.h"
- #define kNearZero 0.000001f
- @interface GTScrollNavigationBar () <UIGestureRecognizerDelegate>
- @property (strong, nonatomic) UIPanGestureRecognizer* panGesture;
- @property (assign, nonatomic) CGFloat lastContentOffsetY;
- @end
- @implementation GTScrollNavigationBar
- - (id)initWithCoder:(NSCoder *)aDecoder {
- self = [super initWithCoder:aDecoder];
- if (self) {
- [self setup];
- }
- return self;
- }
- - (id)initWithFrame:(CGRect)frame
- {
- self = [super initWithFrame:frame];
- if (self) {
- [self setup];
- }
- return self;
- }
- - (void)setup
- {
- self.panGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self
- action:@selector(handlePan:)];
- self.panGesture.delegate = self;
- self.panGesture.cancelsTouchesInView = NO;
- NSNotificationCenter *defaultCenter = [NSNotificationCenter defaultCenter];
- [defaultCenter addObserver:self
- selector:@selector(applicationDidBecomeActive)
- name:UIApplicationDidBecomeActiveNotification
- object:nil];
- [defaultCenter addObserver:self
- selector:@selector(statusBarOrientationDidChange)
- name:UIApplicationDidChangeStatusBarOrientationNotification
- object:nil];
- }
- - (void)dealloc
- {
- if (!SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(@"9.0")) {
- [[NSNotificationCenter defaultCenter] removeObserver:self];
- }
- }
- #pragma mark - Properties
- - (void)setScrollView:(UIScrollView*)scrollView
- {
- [self resetToDefaultPositionWithAnimation:NO];
- _scrollView = scrollView;
- // remove gesture from current panGesture's view
- if (self.panGesture.view) {
- [self.panGesture.view removeGestureRecognizer:self.panGesture];
- }
- if (scrollView) {
- [scrollView addGestureRecognizer:self.panGesture];
- }
- }
- #pragma mark - Public methods
- - (void)resetToDefaultPositionWithAnimation:(BOOL)animated
- {
- self.scrollState = GTScrollNavigationBarStateNone;
- CGRect frame = self.frame;
- frame.origin.y = [self statusBarTopOffset];
- [self setFrame:frame alpha:1.0f animated:animated];
- }
- #pragma mark - Notifications
- - (void)statusBarOrientationDidChange
- {
- [self resetToDefaultPositionWithAnimation:NO];
- }
- - (void)applicationDidBecomeActive
- {
- [self resetToDefaultPositionWithAnimation:NO];
- }
- #pragma mark - UIGestureRecognizerDelegate
- - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer
- shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
- {
- return YES;
- }
- #pragma mark - panGesture handler
- - (void)handlePan:(UIPanGestureRecognizer*)gesture
- {
- if (!self.scrollView || gesture.view != self.scrollView) {
- return;
- }
- // Don't try to scroll navigation bar if there's not enough room
- if (self.scrollView.frame.size.height + (self.bounds.size.height * 2) >=
- self.scrollView.contentSize.height) {
- return;
- }
- CGFloat contentOffsetY = self.scrollView.contentOffset.y;
- // Reset scrollState when the gesture began
- if (gesture.state == UIGestureRecognizerStateBegan) {
- self.scrollState = GTScrollNavigationBarStateNone;
- self.lastContentOffsetY = contentOffsetY;
- return;
- }
- CGFloat deltaY = contentOffsetY - self.lastContentOffsetY;
- if (deltaY < 0.0f) {
- self.scrollState = GTScrollNavigationBarStateScrollingDown;
- } else if (deltaY > 0.0f) {
- self.scrollState = GTScrollNavigationBarStateScrollingUp;
- }
- CGRect frame = self.frame;
- CGFloat alpha = 1.0f;
- CGFloat maxY = [self statusBarTopOffset];
- CGFloat minY = maxY - CGRectGetHeight(frame) + 1.0f;
- // NOTE: plus 1px to prevent the navigation bar disappears in iOS < 7
- CGFloat contentInsetTop = self.scrollView.contentInset.top;
- bool isBouncePastTopEdge = contentOffsetY < -contentInsetTop;
- if (isBouncePastTopEdge && CGRectGetMinY(frame) == maxY) {
- self.lastContentOffsetY = contentOffsetY;
- return;
- }
- bool isScrolling = (self.scrollState == GTScrollNavigationBarStateScrollingUp ||
- self.scrollState == GTScrollNavigationBarStateScrollingDown);
- bool gestureIsActive = (gesture.state != UIGestureRecognizerStateEnded &&
- gesture.state != UIGestureRecognizerStateCancelled);
- if (isScrolling && !gestureIsActive) {
- // Animate navigation bar to end position
- if (self.scrollState == GTScrollNavigationBarStateScrollingDown) {
- frame.origin.y = maxY;
- alpha = 1.0f;
- }
- else if (self.scrollState == GTScrollNavigationBarStateScrollingUp) {
- frame.origin.y = minY;
- alpha = kNearZero;
- }
- [self setFrame:frame alpha:alpha animated:YES];
- }
- // When panning down at beginning of scrollView and the bar is expanding, do not update lastContentOffsetY
- if (!isBouncePastTopEdge && CGRectGetMinY(frame) == maxY) {
- self.lastContentOffsetY = contentOffsetY;
- }
- }
- #pragma mark - helper methods
- - (CGFloat)statusBarTopOffset
- {
- CGRect statusBarFrame = [UIApplication sharedApplication].statusBarFrame;
- CGFloat topOffset = MIN(CGRectGetMaxX(statusBarFrame), CGRectGetMaxY(statusBarFrame));
- bool isInCallStatusBar = topOffset == 40.0f;
- if (isInCallStatusBar) {
- topOffset -= 20.0f;
- }
- return topOffset;
- }
- - (void)setFrame:(CGRect)frame alpha:(CGFloat)alpha animated:(BOOL)animated
- {
- if (animated) {
- [UIView beginAnimations:@"GTScrollNavigationBarAnimation" context:nil];
- }
- CGFloat offsetY = CGRectGetMinY(frame) - CGRectGetMinY(self.frame);
- UIView *firstView = [self.subviews firstObject];
- for (UIView *view in self.subviews) {
- bool isBackgroundView = view == firstView;
- bool isViewHidden = view.hidden || view.alpha == 0.0f;
- if (isBackgroundView || isViewHidden)
- continue;
- view.alpha = alpha;
- }
- self.frame = frame;
- if (self.scrollView) {
- CGRect parentViewFrame = self.scrollView.superview.frame;
- parentViewFrame.origin.y += offsetY;
- parentViewFrame.size.height -= offsetY;
- self.scrollView.superview.frame = parentViewFrame;
- }
- if (animated) {
- [UIView commitAnimations];
- }
- }
- @end
- @implementation UINavigationController (GTScrollNavigationBarAdditions)
- @dynamic scrollNavigationBar;
- - (GTScrollNavigationBar *)scrollNavigationBar
- {
- return (GTScrollNavigationBar *)self.navigationBar;
- }
- @end
|