在iOS编程路上走过的坑 - Tips2
- 1. 文档更新说明
- 2. 背景
- 2.1. UI相关
- 2.1.1. CAGradientLayer如何实现径向渐变?
- 2.1.2. 用代码创建UIButton对象需要注意的问题
- 2.1.3. UIImageView不接受点击事件
- 2.1.4. 如何加载其他Boundle中的图片资源
- 2.1.5. 设置按钮不同状态的颜色
- 2.1.6. TableView的didSelectRowAtIndexPath方法不被触发的可能原因
- 2.1.7. 移动TableViewCell的分割线至屏幕外
- 2.1.8. 判断WKWebView是否加载完成
- 2.1.9. Ipad运行Iphone模式的APP,导航栏过厚问题
- 2.1.10. shouldChangeTextInRange方法不被调用的情况
- 2.1.11. UITextField输入数字字符闪退
- 2.1.12. 控制TableView的HeaderView悬浮情况
- 2.1.13. 升级Xcode8之后可视化设置的颜色与实际运行效果不符合.
- 2.1.14. 获取StoryBoard中的控制器
- 2.1.15. 让TableView从屏幕的顶部开始显示(在状态栏下面开始显示)
- 2.1.16. 让一个小图片填充整个按钮的方法
- 2.1.17. 获取带有约束的视图真是的Frame
- 2.1.18. UITextField文字修改了但是不触发监听事件.
- 2.1.19. UITableView的reloadData操作会引起键盘消失.
- 2.1.20. becomeFirstResponder失效
- 2.1.21. 设置UITableViewHeaderFooterView背景颜色无效
- 2.1.22. UIPickerView无法显示选中行指示视图
- 2.2. 数据相关
- 2.1. UI相关
文档更新说明
- 第五次更新 2017年4月11日
- 第四次更新 2016年12月22日
- 第三次更新 2016年10月23日
- 第二次更新 2016年09月05日
- 首次更新 2016年08月19日
背景
本文为”记录我在iOS开发路上遇到的坑”系列第二篇文章,主要用来记录我在iOS编程的第二个阶段所遇到的问题.
UI相关
CAGradientLayer如何实现径向渐变?
这个标题纯属吸引搜索的.因为CAGradientLayer这个类并不支持径向径向渐变.CAGradientLayer虽然提供一个属性type,只是他就提供一种类型kCAGradientLayerAxial(轴向渐变).所以想要在iOS中实现径向渐变,只能使用更加底层的类CGGradientRef了.
解决方案:具体使用代码参考后面给的文章,有详细介绍如何使用CGGradientRef进行径向渐变处理,这里详细介绍一下绘制渐变的方法CGContextDrawRadialGradient的相关参数含义
1 | CGContextDrawRadialGradient |
用代码创建UIButton对象需要注意的问题
创建UIButton对象时,Apple推荐使用实例方法代替init方法.
解决方案:使用实例方法创建按钮.UIButton *button = [UIButton buttonWithType:UIButtonTypeSystem]; 其中buttonWithType:System类型的按钮自带了一些系统效果,如果不需要,可以使用UIButtonTypeCustom,创建自定义按钮.
UIImageView不接受点击事件
这个问题应该比较场景,UIImageView默认是不响应用户触摸事件的.
解决方案:设置它的userInteractionEnabled为YES即可
如何加载其他Boundle中的图片资源
如果是当前APP的Boundle,加载图片非常简单,直接[UIImage imageNamed:@”HomeSelc”]就可以加载到当前Boundle中的对应图片了.加载其他Boundle则不同.
解决方案:详细代码如下
1 | NSBundle *bundle = [NSBundle bundleForClass:[SVProgressHUD class]]; |
设置按钮不同状态的颜色
按钮颜色如果要针对不同状态(比如高亮,不可用)等去设置颜色,只能通过设置按钮的背景图去实现.
解决方案:下面提供颜色转UIImage的方案,方便设置按钮不同状态的颜色
1 | //- setBackgroundImage:forState: 配合下面代码实现 |
TableView的didSelectRowAtIndexPath方法不被触发的可能原因
TableView的点击事件不会被响应的可能原因非常多,常见的也是比较低级的错误比如没有代理,frame超出的父视图的frame等.
这里主要记录一种比较隐蔽的情况.想一下这样一种情况,有一个TableView以一个小于父视图的尺寸被添加到View上,比如这个图片
图片中的邮箱列表就是一个TableView,加到了控制器的View上.因为这个是登录界面,点击输入框会弹出键盘,所以我在View上加了一个点击手势,点击到View就收起键盘.就是这样一个场景,导致TableView中的didSelectRowAtIndexPath不会被触发.
先来分析一下原因,点击事件是从控制器View向子视图传递的,由于View上的手势识别到了事件,所以就拦截不向上传递,所以TableView没法接收到事件.
解决方案1:第一种解决方案是实现UIGestureRecognizerDelegate的代理方法- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch,自己判断是不是要让手势接收事件,代码如下:
1 | - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch{ |
解决方案2:第二种解决方案比较直接简单,设置一下UITapGestureRecognizer的cancelsTouchesInView = NO即可.意思是如果这个点击事件被手势识别了,那还要不要取消传递给View(A Boolean value affecting whether touches are delivered to a view when a gesture is recognized.),所以我们成NO,默认是YES也就是取消传递给View.
移动TableViewCell的分割线至屏幕外
TabelView可以给Cell加上分割线,默认的分割线距离Cell左边有一定距离,有时候我们希望分割线从Cell的最左边开始,有时候希望某些Cell的分割线不要出现(不出现还可以参考上一篇文章Tips).系统提供一个接口separatorInset用来设置分割线的位置,不过直接使用会发现达不到预期效果.
解决方案:正确的做法是使用下的代码进行设置
1 | cell.layoutMargins = UIEdgeInsetsZero; |
判断WKWebView是否加载完成
WKWebView里面有一个delegate是WKNavigationDelegate,实现了这个代理可以判断链接的加载情况,但是这个代理非常不准确,某些情况可能加载完成了却没有调用Finish方法.(具体情况忘记了,但一定有.)
解决方案:使用KVO方法,监听webView的loading属性可以做到准确识别加载情况.
1 | [self.webView addObserver:self forKeyPath:@"loading" options:NSKeyValueObservingOptionNew context:NULL]; |
Ipad运行Iphone模式的APP,导航栏过厚问题
这个问题先看下图
图中可以很明显看到由于ipad把状态栏放到顶部了,所以导航栏看起来非常厚.通过下面的解决方案可以让系统将导航栏顶部原本状态栏位置变成黑色.(效果如下图)
解决方案:先在plist中设置状态栏样式由控制器控制(加入并设置View controller-based status bar appearance设置为YES),接下来比较复杂,需要重写所有用到的控制器,再将原来的控制器继承自重写过的控制器,在重写的控制器中加入如下代码:
1 | - (void)viewDidLoad { |
注意,如果UIViewController控制器被设置为UINavigationController的根控制器,则需要重写UINavigationController,而不是重写UIViewController
shouldChangeTextInRange方法不被调用的情况
目前发现一种情况导致UITextViewDelegate代理方法不被调用:如果用户使用输入法的推荐输入时,则该方法不被调用.根据业务需求,例如限制字符等,该方法不被调用则会影响实际功能.
解决方案:使用输入法推荐输入时,textViewDidChange:会被调用.所以可以在这个方法中对超出长度限制部分的字符对一个截取操作.比如,[textView.text substringToIndex:300],限制300个字符
UITextField输入数字字符闪退
这个BUG比较奇怪,我也不知道是怎么出现了.一开始输入没问题,但是后面就报错了,提示-[NSNull length]: unrecognized selector sent to instance云云.翻阅了Google得到如下解决方案,在这里记录一下,因为这个问题确实值得留意.出现的原因就是我不小心点击了下图这个地方,然后没有填写数据就取消编辑导致的.
解决方案:解决方案参考这里What is causing NSNull length unrecognized selector keyCommand error 删除Storyboard源码里面的下面代码,重新运行,输入正常了~😆
1 | <keyCommands> |
控制TableView的HeaderView悬浮情况
headerView有一个特性,随着tableView的滚动,在tableView最上方默认总会有一个headerView(当前tableView最顶部显示的Cell所属的Section的headerView)悬浮并且固定在最顶部,这个特性很好,但是有时候需要关闭.
解决方案:设置tableView的Style即可控制悬浮情况.Style分别可设置为Plain,与Grouped.Plain也是默认的类型,支持悬浮.Grouped不支持悬浮
升级Xcode8之后可视化设置的颜色与实际运行效果不符合.
之前在Xcode7保存的颜色,在Xcode8上使用时,会发现颜色有一些偏差,这可能是因为Xcode采用新的颜色方案所致.
解决方案:在Xcode中使用sRGB IEC61966-2.1配置为标准导致的,所以需要对颜色配置进行修改,如下图:
获取StoryBoard中的控制器
解决方案:使用下方代码即可
1 | + (id)loadViewController:(NSString *)identifier from:(NSString *)storyboardName { |
让TableView从屏幕的顶部开始显示(在状态栏下面开始显示)
解决方案:设置tableview的automaticallyAdjustsScrollViewInsets = NO,然后约束到顶部就可以了.
让一个小图片填充整个按钮的方法
解决方案:可以使用如下代码实现
1 | weixinBtn.layer.contents = (__bridge id)[UIImage imageNamed:@"WeiXinIcon"].CGImage; |
获取带有约束的视图真是的Frame
对于有约束的视图,如果直接在控制器加载完成后获取其Frame,得到的值可能是约束还未生效时值,所以获取到的值很可能是不准确的.
解决方案:要准确获取约束视图的Frame,可以先调用view的layoutifneeded,然后就能准确拿到view的Frame了
UITextField文字修改了但是不触发监听事件.
对UITextField.text的修改主要有两种方法,通过代码修改和键盘输入修改.使用KVO只能监听到代码修改引起的变化,而使用NSNotificationCenter监听UITextFieldTextDidChangeNotification事件,只只能监听到由键盘引起的输入.
解决方案:可以对UITextField同时执行两种监听,才能同时得到满足代码和键盘修改引起的变化通知.
UITableView的reloadData操作会引起键盘消失.
解决方案:某些情况下,不想让键盘消失,可以使用 - reloadRowsAtIndexPaths:withRowAnimation:方法代替,避免重载到正在编辑的视图.
becomeFirstResponder失效
becomeFirstResponder失效的可能原因是当前的textview等视图还没加载好导致的.
解决方案:可以考虑使用延时代码,延迟执行becomeFirstResponder方法
设置UITableViewHeaderFooterView背景颜色无效
自从iOS9之后,设置UITableViewHeaderFooterView的背景只能设置在contentView属性中,一般出现设置无效的可能原因是使用了xib布局视图,然后在视图中设置背景导致的.iOS9中哪怕在UserDefinedRuntimeAttritubes中设置contentView.backgroundColor都不行,这是因为iOS9中Xib的视图最终并不会被放入contentView里(iOS10则无此问题,iOS10系统下Xib里面的视图都会被放入contentView中).
解决方案:不使用xib布局,或者在xib里面加一层自己的view覆盖原来的UITableViewHeaderFooterView,然后在自己的view里面设置颜色
UIPickerView无法显示选中行指示视图
先看下面图片,图中选中行上下有两条黑边,这个是能正常显示的样子.但是如果创建视图后从未执行过selectRow:inComponent:animated:这个方法,这不会出现黑边(iOS10下,iOS9无此问题)
解决方案:创建好UIPickerView之后额外执行一次[pickerView selectRow:2 inComponent:1 animated:NO]即可解决问题
数据相关
无法读取mainBoundle中的.json文件
json后缀的文件比较特殊,第一次加入xcode之后,编译时可能不会被打包到APP中.
解决方案:对于.json或其他类似格式的文件,需要自己在Xcode中的Targets->Build Phases->Copy Bundle Resources中将.json文件选入.
推送证书p12转pem免密码设置方法
具体做法参考这篇文章:导出 p12 或 pem 文件,这里主要说明一下如何在将p12转成pem的时候允许免密码操作(使用推荐的文章里面的指令转换时,key部分无法免密码).
解决方案:使用如下指令
openssl pkcs12 -in CertificateName.p12 -out CertificateName.pem -nodes
点击推送进入APP的相关处理方案
点击推送信息进入APP的时候,APP可能有两种状态,一种是已经在后台运行,另一种则是以启动的时候开始运行.所以点击推送信息进入APP要处理的,就必须针对这两种情况做处理.
解决方案:当APP已经在后台运行时,点击推送信息进入APP,将会触发AppDelegate中的- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo方法,推送信息附带在userInfo对象里面
当APP以启动的方式运行,则不会触发上面的方法,但是推送数据会在- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 中传入APP,此时可以通过[launchOptions objectForKey:UIApplicationLaunchOptionsRemoteNotificationKey]方法获取推送数据,为NSDictionary类型.
最后需要注意的是,如果APP在前台运行时收到推送信息,didReceiveRemoteNotification方法也会被调用,这个时候根据业务需要,可以通过application.applicationState == UIApplicationStateInactive来区分APP是否在前台运行,进而做不同的业务逻辑.
isEqualToString方法失灵了
isEqualToString方法用于判断两个字符串是否相等.但是有时候会出现两个明明相同的字符串,对比起来却不同.这是有原因的,从官方文档中可以得到答案,详情参考iOS isEqualToString Not Working
解决方案:使用(NSOrderedSame == [string1 localizedCompare:string2])代替