iOS-开发小贴士


持续更新...

1.单击手势和双击手势冲突的解决

- (void)requireGestureRecognizerToFail:(UIGestureRecognizer *)otherGestureRecognizer;
//example usage:
[singleTap requireGestureRecognizerToFail:doubleTap];

2.AutoLayout中的baseline对齐

通常是对有文本显示功能的两个或者多个视图进行约束,NSLayoutAttributeLastBaseline 文本的最后一行、NSLayoutAttributeFirstBaseline文本的第一行,在UIView中有viewForFirstBaselineLayoutviewForLastBaselineLayout两个只读属性,在自定义的视图中,重写它们的getter方法返回某一子视图,然后就可以对这个自定义的视图使用baseline约束了。

3.OC中的范型

为了书写方便,我们在定义数组、字典等容器类型的变量时,常这样写NSArray<NSString *> *_categorys;在类型后加尖括号,括号内表示元素类型,在使用到某一个元素时,可以直接调用方法,像这样:_categorys.lastObject.length。在赋初值时,如果元素类型不匹配,Xcode会有警告。其中的关键是ObjetctType,在NSArray的头文件中@interface NSArray<__covariant ObjectType>是这样写的,自定义一个类,如下:

@interface Test<ObjetctType> : NSObject

- (void)addObj:(ObjetctType)obj;

@end

范型图1

注意,我在尖括号中只加入了ObjetctType,而没有添加__covariant关键字,__covariant关键字表示协变(让一个带有协变参数的泛型接口(或委托)可以接收类型更加精细化,具体化的泛型接口(或委托)作为参数,可以看成OO中多态的一个延伸。),因为有这样一个关键字,NSArray可以做以下操作:
范型图2

Test不可以:
范型图3

另外与__covariant相对的是__contravariant逆变(让一个带有协变参数的泛型接口(或委托)可以接收粒度更粗的泛型接口或委托作为参数,这个过程实际上是参数类型更加精细化的过程。)
协变让一个粗粒度接口(或委托)可以接收一个更加具体的接口(或委托)作为参数(或返回值);逆变让一个接口(或委托)的参数类型(或返回值)类型更加具体化,也就是参数类型更强,更明确。
  通常,协变类型参数可用作委托的返回类型,而逆变类型参数可用作参数类型。对于接口,协变类型参数可用作接口的方法的返回类型,而逆变类型参数可用作接口的方法的参数类型。

ps.此处黑体字部分参考博客:一句话清晰总结协变(covariant)和逆变 (contravariant)

4.iOS11中搜索框的新特性

    UINavigationItem
    @available(iOS 11.0, *)
    open var searchController: UISearchController?
搜索框

5.swift中的值、变量和常量

值 (value)是不变的,永久的,它从不会改变。比如,1, true 和 [1,2,3] 都是值。这些是字面量 (literal)的例子,值也可以是运行时生成的。当你计算 5 的平方时,你得到的数字也是一个值。
当我们使用 var x = [1,2] 来将一个值进行命名的时候,我们实际上创建了一个名为 x 的变量 (variable)来持有 [1,2] 这个值。通过像是执行 x.append(3) 这样的操作来改变 x 时,我们并没有改变原来的值。相反,我们所做的是使用 [1,2,3] 这个新的值来替代原来 x 中的内容。可能实际上它的内部实现真的只是在某段内存的后面添加上一个条目,并不是全体的替换,但是至少从逻辑上来说值是全新的。我们将这个过程称为变量的改变 (mutating)
我们还可以使用 let 而不是 var 来声明一个常量变量 (constant variables),或者简称为常量。一旦常量被赋予一个值,它就不能再次被赋一个新的值了。
摘录来自: Chris Eidhof. “Swift 进阶”。

6.在使用git clone某一个库的时候如果出现如下错误:
error_01
error: RPC failed; curl 18 transfer closed with outstanding read data remaining
....

错误的原因是git库的缓存空间不足,无法clone过大的库
可在当前目录下执行如下命令:

git config http.postBuffer 524288000

524288000表示524288000B,即500MB,具体的数值可根据不同状况来进行调整。

