Ekulelu's Blog

iOS的UINavigationViewController 相关

人们都说转角遇到爱,天天和代码打交道的我是转角遇到坑。好,这个分类的名字就决定是你了。这个分类下面的文章主要是记录一些实际开发或学习的时候遇到的坑(说白了,就是个笔记本)。
希望这本坑能给大家一些帮助,少走一些弯路。

UINavigationViewController,这个是iOS开发中最最最常用的一个类。苹果的封装给我们省去了很多麻烦事,但是这种自动性也会带来一些坑。

导航栏相关

先说导航栏,UI首先就会和你说把导航栏的按钮给我改了。一般会以为很简单,换个图片就好了,但是坑很多。

修改返回键图标

第1种方法:
下面这段放在需要的那个类里面用。这个不会清除系统的滑动返回效果。

1
2
3
self.navigationController.navigationBar.backIndicatorImage = [UIImage imageNamed:@"back_normal_for_device_list"];
self.navigationController.navigationBar.backIndicatorTransitionMaskImage = [UIImage imageNamed:@"back_press_for_device_list"];

这里多说一句,系统的返回滑动由下面的属性控制

1
navigationController.interactivePopGestureRecognizer.enabled

如果需要关闭,用下面的代码

1
2
3
if ([self.navigationController respondsToSelector:@selector(interactivePopGestureRecognizer)]) {
self.navigationController.interactivePopGestureRecognizer.enabled = NO;
}//关闭滑动返回

系统的东西好好的为什么要关闭呢?因为这里面有坑!
如果你的返回出来的VC(viewController)里面,在viewWillAppear里面写了动画。那么你会发现滑动出来的时候,没有动画效果。明明代码执行了,但是没有动画!

下面解释原因:
滑动的时候,其实是有动画效果的,这个时候已经触发了viewWillAppear方法,代码已经执行了,动画已经开始了。但是,当你滑动结束之后,系统会调用方法把动画都停掉!
如果需要滑动效果又要动画,我的解决方案是使用一个UIScreenEdgePanGestureRecognizer代替了navigationVC的右划手势,然后在自定义手势完成的时候,发送通知开启动画,这样有动画了。如果有更好的方法,请告知我。
此问题感谢Ambition告知我原因。

第2种方法:
下面这个会清除滑动返回效果

1
self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithImage:[UIImage imageNamed:@"back_normal"] style:UIBarButtonItemStylePlain target:self action:@selector(goToPrevious)]; //修改左边返回按钮图标

但是上面的代码位置是固定的,如果UI有对按钮位置有需求还得改,而且你回发现上面的代码没法设置位置。所以只能使用另外的方法

第3种方法:

1
2
3
4
5
6
7
8
UIButton *backBtn = [[UIButton alloc] initWithFrame:CGRectMake(-5, 0, 44, 44)];
[backBtn setNormalImage:@"back_normal" highlightedImage:@"back_press"];
backBtn.contentEdgeInsets = UIEdgeInsetsMake(0, -10, 0, 0);
[backBtn addTarget:self action:@selector(goToPrevious) forControlEvents:UIControlEventTouchUpInside];
UIView *leftView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 44, 44)];
[leftView addSubview:backBtn];
self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithCustomView:leftView];

效果图片如下,这里使用了UIBarButtonItem 的initWithCustomView方法,但如果你直接把按钮传入,位置也是固定的,而且比上面的方法更靠近中间,所以这里再使用了一个view来包装按钮:按钮在view里面有偏移,按钮的图片在按钮里面也可以再偏移。只要按钮图片位置不超出view的位置,那么就是可以点击的。

第二种方法和第三种方法都可以用于修改右边的图标

删除下一级push出来的VC返回键的文字

这段代码放在使用push方法的那个类用,只对下一级的vc有效果。且如果下一级的VC自定义了左边的按钮,那么这个方法没有效果。

1
self.navigationItem.backBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"" style:UIBarButtonItemStylePlain target:nil action:nil];

设置中间文字颜色大小等

