iOS 16 计算文本尺寸崩溃 boundingRectWithSize:

iOS 16 计算文本尺寸崩溃

线上出现一个奇怪的崩溃, 堆栈信息比较少, 每天十几次, 发现是 iOS16 的新坑.

崩溃日志最后的堆栈是

1
CGSize size = [self.descriLabel.text calculateBoundingRectWithSize:CGSizeMake(sessionMaxWidth, CGFLOAT_MAX) font:[ComHelper regularSystemFontOfSize:14] paragraphStyle:paragraphStyle].size;

我们 NSString 工具类计算尺寸出来崩溃, 计算用的 api 是boundingRectWithSize:options:attributes:context:context, 也是很常用的接口.

复现路径

一番定位后, 复现需要满足如下几个条件

  1. iOS 16 版本, 各个小版本均可
  2. 文本中含有 @" \n", 这个空格是 “NonBreakingSpace” 不间断空格, 空格后面接换行符
  3. attributes 中使用了 ParagraphStyle, 并且 alignment设置为了NSTextAlignmentJustified
  4. 换行符左边的文本足够长

什么是 “不间断空格”?

  1. 不间断空格 \u00a0, 主要用在office中, 让一个单词在结尾处不会换行显示, 快捷键ctrl+shift+space
  2. 半角空格(英文符号) \u0020, 代码中常用的
  3. 全角空格(中文符号) \u3000, 中文文章中使用

修复方案

这种特殊场景下换成正常的空格即可

测试代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.


NSString *enterChar = @"\n";
NSString *normalSpace = @" "; // 普通空格 \u0020
NSString *nonBreakingSpace = @" "; // \u00a0
NSString *normalEmpty = @" \n"; // \u0020\n
NSString *errorEmpty = @" \n"; // \u00a0\n

NSMutableParagraphStyle *paragraphStyle = [[NSMutableParagraphStyle alloc] init];
[paragraphStyle setLineSpacing:8];

NSMutableParagraphStyle *paragraphStyle1 = [[NSMutableParagraphStyle alloc] init];
[paragraphStyle1 setLineSpacing:8];
paragraphStyle1.alignment = NSTextAlignmentJustified;

[self testA:enterChar count:30 left:@"abc" right:@"只" style:nil];
[self testA:normalSpace count:30 left:@"abc" right:@"只" style:nil];
[self testA:nonBreakingSpace count:30 left:@"abc" right:@"只" style:nil];
[self testA:normalEmpty count:30 left:@"abc" right:@"只" style:nil];
[self testA:errorEmpty count:30 left:@"abc" right:@"只" style:nil];

[self testA:enterChar count:30 left:@"abc" right:@"只" style:paragraphStyle];
[self testA:normalSpace count:30 left:@"abc" right:@"只" style:paragraphStyle];
[self testA:nonBreakingSpace count:30 left:@"abc" right:@"只" style:paragraphStyle];
[self testA:normalEmpty count:30 left:@"abc" right:@"只" style:paragraphStyle];
[self testA:errorEmpty count:30 left:@"abc" right:@"只" style:paragraphStyle];

[self testA:enterChar count:30 left:@"abc" right:@"只" style:paragraphStyle1];
[self testA:normalSpace count:30 left:@"abc" right:@"只" style:paragraphStyle1];
[self testA:nonBreakingSpace count:30 left:@"abc" right:@"只" style:paragraphStyle1];
[self testA:normalEmpty count:30 left:@"abc" right:@"只" style:paragraphStyle1];

// 只有这个会崩溃
[self testA:errorEmpty count:30 left:@"abc" right:@"只" style:paragraphStyle1];

NSLog(@"success");
}

- (void)testA:(NSString *)errorStr
count:(NSUInteger)count
left:(NSString *)left
right:(NSString *)right
style:(NSMutableParagraphStyle *)paragraphStyle
{
NSMutableString *leftStr = [[NSMutableString alloc] init];
NSMutableString *rightStr = [[NSMutableString alloc] init];
for (int i = 0; i < count; i++)
{
[leftStr appendString:left];
for (int j = 0; j < count; j++)
{
[rightStr appendString:right];

NSString *testStr = [NSString stringWithFormat:@"%@%@%@", leftStr, errorStr, rightStr];
NSDictionary *dic = @{
NSFontAttributeName: [UIFont systemFontOfSize:14 weight:UIFontWeightRegular],
};
if (paragraphStyle)
{
dic = @{
NSFontAttributeName: [UIFont systemFontOfSize:14 weight:UIFontWeightRegular],
NSParagraphStyleAttributeName: paragraphStyle
};
}

// left 比较长, 绘制导致换行, 超过了设置的宽度(300) 才会崩溃
CGRect rect = [testStr boundingRectWithSize:CGSizeMake(300, CGFLOAT_MAX)
options:NSStringDrawingUsesLineFragmentOrigin
attributes:dic
context:nil];
}
}
}
-------------本文结束感谢您的阅读-------------

欢迎关注我的其它发布渠道