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

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

你自己会如何实现这种行为呢?我知道下面的解决方案,但它立即消失,它并没有绑定到用户的滚动手势的速度在所有。

[navigationController setNavigationBarHidden: YES animated:YES];

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

95582 次浏览

我完成这个任务的方法之一是。

例如,将视图控制器注册为 UITableViewUIScrollViewDelegate

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

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

还可以根据一些阈值和可以设置和计算的因子来设置子视图的 alpha。

希望能有帮助!

我有个快速而肮脏的解决办法。虽然还没有进行深入的测试,但是我的想法是这样的:

该属性将保留我的 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,我知道这很难看,但这是一个快速实现的方法。欢迎提出意见或建议:)

你可以看看我的 导航栏。我已经子类化了 UINavigationBar,使它能够基于 UIScrollView 的滚动来滚动。

注意: 如果您有一个 OPAQUE 导航栏,scrollview 必须在导航栏被隐藏时使用 EXPAND。这正是 GTScrollNavigationBar 所做的。(就像 iOS 上的 Safari 一样。)

@ peerless 给出的解决方案是一个很好的开始,但它只在拖动开始时启动一个动画,而没有考虑滚动的速度。这样的体验比你在 Facebook 应用程序中得到的体验更加起伏不定。为了与 Facebook 的行为相匹配,我们需要:

  • 以与拖动速率成正比的速率隐藏/显示导航栏
  • 启动一个动画,以完全隐藏条,如果滚动停止时,条部分隐藏
  • 当导航栏收缩时,会淡出导航栏的项目。

首先,您需要以下属性:

@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];
}
}

您还需要这些 helper 方法:

- (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];
}];
}

对于稍微不同的行为,将在滚动时重新定位工具条的行(scrollViewDidScroll中的 else块)替换为以下行:

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

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

注意: 此解决方案仅适用于 iOS7 + 。如果您支持旧版本的 iOS,一定要添加必要的检查。

除了 Iwburk 的回答,我还添加了以下内容来修复非自定义导航栏的 alpha 问题,并在 viewWillDisalize 方法中重置导航栏:

- (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];
}

下面是我的实现: 导航栏

在我的方法中,我使用 KVO来观察 UIScrollView的状态,因此没有必要使用委托(您可以使用这个委托来满足其他任何需要)。

这里还有一个实现: TLYShyNavBar 发布!

在尝试了所提供的解决方案之后,我决定自己创建一个,对我来说,它们要么表现不佳,要么有很高的进入门槛和锅炉板代码,要么缺乏导航栏下面的扩展视图。要使用这个组件,您只需要:

self.shyNavBarManager.scrollView = self.scrollView;

哦,我们自己的应用程序对它进行了实战测试。

我尝试实现 GTScrollNavigationBar,但我的应用程序要求我修改自动布局约束。我决定在 GitHub 上放一个我的实现示例,以防其他人需要用自动布局来做这件事。我在其他大多数实现中遇到的另一个问题是,人们没有设置滚动视图的边界,以避免在同时滚动和调整滚动视图大小时产生的视差滚动效果。

检查 NavBarViewController,如果你需要这样做与自动布局。我已经包含了两个版本,一个只有导航条,另一个在导航条下面有一个子条,它会在导航条崩溃之前折叠起来。

我试图在需要一个定制的头部位于 UITableView 的情况下模仿这种行为。我滚动了我自己的“导航”栏,因为它位于页面上一堆其他东西的下面,我希望部分标题遵循默认的“停靠”行为。我想我找到了一个非常聪明和简洁的方法来调整一个 UITableView/UIScrollView 和另一个对象,其风格类似于在 Facebook/Instagram/Chrome 等应用程序中看到的风格。

在我的。Xib 文件中,我将组件加载到了一个自由视图: http://imgur.com/0z9yebJ(抱歉,没有内联图像的代表)

请注意,在左侧栏中,表是在主标题视图后面排序的。你不能从截图中看出来,但是它和主标题视图的 y 位置是一样的。由于 UITableView 上的 contentInset 属性扩展到看不见的地方,因此设置为76(主标题视图的高度)。

为了使主页眉视图与 UIScrollView 一起滑动,我使用了 UIScrollViewCommittee 的 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);
}
}

第一个 如果语句完成了大部分繁重的工作,但是我必须包含另外两个语句来处理这样的情况: 用户正在强制拖动,发送给 scrollViewDidScroll 的初始 contentOffset 值超出了第一个 如果语句的范围。

