iOS 11下调用NSString的boundingRectWithSize方法崩溃问题求助
boundingRectWithSize崩溃及多行UILabel渲染崩溃的问题 这个iOS11的文本渲染坑我之前帮同事排查过,刚好能给你几个靠谱的解决方案。先说说问题根源,再看具体办法:
问题根源
这是iOS 11系统文本渲染引擎的一个已知bug,当字符串中包含某些特殊Unicode字符(比如你示例里的混合日文、emoji、特殊符号组合)时,系统在执行文本换行计算(NSParagraphArbitrator相关逻辑)时,会在-[NSString rangeOfCharacterFromSet:options:range:]方法中抛出异常,进而导致boundingRectWithSize调用崩溃,同时行数设为0的多行UILabel渲染时也会触发相同问题。
可行解决方案
方案1:过滤/替换问题字符
如果业务允许,可以提前过滤掉那些可能触发崩溃的特殊Unicode字符。你可以自定义需要过滤的字符集合,示例代码如下:
NSString *originalStr = @" しょーたฅ(๑•̀ω•́๑)ฅニャンニャンガオー★"; // 定义需要过滤的字符集合,可根据实际出现问题的字符调整 NSCharacterSet *problemChars = [NSCharacterSet characterSetWithCharactersInString:@"ฅ(๑•̀ω•́๑)ฅニャンニャンガオー★"]; // 清理字符串 NSString *cleanedStr = [[originalStr componentsSeparatedByCharactersInSet:problemChars] componentsJoinedByString:@""]; // 使用清理后的字符串计算尺寸 CGSize strSize = [cleanedStr boundingRectWithSize:CGSizeMake(281.33333, 100) options:NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading | NSStringDrawingUsesDeviceMetrics attributes:@{NSFontAttributeName:[UIFont systemFontOfSize:16]} context:nil].size;
⚠️ 注意:这个方案需要根据你的业务场景调整过滤规则,如果必须保留这些特殊字符,建议用下面的方案。
方案2:用CoreText手动计算文本尺寸
绕开系统的boundingRectWithSize方法,直接使用底层的CoreText来计算文本尺寸,完全避免触发上层的系统bug。示例代码:
NSString *str = @" しょーたฅ(๑•̀ω•́๑)ฅニャンニャンガオー★"; UIFont *font = [UIFont systemFontOfSize:16]; CGFloat maxWidth = 281.33333; // 构造CoreText需要的字体和富文本 CTFontRef ctFont = CTFontCreateWithName((__bridge CFStringRef)font.fontName, font.pointSize, NULL); NSMutableAttributedString *attributedStr = [[NSMutableAttributedString alloc] initWithString:str]; [attributedStr addAttribute:(id)kCTFontAttributeName value:(__bridge id)ctFont range:NSMakeRange(0, str.length)]; // 计算文本尺寸 CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString((__bridge CFAttributedStringRef)attributedStr); CGSize suggestedSize = CTFramesetterSuggestFrameSizeWithConstraints(framesetter, CFRangeMake(0, 0), NULL, CGSizeMake(maxWidth, CGFLOAT_MAX), NULL); CGSize strSize = CGSizeMake(ceil(suggestedSize.width), ceil(suggestedSize.height)); // 记得释放CoreText对象,避免内存泄漏 CFRelease(ctFont); CFRelease(framesetter);
这个方案兼容性更好,而且不会受系统上层bug影响。
方案3:针对多行UILabel的临时修复
如果是行数设为0的UILabel渲染崩溃,可以用一个小技巧绕过:先临时把行数设为1,强制Label完成一次布局,再改回多行模式:
UILabel *multiLineLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 281.33333, 100)]; multiLineLabel.font = [UIFont systemFontOfSize:16]; multiLineLabel.text = @" しょーたฅ(๑•̀ω•́๑)ฅニャンニャンガオー★"; // 临时设置行数为1,强制布局 multiLineLabel.numberOfLines = 1; [multiLineLabel layoutIfNeeded]; // 改回多行模式,再次布局 multiLineLabel.numberOfLines = 0; [multiLineLabel layoutIfNeeded];
这个办法能快速解决Label渲染崩溃的问题,适合不想改动文本计算逻辑的场景。
额外建议
如果你的App支持的最低系统版本可以提升到iOS12及以上,那最好不过——苹果在iOS12中已经修复了这个文本渲染的bug,不需要做任何额外处理。
内容的提问来源于stack exchange,提问作者Yanneng Zhang

