模仿Facebook隐藏/显示扩展/缩小导航栏

在新的iOS7的Facebook iPhone应用程序中,当用户向上滚动navigationBar栏时,会逐渐隐藏到完全消失的点。 然后当用户向下滚动navigationBar栏时,逐渐显示出来。

你将如何自己实现这个行为? 我知道下面的解决scheme,但它马上消失,它并没有绑在用户的滚动手势的速度。

 [navigationController setNavigationBarHidden: YES animated:YES]; 

我希望这不是一个重复,因为我不知道如何最好地描述“扩大/收缩”的行为。

@peerless给出的解决scheme是一个很好的开始,但是只要拖动开始,它就会启动一个animation,而不考虑滚动的速度。 这会导致比你在Facebook应用程序更令人兴奋的体验。 为了配合Facebook的行为,我们需要:

  • 以与拖动速率成比例的速率隐藏/显示导航栏
  • 如果在酒吧部分隐藏时滚动停止,则启动animation以完全隐藏酒吧
  • 当条形缩小时淡化导航条的项目。

首先,您需要以下属性:

 @property (nonatomic) CGFloat previousScrollViewYOffset; 

这里是UIScrollViewDelegate方法:

 - (void)scrollViewDidScroll:(UIScrollView *)scrollView { CGRect frame = self.navigationController.navigationBar.frame; CGFloat size = frame.size.height - 21; CGFloat framePercentageHidden = ((20 - frame.origin.y) / (frame.size.height - 1)); CGFloat scrollOffset = scrollView.contentOffset.y; CGFloat scrollDiff = scrollOffset - self.previousScrollViewYOffset; CGFloat scrollHeight = scrollView.frame.size.height; CGFloat scrollContentSizeHeight = scrollView.contentSize.height + scrollView.contentInset.bottom; if (scrollOffset <= -scrollView.contentInset.top) { frame.origin.y = 20; } else if ((scrollOffset + scrollHeight) >= scrollContentSizeHeight) { frame.origin.y = -size; } else { frame.origin.y = MIN(20, MAX(-size, frame.origin.y - scrollDiff)); } [self.navigationController.navigationBar setFrame:frame]; [self updateBarButtonItems:(1 - framePercentageHidden)]; self.previousScrollViewYOffset = scrollOffset; } - (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView { [self stoppedScrolling]; } - (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate { if (!decelerate) { [self stoppedScrolling]; } } 

你还需要这些辅助方法:

 - (void)stoppedScrolling { CGRect frame = self.navigationController.navigationBar.frame; if (frame.origin.y < 20) { [self animateNavBarTo:-(frame.size.height - 21)]; } } - (void)updateBarButtonItems:(CGFloat)alpha { [self.navigationItem.leftBarButtonItems enumerateObjectsUsingBlock:^(UIBarButtonItem* item, NSUInteger i, BOOL *stop) { item.customView.alpha = alpha; }]; [self.navigationItem.rightBarButtonItems enumerateObjectsUsingBlock:^(UIBarButtonItem* item, NSUInteger i, BOOL *stop) { item.customView.alpha = alpha; }]; self.navigationItem.titleView.alpha = alpha; self.navigationController.navigationBar.tintColor = [self.navigationController.navigationBar.tintColor colorWithAlphaComponent:alpha]; } - (void)animateNavBarTo:(CGFloat)y { [UIView animateWithDuration:0.2 animations:^{ CGRect frame = self.navigationController.navigationBar.frame; CGFloat alpha = (frame.origin.y >= y ? 0 : 1); frame.origin.y = y; [self.navigationController.navigationBar setFrame:frame]; [self updateBarButtonItems:alpha]; }]; } 

对于稍有不同的行为,用滚动条replace滚动条( scrollViewDidScrollelse块)时重新定位条的行:

 frame.origin.y = MIN(20, MAX(-size, frame.origin.y - (frame.size.height * (scrollDiff / scrollHeight)))); 

这根据最后一个滚动百分比而不是绝对数量来定位栏,这会导致较慢的淡入淡出。 原来的行为更像Facebook,但我也喜欢这个。

注意:此解决scheme仅适用于iOS 7+。 如果您支持较早版本的iOS,请务必添加必要的检查。

编辑:只适用于iOS 8及以上版本。

你可以尝试使用

 self.navigationController.hidesBarsOnSwipe = YES; 

为我工作。

如果你在swift编码,你必须使用这种方式(从https://stackoverflow.com/a/27662702/2283308

 navigationController?.hidesBarsOnSwipe = true 

这里是一个更多的实现: TLYShyNavBar v1.0.0发布!

我决定尝试提供的解决scheme后,我自己做,而对我来说,他们performance不佳,有一个高入口和锅炉板代码的障碍,或缺乏导航栏下的扩展视图。 要使用这个组件,你所要做的就是:

 self.shyNavBarManager.scrollView = self.scrollView; 

哦,这是在我们自己的应用程序进行testing。

你可以看看我的GTScrollNavigationBar 。 我已经subclassed UINavigationBar使其滚动基于UIScrollView的滚动。

注意:如果你有一个OPAQUE导航条,滚动视图必须EXPAND,因为导航条得到了HIDDEN。 这正是GTScrollNavigationBar所做的。 (就像iOS上的Safari一样)

iOS8包含的属性可以让导航栏免费隐藏。 有一个WWDCvideo演示它,search“视图控制器在iOS 8中的进步”。

例如

 class QuotesTableViewController: UITableViewController { override func viewDidAppear(animated: Bool) { super.viewDidAppear(animated) navigationController?.hidesBarsOnSwipe = true } 

}

其他属性:

 class UINavigationController : UIViewController { //... truncated /// When the keyboard appears, the navigation controller's navigationBar toolbar will be hidden. The bars will remain hidden when the keyboard dismisses, but a tap in the content area will show them. @availability(iOS, introduced=8.0) var hidesBarsWhenKeyboardAppears: Bool /// When the user swipes, the navigation controller's navigationBar & toolbar will be hidden (on a swipe up) or shown (on a swipe down). The toolbar only participates if it has items. @availability(iOS, introduced=8.0) var hidesBarsOnSwipe: Bool /// The gesture recognizer that triggers if the bars will hide or show due to a swipe. Do not change the delegate or attempt to replace this gesture by overriding this method. @availability(iOS, introduced=8.0) var barHideOnSwipeGestureRecognizer: UIPanGestureRecognizer { get } /// When the UINavigationController's vertical size class is compact, hide the UINavigationBar and UIToolbar. Unhandled taps in the regions that would normally be occupied by these bars will reveal the bars. @availability(iOS, introduced=8.0) var hidesBarsWhenVerticallyCompact: Bool /// When the user taps, the navigation controller's navigationBar & toolbar will be hidden or shown, depending on the hidden state of the navigationBar. The toolbar will only be shown if it has items to display. @availability(iOS, introduced=8.0) var hidesBarsOnTap: Bool /// The gesture recognizer used to recognize if the bars will hide or show due to a tap in content. Do not change the delegate or attempt to replace this gesture by overriding this method. @availability(iOS, introduced=8.0) unowned(unsafe) var barHideOnTapGestureRecognizer: UITapGestureRecognizer { get } } 

通过http://natashatherobot.com/navigation-bar-interactions-ios8/find

我有一些快速和肮脏的解决scheme。 没有做任何深入的testing,但这里的想法是:

该属性将保留在我的UITableViewController类的导航栏中的所有项目

 @property (strong, nonatomic) NSArray *navBarItems; 

在同一个UITableViewController类中我有:

 -(void)scrollViewDidScrollToTop:(UIScrollView *)scrollView { if([[[UIDevice currentDevice] systemVersion] floatValue] < 7.0f){ return; } CGRect frame = self.navigationController.navigationBar.frame; frame.origin.y = 20; if(self.navBarItems.count > 0){ [self.navigationController.navigationBar setItems:self.navBarItems]; } [self.navigationController.navigationBar setFrame:frame]; } -(void)scrollViewDidScroll:(UIScrollView *)scrollView { if([[[UIDevice currentDevice] systemVersion] floatValue] < 7.0f){ return; } CGRect frame = self.navigationController.navigationBar.frame; CGFloat size = frame.size.height - 21; if([scrollView.panGestureRecognizer translationInView:self.view].y < 0) { frame.origin.y = -size; if(self.navigationController.navigationBar.items.count > 0){ self.navBarItems = [self.navigationController.navigationBar.items copy]; [self.navigationController.navigationBar setItems:nil]; } } else if([scrollView.panGestureRecognizer translationInView:self.view].y > 0) { frame.origin.y = 20; if(self.navBarItems.count > 0){ [self.navigationController.navigationBar setItems:self.navBarItems]; } } [UIView beginAnimations:@"toggleNavBar" context:nil]; [UIView setAnimationDuration:0.2]; [self.navigationController.navigationBar setFrame:frame]; [UIView commitAnimations]; } 

这只是为ios> = 7,这是我知道的丑陋,但一个快速的方法来实现这一点。 任何意见/build议,欢迎:)

这是我的实现: SherginScrollableNavigationBar 。

在我的方法中,我使用KVO来观察UIScrollView的状态,所以没有必要使用一个委托(你可以使用这个委托来完成你所需要的任何事情)。

这适用于iOS 8及以上版本,并确保状态栏仍保留其背景

 self.navigationController.hidesBarsOnSwipe = YES; CGRect statuBarFrame = [UIApplication sharedApplication].statusBarFrame; UIView *statusbarBg = [[UIView alloc] initWithFrame:statuBarFrame]; statusbarBg.backgroundColor = [UIColor blackColor]; [self.navigationController.view addSubview:statusbarBg]; 

如果你想在状态栏上点击导航栏,你可以这样做:

 - (void)scrollViewDidScrollToTop:(UIScrollView *)scrollView { self.navigationController.navigationBarHidden = NO; } 

请尝试我的这个解决scheme,让我知道为什么这不如以前的答案。

 - (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset { if (fabs(velocity.y) > 1) [self hideTopBar:(velocity.y > 0)]; } - (void)hideTopBar:(BOOL)hide { [self.navigationController setNavigationBarHidden:hide animated:YES]; [[UIApplication sharedApplication] setStatusBarHidden:hide withAnimation:UIStatusBarAnimationSlide]; } 

我已经完成的一个方法如下。

注册你的视图控制器,例如你的UITableViewUIScrollViewDelegate

 - (void)scrollViewDidScroll:(UIScrollView *)scrollView; - (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView; - (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate; 

从de UIScrollViewDelegate方法中,您可以获得新的contentOffset并相应地将UINavigationBar相应的翻译。

设置子视图的alpha也可以基于一些可以设置和计算的阈值和因素来完成。

希望能帮助到你!

除了Iwburk的回答之外,我还添加了以下内容来解决非自定义导航栏上的alpha问题,并重置viewWillDisappear方法中的导航栏:

 - (void)updateBarButtonItems:(CGFloat)alpha { for (UIView *view in self.navigationController.navigationBar.subviews) { NSString *className = NSStringFromClass([view class]); if ( ![className isEqualToString:@"_UINavigationBarBackground"] ) { view.alpha = alpha; } } } - (void)resetNavigationBar { CGRect frame = self.navigationController.navigationBar.frame; frame.origin.y = 20; [self.navigationController.navigationBar setFrame:frame]; [self updateBarButtonItems:1.0f]; } 

我正在寻找一个解决scheme,允许任何风格和任何行为。 你会注意到,酒吧凝结行为在许多不同的应用程序是不同的。 当然,应用程序之间的酒吧看起来完全不同。

我用https://github.com/bryankeller/BLKFlexibleHeightBar/为这个问题创build了一个解决scheme

您可以定义自己的行为规则,以控制酒吧如何以及何时缩小和增长,并且可以准确定义酒吧的子视图如何对酒吧冷凝或成长起反应。

看看我的项目,如果你想要很大的灵活性,使任何forms的标题栏,你可以想出来。

我试着实现GTScrollNavigationBar,但我的应用程序需要我修改自动布局约束。 我决定在GitHub上提供一个例子,以防其他人必须使用自动布局来做到这一点。 我与其他大多数其他实现的另一个问题是,人们不设置滚动视图的边界以避免您在滚动时创build的视差滚动效果,并同时调整滚动视图的大小。

如果您需要使用自动布局执行此操作,请查看JSCollapsingNavBarViewController 。 我已经包含了两个版本,一个只有导航栏,另一个在导航栏下方有一个子栏,它在折叠导航栏之前折叠起来。

我试图模仿这种行为,在我需要一个定制的标题坐在一个UITableView的情况下。 我推出了自己的“导航”栏,因为它位于页面上的一堆其他东西下面,我希望节标题遵循默认的“对接”行为。 我觉得我find了一个相当聪明和简洁的方式来调整一个UITableView / UIScrollView与另一个对象的风格类似于在Facebook / Instagram / Chrome /等看到的风格。 应用。

在我的.xib文件中,我把我的组件加载到自由格式的视图中: http : //imgur.com/0z9yebJ (对不起,没有内联图像的代表)

请注意,在左侧边栏中,表格是在主标题视图后面订购的。 你不能从截图中看出来,但是它也和主标题视图有相同的y位置。 由于它延伸出来,UITableView的contentInset属性设置为76(主标题视图的高度)。

为了使主标题视图与UIScrollView一起滑动,我使用UIScrollViewDelegate的scrollViewDidScroll方法来执行一些计算,并更改UIScrollView的contentInset以及主标题视图的框架。

 - (void)scrollViewDidScroll:(UIScrollView *)scrollView { UIEdgeInsets insets = scrollView.contentInset; //tableViewInsetDelta and tableViewOriginalInsetValue are NSInteger variables that I set to 0 and 76, respectively, in viewDidLoad tableViewInsetDelta = tableViewOriginalInsetValue + scrollView.contentOffset.y; insets.top = tableViewOriginalInsetValue - tableViewInsetDelta; if (scrollView.contentOffset.y > -76 && scrollView.contentOffset.y < 0) { [scrollView setContentInset:insets]; self.pathTitleContainer.frame = CGRectMake(self.pathTitleContainer.frame.origin.x, 44 - tableViewInsetDelta, self.pathTitleContainer.frame.size.width, self.pathTitleContainer.frame.size.height); } else if (scrollView.contentOffset.y > 0) { insets.top = 0; [scrollView setContentInset:insets]; self.pathTitleContainer.frame = CGRectMake(self.pathTitleContainer.frame.origin.x, -32, self.pathTitleContainer.frame.size.width, self.pathTitleContainer.frame.size.height); } else if (scrollView.contentOffset.y < -76) { insets.top = 76; [scrollView setContentInset:insets]; self.pathTitleContainer.frame = CGRectMake(self.pathTitleContainer.frame.origin.x, 44, self.pathTitleContainer.frame.size.width, self.pathTitleContainer.frame.size.height); } } 

第一个if语句完成大部分繁重的工作,但是我必须包含另外两个才能处理用户强行拖动的情况,并且发送给scrollViewDidScroll的初始contentOffset值不在第一个if语句的范围之内。

最终,这对我来说真的很好。 我讨厌用一堆臃肿的子类加载我的项目。 我不能说这是否是性能最好的解决scheme(我一直犹豫不决,因为它始终被调用,所以在scrollViewDidScroll中放置任何代码),但是代码覆盖面是我见过的最小的这个问题的解决scheme,它不涉及在UIScrollView中嵌套UITableView(苹果build议在文档和触摸事件在UITableView有点时髦)。 希望这可以帮助别人!

HidingNavigationBar一个伟大的项目,隐藏导航栏选项卡栏,如果你想。

HidingNavigationBar支持隐藏/显示以下视图元素:

UINavigationBar的

UINavigationBar和一个扩展UIView

UINavigationBar和一个UIToolbar

UINavigationBar和一个UITabBar

https://github.com/tristanhimmelman/HidingNavigationBar

我用这种方式试了一下,希望能帮上忙。 只需在委托方法中实现代码并设置为所需的视图/子视图

 -(void)scrollViewDidScroll:(UIScrollView *)scrollView{ CGRect frame=self.view.frame; CGRect resultFrame=CGRectZero; if(scrollView.contentOffset.y==0 || scrollView.contentOffset.y<0){ self.lastContentOffset=0; self.offset=0; resultFrame=CGRectMake(0, frame.size.height-(40-self.offset.intValue), frame.size.width, 40-self.offset.intValue); // Pass the resultFrame [self showHide:YES withFrame:resultFrame]; }else if (self.lastContentOffset > scrollView.contentOffset.y){ NSNumber *temp=[NSNumber numberWithDouble:self.lastContentOffset-scrollView.contentOffset.y]; if(temp.intValue>40 || self.offset.intValue<temp.intValue){ self.offset=[NSNumber numberWithInt:0]; resultFrame=CGRectMake(0, frame.size.height-(40-self.offset.intValue), frame.size.width, 40-self.offset.intValue); // Pass the resultFrame [self showHide:YES withFrame:resultFrame]; }else{ if(temp.intValue>0){ self.offset=[NSNumber numberWithInt:self.offset.intValue-temp.intValue]; resultFrame=CGRectMake(0, frame.size.height-(40-self.offset.intValue), frame.size.width, 40-self.offset.intValue); // Pass the resultFrame [self showHide:YES withFrame:resultFrame]; } } }else if (self.lastContentOffset < scrollView.contentOffset.y){ NSNumber *temp=[NSNumber numberWithDouble:scrollView.contentOffset.y-self.lastContentOffset]; if(self.offset.intValue>40 || (self.offset.intValue+temp.intValue)>40){ self.offset=[NSNumber numberWithInt:40]; // Pass the resultFrame [self showHide:NO withFrame:resultFrame]; }else{ self.offset=[NSNumber numberWithInt:self.offset.intValue+temp.intValue]; resultFrame=CGRectMake(0, frame.size.height-(40-self.offset.intValue), frame.size.width, 40-self.offset.intValue); // Pass the resultFrame [self showHide:YES withFrame:resultFrame]; } } self.lastContentOffset = scrollView.contentOffset.y; } -(void)showHide:(Boolean)boolView withFrame:(CGRect)frame{ if(showSRPFilter){ //Assign value of "frame"to any view on which you wan to to perform animation }else{ //Assign value of "frame"to any view on which you wan to to perform animation } } 

@Iwburk的答案的扩展…我不需要改变导航栏的原点,而需要扩大/缩小导航栏的大小。

 - (void)scrollViewDidScroll:(UIScrollView *)scrollView { CGRect frame = self.previousRect; // a property set in the init method to hold the initial size of the uinavigationbar CGFloat size = frame.size.height; CGFloat framePercentageHidden = ((MINIMUMNAVBARHEIGHT - frame.origin.y) / (frame.size.height - 1)); CGFloat scrollOffset = scrollView.contentOffset.y; CGFloat scrollDiff = scrollOffset - self.previousScrollViewYOffset; CGFloat scrollHeight = scrollView.frame.size.height; CGFloat scrollContentSizeHeight = scrollView.contentSize.height + scrollView.contentInset.bottom; if (scrollOffset <= -scrollView.contentInset.top) { frame.origin.y = -MINIMUMNAVBARHEIGHT; } else if ((scrollOffset + scrollHeight) >= scrollContentSizeHeight) { frame.origin.y = -size; } else { frame.origin.y = MIN(-MINIMUMNAVBARHEIGHT, MAX(-size, frame.origin.y - scrollDiff)); } self.previousRect = CGRectMake(0, frame.origin.y, self.jsExtendedBarView.frame.size.width, 155); self.layoutConstraintExtendedViewHeight.constant = MAXIMUMNAVBARHEIGHT + frame.origin.y + MINIMUMNAVBARHEIGHT; [self updateBarButtonItems:(1 - framePercentageHidden)]; self.previousScrollViewYOffset = scrollOffset; } 

它不能与stoppedScrolling方法一起工作,生病后我有一个更新

所有这些方法看起来都过于复杂……所以自然而然地,我build立了自己的:

 class ViewController: UIViewController, UIScrollViewDelegate { var originalNavbarHeight:CGFloat = 0.0 var minimumNavbarHeight:CGFloat = 0 weak var scrollView:UIScrollView! override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view, typically from a nib. // setup delegates scrollView.delegate = self // save the original nav bar height originalNavbarHeight = navigationController!.navigationBar.height } func scrollViewDidScroll(scrollView: UIScrollView) { // will relayout subviews view.setNeedsLayout() // calls viewDidLayoutSubviews } override func viewDidLayoutSubviews() { var percentageScrolled = min(scrollView.contentOffset.y / originalNavbarHeight, 1) navigationController?.navigationBar.height = min(max((1 - percentageScrolled) * originalNavbarHeight, minimumNavbarHeight), originalNavbarHeight) // re-position and scale scrollview scrollView.y = navigationController!.navigationBar.height + UIApplication.sharedApplication().statusBarFrame.height scrollView.height = view.height - scrollView.y } override func viewWillDisappear(animated: Bool) { navigationController?.navigationBar.height = originalNavbarHeight } } 

我发现在Objective-C中给出的所有答案。 这是我在Swift 3中的答案。这是非常通用的代码,可以直接使用。 它适用于UIScrollView和UITableView。

 var lastContentOffset: CGPoint? = nil var maxMinus: CGFloat = -24.0 var maxPlus: CGFloat = 20.0 var initial: CGFloat = 0.0 override func viewDidLoad() { super.viewDidLoad() self.title = "Alarm Details" self.lastContentOffset = self.alarmDetailsTableView.contentOffset initial = maxPlus } func scrollViewDidScroll(_ scrollView: UIScrollView) { var navigationBarFrame: CGRect = self.navigationController!.navigationBar.frame let currentOffset = scrollView.contentOffset if (currentOffset.y > (self.lastContentOffset?.y)!) { if currentOffset.y > 0 { initial = initial - fabs(CGFloat(currentOffset.y - self.lastContentOffset!.y)) } else if scrollView.contentSize.height < scrollView.frame.size.height { initial = initial + fabs(CGFloat(currentOffset.y - self.lastContentOffset!.y)) } } else { if currentOffset.y < scrollView.contentSize.height - scrollView.frame.size.height { initial = initial + fabs(CGFloat(currentOffset.y - self.lastContentOffset!.y)) } else if scrollView.contentSize.height < scrollView.frame.size.height && initial < maxPlus { initial = initial - fabs(CGFloat(currentOffset.y - self.lastContentOffset!.y)) } } initial = (initial <= maxMinus) ? maxMinus : initial initial = (initial >= maxPlus) ? maxPlus : initial navigationBarFrame.origin.y = initial self.navigationController!.navigationBar.frame = navigationBarFrame scrollView.frame = CGRect(x: 0.0, y: initial + navigationBarFrame.size.height , width: navigationBarFrame.size.width, height: self.view.frame.size.height - (initial + navigationBarFrame.size.height)) let framePercentageHidden: CGFloat = ((20 - navigationBarFrame.origin.y) / (navigationBarFrame.size.height)); self.lastContentOffset = currentOffset; self.updateBarButtonItems(alpha: 1 - framePercentageHidden) } func updateBarButtonItems(alpha: CGFloat) { self.navigationController?.navigationBar.titleTextAttributes = [NSForegroundColorAttributeName: UIColor.darkGray.withAlphaComponent(alpha)] self.navigationController?.navigationBar.isUserInteractionEnabled = (alpha < 1) ? false: true guard (self.navigationItem.leftBarButtonItems?.count) != nil else { return } for (_, value) in self.navigationItem.leftBarButtonItems!.enumerated() { value.customView?.alpha = alpha } guard (self.navigationItem.rightBarButtonItems?.count) != nil else { return } for (_, value) in (self.navigationItem.rightBarButtonItems?.enumerated())! { value.customView?.alpha = alpha } } 

将导航项设置为alpha的逻辑是从@ WayneBurkett答案复制的,并在Swift 3中进行了重写。