1
2
3
NSDictionary * dict=[NSDictionary dictionaryWithObject:[UIColor redColor] forKey:NSForegroundColorAttributeName]; //这里可以换成其他的NSFontAttributeName,从而进行字体大小设定
self.navigationController.navigationBar.titleTextAttributes = dict;
self.navigationItem.title = @"fdfdf";

这里有个坑,请问下面代码有什么区别?

1
2
self.navigationController.navigationItem
self.navigationItem

区别在于self.navigationController可能为空。

这里又牵扯出一个对子view初始化的问题,另外的文章讨论。(这是给自己挖坑)

导航栏是否透明

属性如下。这个属性API里面有详细的说明,建议看看。

1
navigationVC.navigationBar.translucent = NO;

这个属性决定导航栏是否透明,而且会影响布局,如果这个属性为NO,那么navigationVC的子view的y=0的位置是紧贴着导航栏底部的。如果为YES,那么y=0位置从屏幕y=0位置算起。
这个属性会对setBackgroundImage有反应,如果image没有任何的透明像素,那么这个属性会自动变为NO;

注:导航栏是否hidden对布局没影响

导航栏背景色

代码如下,颜色淡才看得出效果。这个会保留毛玻璃效果

1
self.navigationController.navigationBar.barTintColor = [UIColor colorWithRed:0.02 green:0 blue:0 alpha:0.051];

如果不想要毛玻璃效果,可以用下面的代码修改导航栏背景图片,传入一张纯色的图片就行。
这个改背景图会覆盖上面的barTintColor,且图片是不透明的话,translucent会自动变NO,如果图片是透明的,请确保translucent为YES才能开启透明效果。

1
[self.navigationController.navigationBar setBackgroundImage:[UIImage imageNamed:@"100"] forBarMetrics:UIBarMetricsDefault];

然后这里有个巨坑
translucent如果是NO,那么y=0的位置是从屏幕的最上边开始算起的。但是,如果translucent是YES的话,那么y=0的位置是导航栏的底部开始算起的!

当NavigationVC里面有scrollview或它的子类的时候,会将scrollview里面的子view自动往下移动64,如果你的scrollview是从y=0开始的话,这里导致一个问题,子view(scrollview.contentSize高度小于scrollview.frame的高度时候)开始的时候不居中显示,layout加上下面代码,留意里面高度有个减了64。

1
2
3
4
5
6
CGFloat offsetX = (self.scrollView.bounds.size.width > self.scrollView.contentSize.width)?
(self.scrollView.bounds.size.width - self.scrollView.contentSize.width) * 0.5 : 0.0;
CGFloat offsetY = (self.scrollView.bounds.size.height > self.scrollView.contentSize.height)?
(self.scrollView.bounds.size.height - self.scrollView.contentSize.height - 64) * 0.5 : 0.0;
self.imgView.center = CGPointMake(self.scrollView.contentSize.width * 0.5 + offsetX, self.scrollView.contentSize.height * 0.5 + offsetY);

但是如果你的scrollview不是从y=0开始的,那么scrollview上面就会有一条64的空白区域。
如果确实是要scrollview从导航栏底部开始(注意高度要减了64),设置下面的属性

1
self.automaticallyAdjustsScrollViewInsets = NO;

同时在layout和scrollViewDidZoom加上下面代码,确保滑动或缩放的时候居中,注意这里没有减64了

1
2
3
4
5
6
7
//防止放大的时候,子view位置变了,导致滑动不到边界
CGFloat offsetX = (self.scrollView.bounds.size.width > self.scrollView.contentSize.width)?
(self.scrollView.bounds.size.width - self.scrollView.contentSize.width) * 0.5 : 0.0;
CGFloat offsetY = (self.scrollView.bounds.size.height > self.scrollView.contentSize.height)?
(self.scrollView.bounds.size.height - self.scrollView.contentSize.height) * 0.5 : 0.0;
self.imgView.center = CGPointMake(self.scrollView.contentSize.width * 0.5 + offsetX, self.scrollView.contentSize.height * 0.5 + offsetY);

这个问题可以参考这篇文章:
http://www.jianshu.com/p/b2585c37e14b