王者荣耀杨戬厉害吗什么时候出 王者荣耀新英雄

iOS开发之记一次App卡顿Bug的解决历程(踩了一个StoryBoard的坑)
时间: 10:14:51
&&&& 阅读:937
&&&& 评论:
&&&& 收藏:0
标签:虽然今天是周末,但是还是要学习的不是。写这篇博客的目的呢是记录一下自己在上次项目迭代中踩的坑,不过这个坑已经填上了。虽然坑不大,但是踩上去肯定能崴脚。其实还是那句话,在没人给你指路的情况下,踩的坑多了,慢慢的就成长了。为了填今天要讲的这个坑,午觉都没睡呢。当然今天博客的内容并不高深,而且出现的几率还是蛮大的,所以喽就记录一下。也许你已经踩过,或者你已经将此坑填上,但是今天是我踩了一脚呢,没办法,还是记录一下吧。
解决历程用一个字描述就是:&删&。
一、描述这个&坑&
首先呢,我们先来看一下这个Bug的出现场景。A页面Push到B页面时,在iOS9以下的系统上就会在A页面上卡顿一段时间,然后在Push到B页面。当然A页面和B页面的东西还是挺多的。下这么一描述,下方就贴出两张应用的截图吧,应用已经上线,大家可以支持一下呢,在博客中使用App中的两张截图应该并无大碍,想必公司也是会允许的呢, 当然项目的代码是肯定不会往博客上贴的呢。
下方截图就是两个出现Bug的页面,当然,这两个部分都是我在该项目的此次需求中负责的部分了。点击&工作室&页面的&工作室信息&按钮就会Push到工作室信息的页面。在点击按钮时此刻Bug就出现了,而且只有这两个页面跳转时才会卡顿。并在iOS9以下的系统中前两次进入到&工作室信息&页面时都会卡顿,而第三次就没有这个问题了,真是事不过三呢。
A页面是Swift纯代码写的,当然,使用的是SnapKit布的局。而B页面是Objective-C写的,并且用Storyboard做的。在iOS8或者iOS7上从A push到B页面就卡,而且只有在前两次进入的时候卡,再进的话就很顺畅。这到底是为什么呢?所以喽,就开始排查Bug的过程了。
二、找&坑&
Bug出现了,现象就是&卡顿&。意识到自己已经踩在坑上了,不过踩得坑在哪儿,踩得什么坑还得排查呢。下方就是定位的整个过程。
1、缩小&坑&的范围
在"A"页面中有6个UICollectionView的Cell,点击每个Cell都会跳转到相应的页面。跳转的这些页面有Swift写的,也有OC的,有纯代码的, 也有Storyboard实现的。可是唯独只有在进入B页面的时候卡顿。所以喽,应该是B页面相关的东西出了问题。然后我们就把目标转向B页面相关的ViewController和Storyboard上的VC。
于是乎,给B页面的ViewController的viewDidLoad方法添加了一个断点,观察卡顿时是否执行了。运行了一下,点击按钮跳转发生卡顿时发现下方的断点并没有执行。也就是说,在发生卡顿时B页面的视图并未加载完毕,那就是卡顿应该发生在视图加载完成前。
接着上面的结果接着排查,为了确定是B视图加载时发生的卡顿,于是乎就写了下方的这段代码来测试是否是loadView时发生的卡顿。果然呢,点击A页面相关Cell时,打印了一个&前&的Log,然后发生卡顿。卡顿后打印了一个&后&,紧接着进入和B页面。这样一来我们就明确知道,B视图在LoadView时发生了卡顿,而B页面的视图是通过Storyboard来加载的,所以卡顿的原因就在于B页面的Storyboard中控件的实现。
2、进一步找&坑&
经过上一步,我们把iOS8+上卡顿的bug定位在了B页面的Storyboard的具体实现上。下面这个页面是使用的Static TableView来做的,每一行是一个Cell,把整个TableView给删掉,再运行就不卡顿了。于是乎把TableView上一个个静态的Cell依次的删掉,每删一个Cell就运行依次。结果把这个&简介&这个Cell删掉后就不卡了。于是乎我们就把问题定位到了&简介&这个Cell上。
把页面上的状态恢复到初始状态,然后把&个人简介&这个TextView给删了,发现就不卡了。而加上个人简介这个TextView还是会卡顿。于是乎就把问题定位到了TextView上了。那是不是该TextView的约束加的有问题呢,于是乎又把TextView上的约束给删了,不过卡顿的问题还是存在的,那肯定不是该控件的约束问题了。那肯定就是这个TextView控件本身设置的问题。
经过上述所有步骤,最终我们将该卡顿的bug定位在了B页面中个人简介的UITextView控件的加载上。
三、填&坑&
经过上面的步骤,我们找到了&坑&的位置所在。接下来得填坑不是,然后我又仔细的检查了一遍这个TextView的所有配置项,实在是看不出什么问题。然后我就把这个UITextView给删了,然后又随便拖拽了一个,发现就没有卡顿这个问题了。这么神奇呢,然后我又对比了一下刚拖拽的这个UITextView,和我之前的TextView最大的区别就在于这个新的TextView的默认文字是英文的,而之前是中文的。
冥冥之中就感觉到这是问题所在,于是怀疑到&是不是当Storyboard中的TextView的默认值是中文的时后,视图加载渲染时非常耗时呢?&。这还得靠实验来说话,于是乎,把之前的&个人简介&改成了English,然后运行,结果卡顿的问题好了!当填上这个坑的时候我的心情是激动的,虽然填坑的手段极其简单,但是填坑的姿势还是重要的。其实找&坑&的过程,比填坑的过程要有意思的多呢。
四、此次踩坑总结
在博客的最后还是要总结一下的。在iOS7.0+, iOS8.0+上如果你VC布局是用Storyboard来实现的,并且在你的VC上有UITextView这个空间,并且你又在Storyboard上给这个UITextView设置了&中文&默认值的话,那么在前两次该VC加载视图时都会卡顿。应该是在加载视图是渲染这个TextView中的&中文&默认值比较耗时。解决方法是&把这个中文默认值在Storyboard中去掉,然后在ViewDidLoad方法中添加相应的默认值&。
为了再次验证该问题,我还特地写了个小的Demo。Demo比较简单,就是两个VC, 一个VC push出另一个VC。在目标VC中有一个TextView, 而这个TextView的默认值是&中文&字符。在这种情况下,iOS8和iOS7的系统上在PUSH时就会卡顿,在iOS9上就没有问题。然后把TextView的&中文&默认值改成英文,或者去掉就解决了这个问题。
上面的所有内容就是这个&踩坑&、&找坑&和&填坑&的过程。其实寻找Bug这个过程还是蛮有意思的,虽然没睡午觉,但是也不觉得困呢。OK,今天的博客就先到这。
&&国之画&&&& &&
版权所有 京ICP备号-2
迷上了代码!iOS:hidesBottomBarWhenPushed的正确用法 - isaced
October 12, 2013
iOS:hidesBottomBarWhenPushed的正确用法
今天说的是在TabBar嵌套Nav时,进行Push的时候隐藏TabBar的问题。
之前项目也需要这么做,那时候iOS7还没出,也是各种搜罗,后来的解决方法是当push操作的时候自己隐藏Tabbar,push过去视图拉伸适应屏幕,再pop回来的时候接再显示Tabbar,过程复杂还需要自己写动画,最终效果也不是很理想。
前两天公司APP上架,当时没有适配iOS7,在XCode4.6上开发编译并发布,居然在iOS7下跑起来没有太大的问题,只是一个Nav的文字错位,有闪退,不过勉强还能用,其中有自定义Tabbar,有很多动画,后来在一篇介绍iOS7适配的文章中看到这么一句话:
在Xcode 4.6上使用iOS 6 SDK进行编译的app在iOS
7上运行时是采用一种特殊的模拟模式,它试图保存app原来的样子。但是一旦你升级到了Xcode 5,在iOS
7SDK上编译,你的app就会开始出现状况了。
已经写的很清楚了,于是我再到Xcode5中编译运行原来的项目,就破漏百出了,开始完全崩溃,各种问题浮现。
好了,回到今天说的正题,先和大家说说hidesBottomBarWhenPushed,从这个属性名也能知道它的意思了,官方的解释是这样:
If YES, then when this view controller is pushed into a controller hierarchy with a bottom bar (like a tab bar), the bottom bar will slide out. Default is NO.
大致意思是如果为YES,当这个控制器push的时候,底部的Bar,比如Tabbar会滑走,也就是不会在push后的视图上显示出来,默认值为NO。
我讲的场景大概为这个样子:
最外面是一个TabBarController,套了两个NavgationController,当其中一个VC push下去的时候,一般情况是这样:
当隐藏Tabbar的时候再push,效果是这样:
怎么样,是不是有时候确实会遇到这样的情况?其实苹果真的考虑的很周全,为我们创造了hidesBottomBarWhenPushed这个属性,为了解决这个问题。代码非常简单,一句或者两句话即可,这里得分几种Push的情况。
Case1:xib加载或者Storyboard用identifier获取Controller
UIViewController *v2 = [self.storyboard instantiateViewControllerWithIdentifier:@&v2&];
v2.hidesBottomBarWhenPushed = YES;
[self.navigationController pushViewController:v2 animated:YES];
Case2:拉线,也就是Storyboard用performSegue
self.hidesBottomBarWhenPushed = YES;
[self performSegueWithIdentifier:@&tov2& sender:nil];
self.hidesBottomBarWhenPushed = NO;
Tip:经测试证明,此种方式只会对后面的一级生效,继续往后Push还会出现TabBar,要继续往后push也隐藏Tabbar还得使用Case3的方法,也建议如此!
Case3:拉线,在prepareForSegue函数里
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
[segue.destinationViewController setHidesBottomBarWhenPushed:YES];
更方便的做法:如果你在用 Storyboard,可以在 ViewController 的设置面板中把 Hide Bottom Bar on Push 属性勾选上,效果和上文代码一样。
暂时就用到这几点,我之前的做法,自己手动隐藏,拉伸view,显示不但麻烦,兼容性也不好,移到iOS7上问题多多,不过用这个属性可以非常方便的实现此需求,并且在iOS6上也完美兼容哦。
注意:还有个问题,这个属性只支持非自定义的Tabbar,也就是只支持原生Tabbar,如果是自定义的Tabbar会产生你意想不到的效果,我之前就遇到过,因为使用hidesBottomBarWhenPushed后,系统内部会处理TabbarController上Tabbar这个View,我之前自定义的Tabbar做法是吧原生Tabbar这个View隐藏掉,然后添加到自己绘制的Tabbar
View上去,缺点就是这样你的自定义的TabBarview接收不到系统应有的一些响应,于是我尝试着把自定义的TabBar
View添加到原来的TabBar View上,也就是不隐藏原生的TabBar,而是覆盖在上面,看不出任何区别,效果也能达到上面图片的效果!
Please enable JavaScript to view theiOS NavigationBar颜色、透明度、隐藏设置探究 - 简书
iOS NavigationBar颜色、透明度、隐藏设置探究
一、在项目开发中NavigationBar设置遇到的坑
  在平时的开发中,我们往往会遇到这样的需求,两个ViewController的NavigationBar颜色不同、透明度不同或者有的隐藏有的不隐藏,当两个ViewController进行push或pop操作时,那么你可能会看到下面现象:
