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