实现导航栏动态变化交互效果的套路


| 导语 套路,在武术中是指一连串含有技击和攻防含义的动作组合,在本文中,指的就是一套实现特定功能的代码组合。
一、交互效果 如图所示,导航栏随列表滑动而动态改变背景色和标题显隐,这种交互设计比较常见,那该怎么去实现这样的交互效果呢?想必大体的思路大家都能想得到,首先是让VC的view的布局从屏幕上边缘开始,在UITableView滚动回调方法中根据偏移量改变导航栏的背景透明度。思路简单,但是具体到实现却是有不少技术细节要注意。下面我将分享我所在iOS项目实现导航栏动态变化交互效果的套路。

二、代码套路 第一步,如何让view的布局从屏幕顶部边缘开始,到TabBar的顶部截止?
iOS 7 之后,UINavigationBar和UITabBar默认都变成了半透明,系统也调整了view的布局,默认使用全屏布局(full-screen layout),即铺满整个屏幕。为了让开发者可以自主控制view的布局,UIViewController新增了edgesForExtendedLayout属性,通过设置此属性,你可以指定view的边(上、下、左、右)延伸到整屏幕。

@property(nonatomic,assign) UIRectEdge edgesForExtendedLayout NS_AVAILABLE_IOS(7_0); typedef NS_OPTIONS(NSUInteger, UIRectEdge) {UIRectEdgeNone = 0,UIRectEdgeTop = 1 << 0,UIRectEdgeLeft = 1 << 1,UIRectEdgeBottom = 1 << 2,UIRectEdgeRight = 1 << 3,UIRectEdgeAll = UIRectEdgeTop | UIRectEdgeLeft | UIRectEdgeBottom | UIRectEdgeRight} NS_ENUM_AVAILABLE_IOS(7_0);

刚刚说到UINavigationBar和UITabBar默认是半透明,但如果是不透明,你会发现设置edgesForExtendedLayout没有效果,如何在不透明的情况下也能延伸到整屏幕呢。UIViewController同时新增了一个extendedLayoutIncludesOpaqueBarssh属性,默认是NO,如果设置为YES,则在Bar不透明的情况下,edgesForExtendedLayout也能生效。所以套路的第一步是:
self.edgesForExtendedLayout = UIRectEdgeTop; self.extendedLayoutIncludesOpaqueBars = YES;

第二步,如何动态改变导航栏的透明度和标题?
UINavigationBar的translucent、barTintColor、backgroundImage三者共同决定了UINavigationBar的背景效果,而translucent在中间起非常关键的作用,它决定了系统如何使用barTintColor和backgroundImage去设置UINavigationBar的背景效果。如果不了解其中细节的,强烈推荐我的另一篇文章《UINavigationBar的translucent、barTintColor、backgroundImage》下面直接分享动态改变导航栏的透明度和标题的最佳实践写法:
1. 不主动去设置UINavigationBar的translucent为YES,因为如果主动设置为YES,则导航栏无法做到完全不透明;
2. 通过setBackgroundImage:forBarMetrics: 的方式来设置导航栏背景,以达到控制背景效果的目的,为什么要怎样操作,在我刚刚推荐的文章里有详细的分析;
3. backgroundImage通过颜色生成,通过改变颜色alpha值,来动态控制图片的透明度,最终控制导航栏透明度;
4. 如果导航栏已经是不透明的背景了,就不需要再重复设置,减少颜色转图片的资源消耗,可以通过translucent的值是否为NO来判断是否已经设为不透明背景;
5. 通过设置self.navigationItem.titlel来控制标题的变化,而不是self.title, 因为self.title会同时改变tabBar是的title;
所以套路的第二步是:
// 根据scrollView的偏移量来动态计算alpha值 - (void)scrollViewDidScroll:(UIScrollView *)scrollView { CGPoint offset = scrollView.contentOffset; CGFloat alpha = 1.0; if (offset.y <= 0) { alpha = 0; } else if (offset.y < TOP_SAFE_OFFSET) { alpha = 1.0 - (TOP_SAFE_OFFSET - offset.y)/TOP_SAFE_OFFSET; } else { alpha = 1.0; } if (!(alpha == 1 && !self.navigationController.navigationBar.translucent)) { //如果导航栏背景已经是不透明,那就不再需要重复设置了,减少颜色转图片的开销 [self.navigationController.navigationBar ig_setBackgroundImageByColor:HEXACOLOR(IGAME_NAVIBAR_COLOUR_VALUE, alpha) forBarMetrics:UIBarMetricsDefault]; } self.navigationItem.title = alpha > 0.2 ? @"赛事":@""; }

项目中导航栏一般是纯色背景,所以提供一个分类方法,快速通过颜色来设置背景图
@interface UINavigationBar (IGAppearance)- (void)ig_setBackgroundImageByColor:(UIColor *)imageColor forBarMetrics:(UIBarMetrics)barMetrics; @end

通过颜色来创建图片的方法
@interface UIImage (IGUIKit)+ (nullable UIImage *)imageWithColor:(UIColor *)color; @end

第三步,如何让tableView不自动偏移?
如图,当我们设置了view从顶部边缘开始布局之后,发现tableView会自动偏移,使内容从导航栏底部开始,为什么会发生偏移呢? 因为tableview从屏幕顶部开始布局,为了不让导航栏遮住tableView的内容,系统默认会自动调整tableview的contentInset,我们可以打印一下tableView,可以看到一个adjustedContentInset: {64, 0, 0, 0}。adjustedContentInset是iOS 11之后新增的属性。如果是iOS 11之前,那就是调整contentInset。
; layer = ; contentOffset: {0, -62}; contentSize: {375, 2068}; adjustedContentInset: {64, 0, 0, 0}>(lldb) po NSStringFromUIEdgeInsets(self.tableView.contentInset){64, 0, 0, 0}

那如何禁止系统自动调整tableView的contentInset呢?下面就是我们套路的最后一步:
if (@available(iOS 11.0, *)) { self.tableView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever; } else { self.automaticallyAdjustsScrollViewInsets = NO; }

【实现导航栏动态变化交互效果的套路】
通过以上三个步骤,我们便能轻轻松松实现导航栏动态变化交互效果。感谢大家的阅读,欢迎大家提出问题和建议!

    推荐阅读