两个ViewController的NavigationBar颜色不同,push/pop时颜色切换不和谐。
push/pop颜色切换不和谐
两个ViewController的NavigationBar隐藏设置不一样,push/pop时隐藏NavigationBar切换不和谐。
push/pop时隐藏NavigationBar切换不和谐
  很丑有木有O(≧口≦)O,强迫症接受不了有木有O(≧口≦)O。  导致出现上述问题的原因是navigationBar只有一个,改变navigationBar样式一定会影响其他ViewController的显示。
二、寻找解决方案
  隐藏与显示的bug解决比较简单,因为苹果已经做好了,出现上述问题的原因是,没有使用对的方法。  隐藏NavigationBar苹果提供了两个方法:[navigationController setNavigationBarHidden:]和[navigationController setNavigationBarHidden:animated:]。第一个方法无动画隐藏navigationBar,第二个方法可以控制是否动画隐藏navigationBar。解决上述bug只需如下代码:
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
[self.navigationController setNavigationBarHidden:NO animated:animated];
//不要使用下面方法,下面方法会导致隐藏和不隐藏的viewController转场时出现bug
[self.navigationController setNavigationBarHidden:NO];
  OK,隐藏与显示的bug解决了。效果如下:
正确的使用方法解决bug
  下面解决颜色的问题。在很多的APP都可以看到不同颜色的navigationBar的转场,但这些APP都完美的解决了颜色问题,比如微信。所以就要看看微信是如何处理的呢,这个时候就用到了一个款神奇,Mac软件Reveal,它可以看到在手机中安装的APP页面层级,至于如何使用请移步:。  通过Reveal看到了微信的页面层次,如下图
  观察微信的层级发现,微信的navigationBar是透明的,ViewController顶部有一个view来充当navigationBar背景色,如上图左右两边的发现ViewController和小程序ViewController的顶部都有一个view,左边是黑字的右边是白色的,这样每个viewController的navigationBar的背景色就可以单独设置。
