在iOS编程路上走过的坑 - Tips
- 1. 文档更新说明
- 2. 背景
- 2.1. UI相关
- 2.1.1. scrollView.contentOffect莫名等于-64
- 2.1.2. 设置控制器backBarButtonItem或者leftBarButtonItem无效
- 2.1.3. 使用addObserver监听视图的frame无效果
- 2.1.4. TabBarController与NavigationController谁嵌套谁?
- 2.1.5. iOS8中CollectionView加载速度异常慢
- 2.1.6. 多个CollectionView出现EXC_BAD_ACCESS
- 2.1.7. CollectionView的Cell中添加按钮无法响应事件
- 2.1.8. 代码设置UIButton.titleLaebl.title无效果
- 2.1.9. 为Scrollview中设置上下左右约束后提示ambiguous
- 2.1.10. 如何让设置约束的视图们执行动画?
- 2.1.11. 在interface界面里找不到Cell视图的contentView属性
- 2.1.12. 有时候无法在导航栏上摆放BarButtonItem
- 2.1.13. 如何在interface界面里和代码里设置导航栏的样式
- 2.1.14. 如何用代码写视图的宽高约束
- 2.1.15. 继承UIImageView的子类,drawRect:方法不被调用
- 2.1.16. 自定义toolBar上的UIBarButtonItem
- 2.1.17. 如何让重写的视图子类也支持直接在Xib,Storyboard中填写参数预览显示效果
- 2.1.18. 如何优雅地往TableView上添加无网络图片标记或者加载指示器
- 2.1.19. 如何去除TableView中多余的分割线
- 2.1.20. 让一个模态视图透明部分显示成上一个界面
- 2.1.21. 多用百分比实现自动布局
- 2.1.22. SearchBar底部有一条黑边
- 2.1.23. 定制SearchBar取消按钮颜色,输入框光标颜色
- 2.2. 数据相关
- 2.1. UI相关
文档更新说明
- 第10次更新 2016年08月19日
- 第9次更新 2016年08月04日
- 第8次更新 2016年05月23日
- 第7次更新 2016年05月10日
- 第6次更新 2016年05月04日
- 第5次更新 2016年04月25日
- 第4次更新 2016年04月18日
- 第3次更新 2016年04月13日
- 第2次更新 2016年03月31日
- 首次更新 2016年03月30日
背景
记录我在iOS开发路上遇到的坑.
UI相关
scrollView.contentOffect莫名等于-64
当控制器的view属性是scrollView类型,或者控制器view属性的第一个子视图是scrollView类型,或者控制器view属性的第一个子视图下面的首个子视图是scrollView属性的,控制器会自动给scrollView插入一个conentOffect = -64.(其他没列举出来的情况也可能出现,具体情况具体分析.-64其实就是状态栏和导航栏的高度,没导航栏的话就只有状态栏的偏移了).
解决方案: 设置控制器automaticallyAdjustsScrollViewInsets = NO,另外如果导航栏不透明,还需要再设置extendedLayoutIncludesOpaqueBars = YES
1 | //控制器view属性第一个子视图是wkWebView时 |
1 | //控制器view属性第一个子视图非scrollView类型时,控制器不会自动插入-64 |
设置控制器backBarButtonItem或者leftBarButtonItem无效
被NavigationController使用push显示的控制器,在控制器里面使用self. navigationController. navigationItem. leftBarButtonItem 或者使用self. navigationController. navigationItem. backBarButtonItem 去设置顶部返回按钮的内容,结果总是不起作用,连按钮的标题的改不了.
解决方案:要在控制器内使用self.navigationItem.leftBarButtonItem才是正确的.参考下图
使用addObserver监听视图的frame无效果
A视图拥有子视图B,视图B使用约束让其与A的frame一样.使用- (void)addObserver:(NSObject )anObserver forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context 方法监听B的frame,然后改变A的frame,这个时候B视图虽然会随着A视图frame变化而变化,但是并不会触发监听事件- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context
*解决方案:由于没有直接设置B的frame所以导致无法触发监听,可以直接监听A的frame就能触发监听事件了,另外这个坑我在实际开发中遇到,不知是不是规则如此还是另有原因**
TabBarController与NavigationController谁嵌套谁?
到底是TabBarController里面的每一个tiem页面都嵌套一个NavigationController,还是NavigationController嵌套TabBarController?这个问题对于确实值得讨论,需要结合实际需求选择.选择不妥当会虽然能实现同样的功能,但是不方便代码的设计.(下面简称tab,nav)
解决方案:一个tab嵌套一个nav,nav push一个视图的时候如果将被push的视图设置为hideBottomBarOnPush的话,效果就是微信那样,圧入的视图会覆盖底部的tab按钮.而nav嵌套tab,然后push一个视图的话,不需要设置hideBottomBarOnPush效果也是微信那样.不过后者的话一会所有页面的navigationBar都是同一个.这里需要根据实际需求选择嵌套的方式.如果,不同页面有着不同的navigationBar,那就需要tab嵌套nav,相反则是nav嵌套tab.
iOS8中CollectionView加载速度异常慢
如果在iOS8中,CollectionView 的Cell使用特殊字体就会导致这个问题,iOS9则无异常.
解决方案:不使用特殊字体或者对字体做其他处理(暂时未使用到)
多个CollectionView出现EXC_BAD_ACCESS
如果界面上使用多个CollectionView,同时引用同一个CollectionView的UICollectionViewLayout,则会出现这个错误.
解决方案:为每一个CollectionView对象分配一个独立的Layout对象
CollectionView的Cell中添加按钮无法响应事件
在xib文件中直接对CollectionViewCell添加一个按钮等界面元素,可能会发现,按钮无法点击,看上去Cell上的元素就像是一张图片一样,无法产生任何事件响应.
解决方案:这是因为直接添加到cell上的元素,将会被cell的contentView视图覆盖住,所以无法做任何响应.正确的做法是,任何元素都不要直接添加到cell上,而是添加到cell.contentView上
代码设置UIButton.titleLaebl.title无效果
直接在代码中设置UIButton.titleLabel.text,但是并没有任何效果.
解决方案:设置按钮上的问题,需要结合官方文档里说的.因为按钮有多种状态,比如未选中,高亮,选中等,所以需要用setTitle:forState:设置文字.另外按钮的其他属性可能也需要用类似方法设置,具体参考官方文档
为Scrollview中设置上下左右约束后提示ambiguous
有一个View,里面有一个scrollView,scrollView里面有一个按钮,为按钮设置top,宽高,leading约束.为scrollView设置了上下左右的约束后,仍然Xcode提示ScrollView has ambiguous scrollable content height.原因就是UIScrollView有一个contentSize属性.虽然按钮设置了宽,高,top,leading约束,但是仍然无法让系统确定contentSize的尺寸.
解决方案:scrollView里的元素的约束必须全面,这样scrollView才能确定contentSize的尺寸.例如scrollView里面的元素必须设置上下左右宽高,这样才能让scrollView确定好内部的contentSize
如何让设置约束的视图们执行动画?
举一个简单的例子如图,View视图有红,蓝两个子视图.蓝色视图约束为top,leading,hight,width.红色视图约束也和蓝色类型一样.现在想要一个这样的动画效果:当蓝色视图的高度变小时,红色视图在蓝色视图缩小过程向上移动.我尝试了直接给蓝色视图设置一个frame.size.height = 0的动画效果,结果是蓝色视图随便执行了动画,但是红色视图无任何变化.这里对于设置过约束的视图要产生动画效果需要用到另一种方法.
解决方案:想要让约束在动画过程中一直生效,需要使用layoutIfNeeded这个方法,具体用法看最后代码.需要注意的是,如果没有达到预想的效果,肯定是约束没设置好,例如同时设置了视图的上下然后再变化高度,这样就无效果了
1 | - (IBAction)move:(id)sender { |
在interface界面里找不到Cell视图的contentView属性
有时候在xib或者storyboard文件中设置Cell视图的时候,却发现找不到contentView了,原因就是视图对象本身不是一个Cell视图,而是一个UIView视图.
解决方案:从对象库面板里面拉取一个Cell视图替换掉UIView视图即可
有时候无法在导航栏上摆放BarButtonItem
导航栏比较特殊,例如直接从对象库面板拉取一个NavigatinController,rootViewController默认是一个tableViewController,这时候是无法直接在导航栏添加BarButtonItem的.
解决方案:需要将BarButtonItem摆放在Navigation Item上,而Navigation Item则需要放在navigation bar上
如何在interface界面里和代码里设置导航栏的样式
在不熟悉的情况下,设置了效果往往无效,比如颜色等.
下面总结一下导航栏的设置方法(注意,navigationController.navigationBar在viewDidLoad是nil,只有在控制器触发viewWillAppear方法后才有值)
1 | 设置标题颜色,需要设置Title Color. |
如何用代码写视图的宽高约束
代码写约束一般用如下方法
1 | + (instancetype)constraintWithItem:(id)view1 |
其中有一个问题,就是宽高约束这种参考对象就只有自己了,那么如何写呢?
解决方案:
1 | [self.userContactInfoView addConstraint:[NSLayoutConstraint constraintWithItem:self.userContactInfoView attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1 constant:100]]; |
继承UIImageView的子类,drawRect:方法不被调用
开发文档对这个问题进行了详细说明,为了提高ImageView的渲染效率,系统用的是另外的方式渲染视图,不是通过drawRect:方法来进行的.
解决方案:根据文档的提示,不要继承UIImageView去实现展示图片的功能,如果有必要自定义展示图片功能的,那么实现UIView子类吧
自定义toolBar上的UIBarButtonItem
UIBarButtonItem提供了创建UIBarButtonSystemItem的方法,也提供创建自定义视图的方法 - initWithCustomView:,所以想要完全自定义UIBarButtonItem,则可以使用创建自定义视图的方法,先生成一个按钮,自定义按钮,最后在通过initWithCustomView将按钮转化成UIBarButtonItem.
实现方案:先做一个UIButton,可以添加图片文字,可以用EdgeInsets属性设置图片文字的偏移,然后使用- (instancetype)initWithCustomView:(UIView *)customView方法把按钮转成UIBarButtonItem,再把item以数组的形式加到 toolbarItems 就可以了.加到toolbar的item会自动有左右空隙,系统会自动摆放item到toolbar的y轴居中位置,只需要设置UIButton的width和height就可以了,点击事件也需要在UIButton上面加.
如何让重写的视图子类也支持直接在Xib,Storyboard中填写参数预览显示效果
这个问题,应该是需要Xcode的支持,原理就是在类中加上一些能让xcode识别到的特殊标记.
实现方案:具体的标记方法参考这个链接预览自定义视图子类
如何优雅地往TableView上添加无网络图片标记或者加载指示器
这个问题还是比较有意思的,因为tableView上面会有很多cell,如果直接往tableView上添加的话,添加的视图会被cell覆盖,如果没有cell但是有分割线的时候,一样会被分割线覆盖.
解决方案:1.等到cell都加载完了你再圧入自定义视图(比如在viewDidAppear:(BOOL)animated方法里面添加hud.或者在didload方法中延迟0.5s加载);2.自定义视图加入到tableView的父视图中,例如如果是tableView控制器,可以把自定义视图加入到控制器的view
如何去除TableView中多余的分割线
因为tableView中的Cell只能设置显示或者不显示,显示的时候则满屏都显示,尽管Cell的数量还不够一瓶.
解决方案:去除多余分割线主要有几种思路,一种是设置tableView不显示分割线,然后在Cell中用一个高度为1像素的UIView,放在Cell的底部作为分割线,这样可以随心所欲地修改分割线的模样.另一种则是实现- (UIView *)tableView:(UITableView *)tableView viewForFooterInSection:(NSInteger)section这个方法,然后在最后一个section里面返回一个[[UIView alloc] init],接着在- (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section里面,返回高度0.5f,这样多余的分割线就不会显示出来了.(这种方法,最后一个Cell下面是没有分割线的).
让一个模态视图透明部分显示成上一个界面
简单地将模态视图的背景颜色设置成透明,会直接看到黑色背景.
解决方案:除了将视图设置成透明背景颜色之外,还需要设置另一个属性vc.modalPresentationStyle = UIModalPresentationOverCurrentContext,告诉系统要呈现视图栈中下一个视图.
多用百分比实现自动布局
做第一个APP的时候,用自动布局时,大部分是使用相对某一个视图的top,bottom,trailing,leading有多少个点这种方式来实现,后来发现在6p上效果跟5s实在差太多了,导致每次遇到6p就要动态调节一下这个具体值,再后来我就想到了肯定有百分比布局的方法,所以现在会结合百分比布局和具体值布局两种方式来实现了.
解决方案:这里我就不重复描述解决方案了.推荐一篇我参考过的文章AutoLayout 百分比布局
SearchBar底部有一条黑边
为SearchBar设置了tintColor后,底部可能有一条黑线,这个具体原因也不清楚了,解决方案也比较取巧.
解决方案:设置searchController.searchBar.layer.borderWidth = 1;searchController.searchBar.layer.borderColor为想要的颜色,从而替换掉黑边.
定制SearchBar取消按钮颜色,输入框光标颜色
SearchBar比较特殊,系统并没有提供直接的设置接口.这里可以使用一种全局设置的方式重新定制它们的颜色.
解决方案:参考下方代码
1 | searchController.searchBar.tintColor = [UIColor lightGrayColor]; |
数据相关
系统自带的字典与JSON互转
系统本身就系统了两者互转的API,可能不是很明显所以很多人都用了第三方库了.当然第三方库用起来比较方便.
解决方案:字典JSON互转,比较麻烦.
1 | //字典转JSON字符串 |
对象属性中有NSTimer对象,导致对象无法被释放的可能原因
初始化NSTimer对象时一般需要设置一个target,如果这个target是A对象,然后这个定时器又被A对象的属性强引用,就会出现引用循环问题了.一定要等到调用timer的invalidate方法后才能解除循环引用.参考下面代码应该更加清晰.
1 | @interface A : NSObject |
解决方案:在不需要A对象了或者不需要timer的地方,调用timer的invalidate,例如A是一个ViewController,那么就在ViewController将要disappear的时候调用invalidate
为什么NSDate显示的日期和设置的日期不同?
给NSDate初始化一个指定时间,或者用[NSdate date]获取当前时间,不管你的设备是什么时区,使用NSLog打印出来的都是格林标准时间.那么何如打印出本地时间?如何获取当前时区的年月日等参数呢?
解决方案:iOS中的NSDate时区问题.如果是通过字符串生成NSDate的,默认字符串就是参照设备时区再转换的.得到的NSdate可以看成是一个记录了时间戳的对象,如果要从NSDate里面获取到实际的年月日,一种是打印成字符串的,则需要使用descriptionWithLocale:,明确选择一个时区(例如[NSLocale currentLocale])再打印,否则就是显示默认的Greenwich Mean Time (GMT)了.另一种是要获取到年月日的,可以使用日历对象.[NSCalendar currentCalendar],这个实例化出来的就是一个本地日历,不需要再设置成本地时区.然后结合- (NSDateComponents *)components:(NSCalendarUnit)unitFlags fromDate:(NSDate *)date方法,就可以获取到一个完整的日期组件对象了.
使用信号量进行异步操作同步等待
有时候某些操作是异步进行的,但是我们需要获取到数据才能进行下一步操作,这个时候可以使用信号量进行同步等待.
解决方案:先创建一个信号量,在调用异步代码后进行等待信号量,在异步方法中最末尾部分发送信号量,详细代码如下:
1 | //创建一个semaphore |
注意:在AFNetworking中无法使用信号量实现同步操作.使用信号量到了wait部分,主线程进入等待状态,所以网络数据接受到后也无法再通过主线程进入回调.所以无法触发回调中的释放信号量方法,导致程序永久卡死.
使用MJextension将字典转成json字符串需要注意的地方
如果post给服务的数据是经过参数签名,那么就需要特别留意了.因为使用MJextension将模型(模型中有属性是数组类型)转换成字典,在将字典post给服务器的过程中字典会被AFNetWorking(假设是使用这个第三方库)转换成json字符串,同时去除多余的空格.而我们在做参数签名的时候如果将字典转成json字符串的规则跟AFNetWorking不同,则很可能转换出来的json字符串中数组部分会有多余的空格,从而导致参数签名失败.
解决方案:使用MJExtension,遇到模型转字典时,如果字典有数组的,建议把数组先转成json字符串,然后再存回字典,这样在对字典里的内容做参数签名时才不容易出错;否则可能会出现参数签名跟实际上post的内容不一致从而导致签名错误.
CoreData报错:The model used to open the store is incompatible with the one used to create the store
出现这个错误很可能是因为新增了xcdatamodeld文件,但是新增的文件没有被打包进APP里.
解决方案:把APP删了,Product -> Clean一下项目,重新安装即可
读取用户通讯录与审核问题
Apple提供了读取操作用户通讯录的接口,但是审核的时候非常严格,一不小心就因为这问题被拒绝了.
解决方案:读取用户通讯录的时候,需要弹窗写明读取通讯录之后要干嘛用,而且在提交APP审核的时候需要提供一份隐私政策(也就是在APP资料哪里的隐私URL写上地址,这个会显示在商店APP界面的下面.如下图)
如何读取本地json文件
json在OC里面对应的数据结构就是NSDictionary,那本地的json文件是用NSDictionary的相关类方法读取到的?其实不是,如果直接用NSDictionary的类方法读取得的一定是nil了.NSDictionary 有两个类方法用户初始化对象,分别是+ dictionaryWithContentsOfURL:和 + dictionaryWithContentsOfFile:,但是这两个方法使用来读取plist文件的,如果读取本地json文件,应该先把json转成字符串,再将字符串转成NSData,最后将NSData转成SDictionary.
解决方案:具体代码如下:
1 | NSString *jsonString = [NSString stringWithContentsOfURL:[[NSBundle mainBundle] URLForResource:@"region" withExtension:@"json"] encoding:NSUTF8StringEncoding error:nil]; |
注意:URL实质上就是file:///开头的路径(file:///private/var/mobile/Containers/Bundle/Application/F09363E1-7F9F-4315-A3F2-4F08E6092E0A/LiveStone.app/region.json),而path则是从磁盘根目录开始的目录路径(/private/var/mobile/Containers/Bundle/Application/F09363E1-7F9F-4315-A3F2-4F08E6092E0A/LiveStone.app/region.json)