类似文章挺多的,大家都说的非常好,我就是凑凑热闹......
应用场景:
当前界面的设置一个透明的navigationbar的背景,并且实现渐变显示或隐藏的动画,二级界面的navigationbar的背景不透明,使用APP的主题背景色(例如:不透明且无毛玻璃效果);
实现效果:
实现思路:
将系统原有导航栏的背景设置为透明色,同时在每个 ViewController 上添加一个 View 或者 NavigationBar 来充当我们实际看到的导航栏,每个 ViewController 同样只需要关心自身的样式即可。
第一步:增加一个属性
用于记录对navigationbar背景色的引用,用于实现navigationbar背景色变换动画;
/**
记录对navigationbar背景色的引用,用于实现背景色变换动画;
*/
@property (nonatomic, strong) UIView *navigationBarBgViewRef;
第二步:在viewDidLoad中加入代码:
(1)打开navigationbar的背景毛玻璃效果;
(2)设置navigationbar的背景为一张透明图片;
(3)在self.view中添加一张模拟出来的navigaionbar的背景图片;
/**
不使用navigationbar自带的背景颜色,
而是使用自己模拟的navigationbar背景色
*/
[self.navigationController.navigationBar setBackgroundImage:[UIImage new] forBarMetrics:UIBarMetricsDefault];
self.navigationBarBgViewRef = [self appendSimulatorNavigationBarBgViewWithColor:COLOR_THEME];
第三步:在viewWillApear中加入代码:
因为在本界面的二级界面中可能会修改navigationbar的样式,所以当回到本界面时,要恢复到本界面应当显示的效果;
(1)打开navigationbar的背景毛玻璃效果;
(2)设置navigationbar的背景为一张透明图片;
self.navigationController.navigationBar.translucent = YES;
[self.navigationController.navigationBar setBackgroundImage:[UIImage new] forBarMetrics:UIBarMetricsDefault];
self.navigationBarBgViewRef.alpha = 0;
补充相关代码
#define WIDTH_SCREEN [[UIScreen mainScreen] bounds].size.width //屏幕宽度
#define HEIGHT_SCREEN [[UIScreen mainScreen] bounds].size.height //屏幕高度
#define IS_IPHONE_X (WIDTH_SCREEN == 375.f && HEIGHT_SCREEN == 812.f ? YES : NO)
#define IS_IPHONE_XSMAX (WIDTH_SCREEN == 414.f && HEIGHT_SCREEN == 896.f ? YES : NO)
#define HEIGHT_STATUSBAR (IS_IPHONE_X || IS_IPHONE_XSMAX ? 44.f : 20.f)
#define HEIGHT_NAVIGATION_BAR 44
- (UIView *)appendSimulatorNavigationBarBgViewWithColor:(UIColor *)color {
NSInteger simulatorBgViewTag = -9834534;//该值是胡乱定义的,就是个tag而已
if([self.view viewWithTag:simulatorBgViewTag]){
UIView *simulatorView = [self.view viewWithTag:simulatorBgViewTag];
[simulatorView removeFromSuperview];
}
CGFloat height = HEIGHT_NAVIGATION_BAR + HEIGHT_STATUSBAR;
CGSize size = CGSizeMake(WIDTH_SCREEN, height);
UIImage *imgNavBg = [UIImage imageWithColor:color size:size];
UIImageView *imgview = [[UIImageView alloc] initWithImage:imgNavBg];
imgview.contentMode = UIViewContentModeScaleToFill;
imgview.frame = CGRectMake(0, -1 * height, WIDTH_SCREEN,height);
imgview.tag = simulatorBgViewTag;//胡乱输入的
[self.view addSubview:imgview];
return imgview;
}
在界面切换时,实现navigationbar转场变换效果有三种方法,在美团《iOS系统中导航栏的转场解决方案与最佳实践》中有很详细的描述,这里仅摘抄部分,本文中提到的方案其实就是美团这篇文章中说到的方案二,文章的链接如下:
https://tech.meituan.com/navigation_transition_solution_and_best_practice_in_meituan.html
常见的解决方案如下所示:【摘抄自美团思琦《iOS系统中导航栏的转场解决方案与最佳实践》】
-
重新实现一个类似 UINavigationController 的容器类视图管理器,这个容器类视图管理器做好不同 ViewController 间的导航栏样式转换工作,而每个 ViewController 只需要关心自身的样式即可。
-
将系统原有导航栏的背景设置为透明色,同时在每个 ViewController 上添加一个 View 或者 NavigationBar 来充当我们实际看到的导航栏,每个 ViewController 同样只需要关心自身的样式即可。
-
在转场的过程中隐藏原有的导航栏并添加假的 NavigationBar,当转场结束后删除假的 NavigationBar 并恢复原有的导航栏,这一过程可以通过 Swizzle 的方式完成,而每个 ViewController 只需要关心自身的样式即可。
这三种方案各有优劣,我们在网上也可以看到很多关于它们的讨论。
例如方案一,虽然看起来工作量大且难度高,但是这个工作一旦完成,我们就会将处理导航栏转场的主动权牢牢抓在手里。但这个方案的一个弊端就是,如果苹果修改了导航栏的整体风格,就好比 iOS 11 的大标题特效,那么工作量就来了。
对于方案二而言,虽然看起来简单易用,但这需要一个良好的继承关系,如果整个工程里的继承关系混乱或者是历史包袱比较重,后续的维护就像“打补丁”一样,另外这个方案也需要良好的团队代码规范和完善的技术文档来做辅助。
对于方案三而言,它不需要所谓的继承关系,使用起来也相对简单,这对于那些继承关系和历史包袱比较重的工程而言,这一个不错的解决方案,但在解决 Bug 的时候,Swizzle 这种方式无疑会增加解决问题的时间成本和学习成本。