三、有了指导方向,开始动手搬砖
  解决方法好像很简单,只需要在viewController.view的顶部加上一个barBgView就可以了(内心暗喜:这种代码我两分钟就可以敲完,啊哈哈哈ψ(`??)ψ)。  那么开始战斗吧,啊哈哈! 哈利路亚!德玛西亚!赐给我码神的力量吧ヽ(`Д?)?ヽ(`Д?)?ヽ(`Д?)?!  战斗没开始就发现遇到了坑,难道我要每个viewController里都写一遍加入barBgView的代码?不行viewController太多写起来太累;那写一个继承自viewController的父类,然后让所有viewController继承父类,不行那样还是每个viewController都要改;有没有让新项目改动极少的代码就可以实现的方法呢?答案是有的,只需要Category和黑科技Method Swizzling即可。
首先建两个UIViewController的Category.第一个为UIViewController+CFYNavigationBarTransition.h
// UIViewController CFYNavigationBarTransition
@interface UIViewController (CFYNavigationBarTransition)
设置导航栏是否隐藏
@param hidden 隐藏
@param animated 动画
- (void)cfy_setNavigationBarHidden:(BOOL)hidden animated:(BOOL)
第二个位UINavigationController+CFYNavigationBarTransition_Public.h
@interface UIViewController (CFYNavigationBarTransition_Public)
设置导航栏背景色
@param color 背景色
- (void)cfy_setNavigationBarBackgroundColor:(UIColor *)
设置背景图片
@param image 背景图
- (void)cfy_setNavigationBarBackgroundImage:(UIImage *)
设置导航栏透明度
@param alpha 透明度
- (void)cfy_setNavigationBarAlpha:(CGFloat)
@property (readonly) UIColor *cfy_navigationBarBackgroundC
@property (readonly) CGFloat cfy_navigationBarA
  两个都是UIViewController的Category,以public结尾的文件中是对外提供公开的方法和属性,也就是用户可以使用的方法和属性,另个则是放私有方法和属性。  两个category中方法和属性的实现都在UIViewController+CFYNavigationBarTransition.m中实现,实现逻辑加在了注释中
@interface UIViewController ()
cfy_navBarBgView,这个view就是核心,改变navigationBar颜色其实是改变cfy_navBarBgView的背景色
@property (nonatomic, strong) UIView *cfy_navBarBgV
用来判断view是否加载
@property (nonatomic, assign) BOOL cfy_viewA
保存navigationBar颜色
@property (nonatomic, strong) UIColor *cfy_navigationBarBackgroundC
保存navigationBar颜色透明度
@property (nonatomic, assign) CGFloat cfy_navigationBarA
@implementation UIViewController (CFYNavigationBarTransition)
在load中,swizzle四个方法viewDidLoad、viewWillLayoutSubviews、viewDidAppear:、viewDidDisappear:。
+(void)load {
CFYSwizzleMethod(self, @selector(viewDidLoad), @selector(cfy_viewDidLoad));
CFYSwizzleMethod(self, @selector(viewWillLayoutSubviews), @selector(cfy_viewWillLayoutSubviews));
CFYSwizzleMethod(self, @selector(viewDidAppear:), @selector(cfy_viewDidAppear:));
CFYSwizzleMethod(self, @selector(viewDidDisappear:), @selector(cfy_viewDidDisappear:));
在viewDidLoad中添加cfy_navBarBgView
- (void)cfy_viewDidLoad {
[self cfy_viewDidLoad];
// 如果存在navigationController则添加cfy_navBarBgView
if (self.navigationController) {
[self cfy_addNavBarBgView];
- (void)cfy_viewDidAppear:(BOOL)animated {
[self cfy_viewDidAppear:animated];
self.cfy_viewAppeared = YES;
- (void)cfy_viewDidDisappear:(BOOL)animated {
[self cfy_viewDidDisappear:YES];
self.cfy_viewAppeared = NO;
在viewWillLayoutSubviews中对cfy_navBarBgView进行处理,使cfy_navBarBgView能在不同环境正确显示
- (void)cfy_viewWillLayoutSubviews {
[self cfy_viewWillLayoutSubviews];
// 当前viewController没navigationController,直接退出
if (!self.navigationController) {
self.navigationController.navigationBar隐藏了,做一些处理。
如果在navigationBar隐藏时,旋转屏幕,这时如果不处理后并return,而是走下面的代码,那么并不能正确的获取到cfy_navBarBgView的frame。
所以在这里直接将cfy_navBarBgView的宽度设置成屏幕看度,其他不变保持cfy_navBarBgView在隐藏前的状态,这样在从竖屏切换到横屏显示时不会出现一些视觉上的bug
if (self.navigationController.navigationBar.hidden) {
CGRect rect = self.cfy_navBarBgView.
self.cfy_navBarBgView.frame = CGRectMake(0, rect.origin.y, CFYScreenWidth, rect.size.height);
// 获取navigationBar的backgroundView
UIView *backgroundView = [self.navigationController.navigationBar valueForKey:@"_backgroundView"];
// 如果没有则return
if (!backgroundView) {
// 获取navigationBar的backgroundView在self.view中的位置,这个位置也就是cfy_navBarBgView所在的位置。
CGRect rect = [backgroundView.superview convertRect:backgroundView.frame toView:self.view];
// 出现rect.origin.x & 0,情况只有在页面刚push出来并且navigationBar隐藏的时候。
// 这个时候讲rect.origin.y上移rect.size.height,使cfy_navBarBgView也隐藏
// 目的是防止在navigationBar.hidden=NO时出现动画显示错误
if (rect.origin.x & 0) {
rect.origin.y = 0 - rect.size.
// cfy_navBarBgView的x固定0
self.cfy_navBarBgView.frame = CGRectMake(0, rect.origin.y, rect.size.width, rect.size.height);
// 设置当前view的clipsToBounds = NO,原因是,self.view.top可能是从navigationBar.bottom开始,如果clipsToBounds = YES,则cfy_navBarBgView无法显示
self.view.clipsToBounds = NO;
// 将cfy_navBarBgView移到self.view最顶端,防止被其他view遮盖
[self.view bringSubviewToFront:self.cfy_navBarBgView];
#pragma mark - 公开方法 -
设置导航栏背景色
@param color 背景色
- (void)cfy_setNavigationBarBackgroundColor:(UIColor *)color {
self.cfy_navigationBarBackgroundColor =
if (self.navigationController) {
self.cfy_navBarBgView.backgroundColor =
设置背景图片
@param image 背景图
- (void)cfy_setNavigationBarBackgroundImage:(UIImage *)image {
// 后续版本加入
设置导航栏透明度
@param alpha 透明度
- (void)cfy_setNavigationBarAlpha:(CGFloat)alpha {
self.cfy_navigationBarAlpha =
if (self.navigationController) {
self.cfy_navBarBgView.alpha =
#pragma mark - 私有方法 -
设置导航栏是否隐藏
@param hidden 隐藏
@param animated 动画
- (void)cfy_setNavigationBarHidden:(BOOL)hidden animated:(BOOL)animated {
if (self.navigationController) {
// 这里只在cfy_navBarBgView隐藏时使用了动画,原因是cfy_navBarBgView显示时系统自动给加上了动画(这很神奇)
if (hidden && self.cfy_viewAppeared && animated && !self.navigationController.navigationBar.hidden) {
// 在cfy_navBarBgView隐藏,并且view已经Appeared,并且有动画,并且navigationBar不是已经隐藏了时就进行动画
CGRect rect = self.cfy_navBarBgView.
[UIView animateWithDuration:0.2 animations:^{
// 动画时向上运动
self.cfy_navBarBgView.frame = CGRectMake(0, rect.origin.y - rect.size.height, rect.size.width, rect.size.height);
} completion:^(BOOL finished) {
// 动画完成后cfy_navBarBgView隐藏
self.cfy_navBarBgView.hidden =
self.cfy_navBarBgView.hidden =
添加navigationBar背景view
- (void)cfy_addNavBarBgView {
if (!self.isViewLoaded) {
if (!self.navigationController) {
if (!self.navigationController.navigationBar) {
// 获取NavigationBar的BackgroundView在当前view中的位置
CGRect rect = [self cfy_getNavigationBarBackgroundViewRect];
UIView *navBarBgView = [[UIView alloc] initWithFrame:CGRectMake(0, rect.origin.y, rect.size.width, rect.size.height)];
[self.view addSubview:navBarBgView];
// 判断有没有设置颜色
if (self.cfy_navigationBarBackgroundColor) {
navBarBgView.backgroundColor = self.cfy_navigationBarBackgroundC
// 默认是白色
navBarBgView.backgroundColor = [UIColor whiteColor];
self.cfy_navigationBarBackgroundColor = [UIColor whiteColor];
// 设置透明度,默认为1
navBarBgView.alpha = self.cfy_navigationBarA
// 是否隐藏
navBarBgView.hidden = self.navigationController.navigationBar.isH
[self setCfy_navBarBgView:navBarBgView];
获取navigationBar._backgroundView在self.view中的frame
@return _backgroundView的frame
- (CGRect)cfy_getNavigationBarBackgroundViewRect {
UIView *backgroundView = [self.navigationController.navigationBar valueForKey:@"_backgroundView"];
if (!backgroundView) {
return CGRectZ
CGRect rect = [backgroundView.superview convertRect:backgroundView.frame toView:self.view];
#pragma mark - getter/setter -
-(UIView *)cfy_navBarBgView {
UIView *navBarBgView = objc_getAssociatedObject(self, _cmd);
if (nil == navBarBgView) {
[self cfy_addNavBarBgView];
return navBarBgV
- (void)setCfy_navBarBgView:(UIView *)navBarBgView {
objc_setAssociatedObject(self, @selector(cfy_navBarBgView), navBarBgView, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
- (BOOL)cfy_viewAppeared {
return [objc_getAssociatedObject(self, _cmd) boolValue];
- (void)setCfy_viewAppeared:(BOOL)viewAppeared {
objc_setAssociatedObject(self, @selector(cfy_viewAppeared), @(viewAppeared), OBJC_ASSOCIATION_ASSIGN);
- (UIColor *)cfy_navigationBarBackgroundColor {
return objc_getAssociatedObject(self, _cmd);
- (void)setCfy_navigationBarBackgroundColor:(UIColor *)navigationBarBackgroundColor {
objc_setAssociatedObject(self, @selector(cfy_navigationBarBackgroundColor), navigationBarBackgroundColor, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
-(CGFloat)cfy_navigationBarAlpha {
NSNumber *alpha = objc_getAssociatedObject(self, _cmd);
if (!alpha) {
[self setCfy_navigationBarAlpha:1.];
return 1.;
return [alpha floatValue];
- (void)setCfy_navigationBarAlpha:(CGFloat)navigationBarAlpha {
objc_setAssociatedObject(self, @selector(cfy_navigationBarAlpha), @(navigationBarAlpha), OBJC_ASSOCIATION_ASSIGN);
  OK,主要功能完成,上面代码中还有几个问题:
问题1:在设置[navigationController setNavigationBarHidden:animated:]和[navigationController setNavigationBarHidden:]方法是应该对cfy_navBarBgView进行操作;
问题2:没有设置NavigationBar为透明,不设置成透明,前面的工作都白做了。
  那么对navigationController也建一个Category,并swizzle需要的方法。代码如下:UIViewController+CFYNavigationBarTransition.m
@interface UIViewController ()
cfy_navBarBgView,这个view就是核心,改变navigationBar颜色其实是改变cfy_navBarBgView的背景色
@property (nonatomic, strong) UIView *cfy_navBarBgV
用来判断view是否加载
@property (nonatomic, assign) BOOL cfy_viewA
保存navigationBar颜色
@property (nonatomic, strong) UIColor *cfy_navigationBarBackgroundC
保存navigationBar颜色透明度
@property (nonatomic, assign) CGFloat cfy_navigationBarA
@implementation UIViewController (CFYNavigationBarTransition)
在load中,swizzle四个方法viewDidLoad、viewWillLayoutSubviews、viewDidAppear:、viewDidDisappear:。
+(void)load {
CFYSwizzleMethod(self, @selector(viewDidLoad), @selector(cfy_viewDidLoad));
CFYSwizzleMethod(self, @selector(viewWillLayoutSubviews), @selector(cfy_viewWillLayoutSubviews));
CFYSwizzleMethod(self, @selector(viewDidAppear:), @selector(cfy_viewDidAppear:));
CFYSwizzleMethod(self, @selector(viewDidDisappear:), @selector(cfy_viewDidDisappear:));
在viewDidLoad中添加cfy_navBarBgView
- (void)cfy_viewDidLoad {
[self cfy_viewDidLoad];
// 如果存在navigationController则添加cfy_navBarBgView
if (self.navigationController) {
[self cfy_addNavBarBgView];
- (void)cfy_viewDidAppear:(BOOL)animated {
[self cfy_viewDidAppear:animated];
self.cfy_viewAppeared = YES;
- (void)cfy_viewDidDisappear:(BOOL)animated {
[self cfy_viewDidDisappear:YES];
self.cfy_viewAppeared = NO;
在viewWillLayoutSubviews中对cfy_navBarBgView进行处理,使cfy_navBarBgView能在不同环境正确显示
- (void)cfy_viewWillLayoutSubviews {
[self cfy_viewWillLayoutSubviews];
// 当前viewController没navigationController,直接退出
if (!self.navigationController) {
self.navigationController.navigationBar隐藏了,做一些处理。
如果在navigationBar隐藏时,旋转屏幕,这时如果不处理后并return,而是走下面的代码,那么并不能正确的获取到cfy_navBarBgView的frame。
所以在这里直接将cfy_navBarBgView的宽度设置成屏幕看度,其他不变保持cfy_navBarBgView在隐藏前的状态,这样在从竖屏切换到横屏显示时不会出现一些视觉上的bug
if (self.navigationController.navigationBar.hidden) {
CGRect rect = self.cfy_navBarBgView.
self.cfy_navBarBgView.frame = CGRectMake(0, rect.origin.y, CFYScreenWidth, rect.size.height);
// 获取navigationBar的backgroundView
UIView *backgroundView = [self.navigationController.navigationBar valueForKey:@"_backgroundView"];
// 如果没有则return
if (!backgroundView) {
// 获取navigationBar的backgroundView在self.view中的位置,这个位置也就是cfy_navBarBgView所在的位置。
CGRect rect = [backgroundView.superview convertRect:backgroundView.frame toView:self.view];
// 出现rect.origin.x & 0,情况只有在页面刚push出来并且navigationBar隐藏的时候。
// 这个时候讲rect.origin.y上移rect.size.height,使cfy_navBarBgView也隐藏
// 目的是防止在navigationBar.hidden=NO时出现动画显示错误
if (rect.origin.x & 0) {
rect.origin.y = 0 - rect.size.
// cfy_navBarBgView的x固定0
self.cfy_navBarBgView.frame = CGRectMake(0, rect.origin.y, rect.size.width, rect.size.height);
// 设置当前view的clipsToBounds = NO,原因是,self.view.top可能是从navigationBar.bottom开始,如果clipsToBounds = YES,则cfy_navBarBgView无法显示
self.view.clipsToBounds = NO;
// 将cfy_navBarBgView移到self.view最顶端,防止被其他view遮盖
[self.view bringSubviewToFront:self.cfy_navBarBgView];
#pragma mark - 公开方法 -
设置导航栏背景色
@param color 背景色
- (void)cfy_setNavigationBarBackgroundColor:(UIColor *)color {
self.cfy_navigationBarBackgroundColor =
if (self.navigationController) {
self.cfy_navBarBgView.backgroundColor =
设置背景图片
@param image 背景图
- (void)cfy_setNavigationBarBackgroundImage:(UIImage *)image {
// 后续版本加入
设置导航栏透明度
@param alpha 透明度
- (void)cfy_setNavigationBarAlpha:(CGFloat)alpha {
self.cfy_navigationBarAlpha =
if (self.navigationController) {
self.cfy_navBarBgView.alpha =
#pragma mark - 私有方法 -
设置导航栏是否隐藏
@param hidden 隐藏
@param animated 动画
- (void)cfy_setNavigationBarHidden:(BOOL)hidden animated:(BOOL)animated {
if (self.navigationController) {
// 这里只在cfy_navBarBgView隐藏时使用了动画,原因是cfy_navBarBgView显示时系统自动给加上了动画(这很神奇)
if (hidden && self.cfy_viewAppeared && animated && !self.navigationController.navigationBar.hidden) {
// 在cfy_navBarBgView隐藏,并且view已经Appeared,并且有动画,并且navigationBar不是已经隐藏了时就进行动画
CGRect rect = self.cfy_navBarBgView.
[UIView animateWithDuration:0.2 animations:^{
// 动画时向上运动
self.cfy_navBarBgView.frame = CGRectMake(0, rect.origin.y - rect.size.height, rect.size.width, rect.size.height);
} completion:^(BOOL finished) {
// 动画完成后cfy_navBarBgView隐藏
self.cfy_navBarBgView.hidden =
self.cfy_navBarBgView.hidden =
添加navigationBar背景view
- (void)cfy_addNavBarBgView {
if (!self.isViewLoaded) {
if (!self.navigationController) {
if (!self.navigationController.navigationBar) {
// 获取NavigationBar的BackgroundView在当前view中的位置
CGRect rect = [self cfy_getNavigationBarBackgroundViewRect];
UIView *navBarBgView = [[UIView alloc] initWithFrame:CGRectMake(0, rect.origin.y, rect.size.width, rect.size.height)];
[self.view addSubview:navBarBgView];
// 判断有没有设置颜色
if (self.cfy_navigationBarBackgroundColor) {
navBarBgView.backgroundColor = self.cfy_navigationBarBackgroundC
// 默认是白色
navBarBgView.backgroundColor = [UIColor whiteColor];
self.cfy_navigationBarBackgroundColor = [UIColor whiteColor];
// 设置透明度,默认为1
navBarBgView.alpha = self.cfy_navigationBarA
// 是否隐藏
navBarBgView.hidden = self.navigationController.navigationBar.isH
[self setCfy_navBarBgView:navBarBgView];
获取navigationBar._backgroundView在self.view中的frame
@return _backgroundView的frame
- (CGRect)cfy_getNavigationBarBackgroundViewRect {
UIView *backgroundView = [self.navigationController.navigationBar valueForKey:@"_backgroundView"];
if (!backgroundView) {
return CGRectZ
CGRect rect = [backgroundView.superview convertRect:backgroundView.frame toView:self.view];
#pragma mark - getter/setter -
-(UIView *)cfy_navBarBgView {
UIView *navBarBgView = objc_getAssociatedObject(self, _cmd);
if (nil == navBarBgView) {
[self cfy_addNavBarBgView];
return navBarBgV
- (void)setCfy_navBarBgView:(UIView *)navBarBgView {
objc_setAssociatedObject(self, @selector(cfy_navBarBgView), navBarBgView, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
- (BOOL)cfy_viewAppeared {
return [objc_getAssociatedObject(self, _cmd) boolValue];
- (void)setCfy_viewAppeared:(BOOL)viewAppeared {
objc_setAssociatedObject(self, @selector(cfy_viewAppeared), @(viewAppeared), OBJC_ASSOCIATION_ASSIGN);
- (UIColor *)cfy_navigationBarBackgroundColor {
return objc_getAssociatedObject(self, _cmd);
- (void)setCfy_navigationBarBackgroundColor:(UIColor *)navigationBarBackgroundColor {
objc_setAssociatedObject(self, @selector(cfy_navigationBarBackgroundColor), navigationBarBackgroundColor, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
-(CGFloat)cfy_navigationBarAlpha {
NSNumber *alpha = objc_getAssociatedObject(self, _cmd);
if (!alpha) {
[self setCfy_navigationBarAlpha:1.];
return 1.;
return [alpha floatValue];
- (void)setCfy_navigationBarAlpha:(CGFloat)navigationBarAlpha {
objc_setAssociatedObject(self, @selector(cfy_navigationBarAlpha), @(navigationBarAlpha), OBJC_ASSOCIATION_ASSIGN);
至此所有的代码完工。
改变navigationBar的颜色,调用[viewController cfy_setNavigationBarBackgroundColor:bgColor]方法。
改变navigationBar的透明度,调用[viewController cfy_setNavigationBarAlpha:alpha]方法.
隐藏则直接调用UINavigationController中设置NavigationBar隐藏的方法注意事项:不要设置NavigationBar的translucent为NO,原因是设置了translucent=NO,NavigationBar就不能透明了。
四、成果展示
五、代码地址
GitHub:Cocoapods:pod 'CFYNavigationBarTransition'
红色咖啡杯、
银白的电脑、
小黄人抱枕

我要回帖

更多关于 王者荣耀国服第一杨戬 的文章

 

随机推荐