7.字符串含有中文转URL
//iOS9之前
urlString = [urlString stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
//iOS9之后
NSCharacterSet *set = [[NSCharacterSet characterSetWithCharactersInString:@"`#%^{}\"[]|\\<> "] invertedSet];
urlString = [urlString stringByAddingPercentEncodingWithAllowedCharacters:set];
8.富文本高度计算不准确
NSDictionary *dic = @{NSFontAttributeName:detail.font, NSParagraphStyleAttributeName:paragraphStyle, NSKernAttributeName:@0
                          };
CGFloat h = [str boundingRectWithSize:CGSizeMake(kScreenWidth-48.0, MAXFLOAT) options:NSStringDrawingUsesLineFragmentOrigin attributes:dic context:NULL].size.height;

在属性字典中添加NSKernAttributeName:@0键值对

9.使用正则表达式截取html、xml标签内容

应用场景:当从后台获取文字并展示超链接富文本时,需要获取到url和文本内容
<a href="https://www.jianshu.com/u/0bead25792b7">yue博客</a>为例

NSString *replaceKey = @"<a href=";
NSRange keyRange = NSMakeRange(0, 0);
NSString *href = nil, *text = nil;
NSString *content = @"<a href="https://www.jianshu.com/u/0bead25792b7">yue博客</a>";
if ([content containsString:replaceKey]) {
    NSRegularExpression *hrefRegex = [NSRegularExpression regularExpressionWithPattern:@"(?<=href=\").+(?=\")" options:kNilOptions error:NULL];
    NSRegularExpression *textRegex = [NSRegularExpression regularExpressionWithPattern:@"(?<=>).+(?=<)" options:kNilOptions error:NULL];
    
    NSTextCheckingResult *hrefResult = [hrefRegex firstMatchInString:content options:kNilOptions range:NSMakeRange(0, content.length)];
    NSTextCheckingResult *textResult = [textRegex firstMatchInString:content options:kNilOptions range:NSMakeRange(0, content.length)];
    if (hrefResult && textResult && hrefResult.range.location != NSNotFound && textResult.range.location != NSNotFound) {
        href = [content substringWithRange:hrefResult.range];
        text = [content substringWithRange:textResult.range];
    }
    //去掉标签内容
    content = [content stringByReplacingOccurrencesOfString:@"<a href=(.*?)>" withString:@"" options:NSRegularExpressionSearch range:NSMakeRange (0, content.length)];
    content = [content stringByReplacingOccurrencesOfString:@"<\\/a>" withString:@"" options:NSRegularExpressionSearch range:NSMakeRange (0, content.length)];
    keyRange = [content rangeOfString:text];
}

此处代码取自YYKitDemo,我根据自身情况去掉了(?<=href=\").+(?=\" )末尾的空格。
此时已知富文本内容、超链接、range,创建NSMutableAttributedString添加属性即可,点击事件可以使用UITextView或其他第三方控件来做。
(?<=href=\").+(?=\")firstMatchInString方法适用于只有一个标签的场景,当有多个标签时需要在(?=\")前加一个?表示非贪婪匹配,并将firstMatchInString方法换成matchesInString方法,得出的是一个元素为NSTextCheckingResult类型的数组

NSString *content = @"<a href=\"https://www.jianshu.com/u/0bead25792b7\">yue博客</a>\
<a href=\"https://www.jianshu.com/u/0bead25792b7\">yue博客</a>\
<a href=\"https://www.jianshu.com/u/0bead25792b7\">yue博客</a>\
<a href=\"https://www.jianshu.com/u/0bead25792b7\">yue博客</a>";

NSRegularExpression *hrefRegex = [NSRegularExpression regularExpressionWithPattern:@"(?<=href=\").+?(?=\")" options:kNilOptions error:NULL];
NSRegularExpression *textRegex = [NSRegularExpression regularExpressionWithPattern:@"(?<=>).+?(?=<)" options:kNilOptions error:NULL];
NSArray<NSTextCheckingResult *> *hrefResults = [hrefRegex matchesInString:content options:kNilOptions range:NSMakeRange(0, content.length)];
NSArray<NSTextCheckingResult *> *textResults = [textRegex matchesInString:content options:kNilOptions range:NSMakeRange(0, content.length)];
for (NSTextCheckingResult *href in hrefResults) {
    if (href.range.location != NSNotFound) {
        NSLog(@"%@",[content substringWithRange:href.range]);
    }
}
for (NSTextCheckingResult *text in textResults) {
    if (text.range.location != NSNotFound) {
        NSLog(@"%@",[content substringWithRange:text.range]);
    }
}
10.禁止UITableView 的reload动画,解决闪动问题

动画选项设置为UITableViewRowAnimationNone时,会有闪动现象

[UIView performWithoutAnimation:^{
    [self.tableView reloadRowsAtIndexPaths:@[[NSIndexPath indexPathForRow:3 inSection:0]] withRowAnimation:UITableViewRowAnimationNone];
}];
11.APP防止接口被抓包

向后台或者运维要一下网络证书,用钥匙串打开,导出为cer文件,拖至项目内,在创建AFHTTPSessionManager或者AFURLSessionManager时做如下处理

AFSecurityPolicy *policy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModePublicKey];
sessionManager.securityPolicy = policy;

证书验证有如下两种方式,我使用的是AFSSLPinningModePublicKey因为这样可以避免证书到期需要APP重新发版的问题。(2018-12-18证书到期替换新证书后,公钥也会变,在此不建议使用此方法做APP防止接口被抓包处理😢)

AFSSLPinningModePublicKey,//公钥验证,不做有效期验证
AFSSLPinningModeCertificate,//做有效期验证

如果项目中没有使用AFN的话,可在NSURLSessionDelegateNSURLSessionTaskDelegate方法
- (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler中进行设置,具体的验证步骤,请参考AFNetWorking内的做法

AFURLSessionManager.m 
NSURLSessionDelegate ->- (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler;
NSURLSessionTaskDelegate ->- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler;

AFSecurityPolicy.m 
- (BOOL)evaluateServerTrust:(SecTrustRef)serverTrust forDomain:(NSString *)domain;
static NSArray * AFCertificateTrustChainForServerTrust(SecTrustRef serverTrust);
static NSArray * AFPublicKeyTrustChainForServerTrust(SecTrustRef serverTrust);
11.模仿iPhone自带地图的列表滑动效果

demo地址

效果图片

12.判断5G网络的一个坑:iOS14.0CTRadioAccessTechnologyNRNSA 、CTRadioAccessTechnologyNR闪退

通常的判断方法是,在官方API中有两个字符串表示5G,并标注了API_AVAILABLE(ios(14.0))

CORETELEPHONY_EXTERN NSString * const CTRadioAccessTechnologyNRNSA         API_AVAILABLE(ios(14.0)) API_UNAVAILABLE(macOS);
CORETELEPHONY_EXTERN NSString * const CTRadioAccessTechnologyNR            API_AVAILABLE(ios(14.0)) API_UNAVAILABLE(macOS);

但是如果使用如下方法进行判断,那么在iOS14.0、14.0.1版本上则会收获一个crash

CTTelephonyNetworkInfo *netinfo = [[CTTelephonyNetworkInfo alloc] init];
if (@available(iOS 14.0, *)) {
    if ([netinfo.currentRadioAccessTechnology isEqualToString:CTRadioAccessTechnologyNRNSA] || [netinfo.currentRadioAccessTechnology isEqualToString:CTRadioAccessTechnologyNR]) {
        // 5G
    }
}

if (@available(iOS 14.0, *))改为if (@available(iOS 14.1, *))即可

13.Use of unimplemented initializer 'init(nibName:bundle:)

混编项目在iOS12中,swift的navigationController、viewController会报Use of unimplemented initializer 'init(nibName:bundle:)'错误,需要在重写init(rootViewController:)、init()方法的同时,重写init(nibName:bundle:)方法

** “Unlike subclasses in Objective-C, Swift subclasses do not inherit their superclass initializers by default.”
Automatic Initializer Inheritance
Rule 1: If your subclass doesn’t define any designated initializers, it automatically inherits all of its superclass designated initializers.
Rule 2: If your subclass provides an implementation of all of its superclass designated initializers—either by inheriting them as per rule 1, or by providing a custom implementation as part of its definition—then it automatically inherits all of the superclass convenience initializers.
参考

14.关于获取键盘高度异常的bug修复

在过去的一年中,测试常常会提出一个bug,app中固定在键盘上方的输入框,会被键盘遮挡,因为没有规律可循,无法可靠复现,只知道出现这种情况的时候,获取的键盘高度是错误的,所以一直没有得到解决。
这个bug解决的过程是,最后找到了复现规律,当用户输入账号密码登录后,系统的建议保存密码的actionSheet弹出后,就可以复现这个bug。所以当时的解决方法是,修改了输入账号密码的输入框的textContentType,让保存密码的actionSheet不再弹出,bug暂时得到了解决。
但并未真正解决,后来又再次出现了,因为以上说的只是表层原因。根本原因在于,iOS13后,UIViewController的默认modalPresentationStyle改为了automatic,我们为了保持modalPresentationStylefullScreen,使用了runtime的方法交换,将所有的UIViewControllermodalPresentationStyle修改为fullScreen,这一偷懒的做法,把一些系统的视图控制器的modalPresentationStyle也改为了fullScreen,从而导致了系统异常,将此段代码去掉,然后在需要的地方,手动设定fullScreen

15.webView禁用选中、长按、重按手势

<body style="-webkit-user-select: none; user-select: none;">

16.NSURL的一些变化

在iOS17及以上版本,使用+ URLWithString:或其他方法通过url字符串构建NSURL时,对于参数中存在特殊字符如:#[]@等,不进行转义也可构建成功,是因为苹果在URL解析是使用了更新的RFC 3986标准

苹果文档中的说明

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,254评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,875评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,682评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,896评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,015评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,152评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,208评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,962评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,388评论 1 304
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,700评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,867评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,551评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,186评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,901评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,142评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,689评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,757评论 2 351