最终,这对我来说非常有效。我讨厌用一堆臃肿的子类加载我的项目。我不知道这是否是性能方面的最佳解决方案(我一直在犹豫是否要把任何代码放到 scrollViewDidScroll 中,因为它总是被调用) ,但是代码占用是我见过的解决这个问题的任何解决方案中最小的,而且它不涉及在 UIScrollView 中嵌套 UITableView (苹果在文档中建议不要这样做,触摸事件在 UITableView 上会有点时髦)。希望这对谁有帮助!

IOS8包含一些属性,可以免费获得隐藏导航栏的功能。有一个 WWDC 的视频演示了它,搜索“查看控制器在 iOS8中的进步”。

例子 :

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/找到的

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

你可以试试

self.navigationController.hidesBarsOnSwipe = YES;

我没问题。

如果您的编码迅速,你必须使用这种方式(从 https://stackoverflow.com/a/27662702/2283308)

navigationController?.hidesBarsOnSwipe = true

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

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

您可以定义您自己的行为规则来控制条收缩和增长的方式和时间,并且您可以精确地定义您希望条的子视图如何对条收缩或增长作出反应。

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

我用这种方法试过,希望能有帮助。 只需在委托方法中实现代码,并设置为所需的视图/子视图

-(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
}
}

所有这些方法似乎都过于复杂... ... 因此,我自然而然地构建了自己的方法:

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
}


}

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

- (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];
}

这是@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方法一起工作,当我有它的时候我会发布一个更新

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

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;
}

HidingNavigationBar 一个伟大的项目,如果你想隐藏导航栏 还有标签栏。

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

UINavigationBar

UINavigationBar 和扩展 UIView

UINavigationBar 和 UIToolbar

UINavigationBar 和 UITabBar

Https://github.com/tristanhimmelman/hidingnavigationbar

我找到了 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 设置为导航项的逻辑是从@Wayne Burkett应答中复制的,并在 Swift 3中重写。

适用于 Swift 4,5-iOS 11及以上版本

private var previousScrollViewYOffset: CGFloat = 0
private var firstLoad = true
// to avoid scrollViewDidScroll called when first time view controller load
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
firstLoad = false
}
// MARK: - UIScrollViewDelegate
extension ViewController: UIScrollViewDelegate {
func stoppedScrolling() {
let frame = self.navigationController?.navigationBar.frame ?? .zero
if frame.origin.y < UIView.statusBarFrame.size.height {
self.animateNavBar(to: -frame.size.height + UIView.statusBarFrame.size.height)
}
}
func updateBarButtonItems(alpha: CGFloat) {
self.navigationItem.leftBarButtonItems?.forEach{ item in
item.customView?.alpha = alpha
}
self.navigationItem.rightBarButtonItems?.forEach{ item in
item.customView?.alpha = alpha
}
self.navigationItem.titleView?.alpha = alpha
self.navigationController?.navigationBar.tintColor = self.navigationController?.navigationBar.tintColor.withAlphaComponent(alpha)
}
    

func animateNavBar(to y: CGFloat) {
UIView.animate(withDuration: 0.2) {[weak self] in
var frame: CGRect = self?.navigationController?.navigationBar.frame ?? .zero
let alpha: CGFloat = frame.origin.y >= y ? 0 : 1
frame.origin.y = y
self?.navigationController?.navigationBar.frame = frame
self?.updateBarButtonItems(alpha: alpha)
}
}
    

func scrollViewDidScroll(_ scrollView: UIScrollView) {
if firstLoad { return }
var frame = self.navigationController?.navigationBar.frame ?? .zero
let size = frame.size.height - UIView.statusBarFrame.size.height
let framePercentageHidden = (UIView.statusBarFrame.size.height - frame.origin.y) / (frame.size.height - 1)
let scrollOffset = scrollView.contentOffset.y
let scrollDiff = scrollOffset - previousScrollViewYOffset
let scrollHeight = scrollView.frame.size.height
let scrollContentSizeHeight = scrollView.contentSize.height + scrollView.contentInset.bottom
if scrollOffset <= -scrollView.contentInset.top {
frame.origin.y = UIView.statusBarFrame.size.height
} else if ((scrollOffset + scrollHeight) >= scrollContentSizeHeight) {
frame.origin.y = -size
} else {
frame.origin.y = min(UIView.statusBarFrame.size.height, max(-size, frame.origin.y - scrollDiff))
}
self.navigationController?.navigationBar.frame = frame
self.updateBarButtonItems(alpha: 1 - framePercentageHidden)
self.previousScrollViewYOffset = scrollOffset
}
    

func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
self.stoppedScrolling()
}
    

func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
if(!decelerate) {
self.stoppedScrolling()
}
}
}

UIView 扩展

extension UIView {
public static var statusBarFrame: CGRect {
get {
return UIApplication.shared.statusBarFrame
}
}
}

您应该自定义 navigationItem.titleView来应用设置 alpha