iOS16及以上NSAttributedString解析HTML在UITextView崩溃求助
问题:iOS16+下NSAttributedString解析HTML赋值给UITextView崩溃
崩溃信息
Assertion failed: (forceBreakIndex > NSMaxRange(lastResortNode->lineBreak.range)), function -[_NSOptimalLineBreaker _calculateOptimalWrapping], file NSOptimalLineBreaker.m, line 1706
问题背景
该崩溃仅在iOS16及以上版本触发,iOS15及以下运行正常;部分HTML内容可正常解析,部分会触发崩溃(无法提供崩溃的HTML内容)。用户使用的解析代码如下:
func setHTMLContent(_ htmlContent: String, font: UIFont, textColor: UIColor) { guard let attributedString = try? NSMutableAttributedString(data: htmlContent.data(using: .utf8)!, options: [.documentType: NSAttributedString.DocumentType.html], documentAttributes: nil) else { return } attributedString.addAttributes([.font: font, .foregroundColor: textColor], range: NSRange(location: 0, length: attributedString.length)) self.attributedText = attributedString } // 调用方式 SomeTextView.setHTMLContent("Some HTML content", font: UIFont.systemFont(ofSize: 14), textColor: .white)
原因分析
- iOS16对
NSAttributedString的HTML解析引擎做了底层重构,尤其是文本换行、布局计算逻辑(对应崩溃类_NSOptimalLineBreaker),旧版本兼容的异常HTML结构(如空标签、嵌套格式错误、异常换行节点)在新引擎下会触发断言失败。 - 全局调用
addAttributes覆盖全范围属性,会强制替换HTML解析生成的原有排版属性(如段落样式、行高),导致布局计算时的索引逻辑冲突,触发断言。
解决方案
1. 预处理HTML内容
过滤或修复可能触发问题的HTML结构,减少异常节点:
func sanitizeHTML(_ html: String) -> String { var cleanedHTML = html // 移除空的p/div标签 cleanedHTML = cleanedHTML.replacingOccurrences(of: "<p>\\s*</p>", with: "", options: .regularExpression) cleanedHTML = cleanedHTML.replacingOccurrences(of: "<div>\\s*</div>", with: "", options: .regularExpression) // 合并连续换行标签 cleanedHTML = cleanedHTML.replacingOccurrences(of: "<br\\s*/?>\\s*<br\\s*/?>", with: "<br/>", options: .regularExpression) // 移除多余空白字符 cleanedHTML = cleanedHTML.replacingOccurrences(of: "\\s{2,}", with: " ", options: .regularExpression) return cleanedHTML }
在解析前调用该方法处理HTML内容。
2. 避免全局覆盖属性,改用属性合并
遍历每个字符的属性范围,合并自定义字体和颜色,保留原有排版属性:
func setHTMLContent(_ htmlContent: String, font: UIFont, textColor: UIColor) { let cleanedHTML = sanitizeHTML(htmlContent) guard let data = cleanedHTML.data(using: .utf8), let attributedString = try? NSMutableAttributedString(data: data, options: [.documentType: NSAttributedString.DocumentType.html], documentAttributes: nil) else { // 解析失败降级为普通文本 self.text = htmlContent self.font = font self.textColor = textColor return } // 遍历属性范围,合并自定义样式 attributedString.enumerateAttributes(in: NSRange(location: 0, length: attributedString.length), options: []) { attrs, range, _ in var mergedAttrs = attrs mergedAttrs[.font] = font mergedAttrs[.foregroundColor] = textColor attributedString.setAttributes(mergedAttrs, range: range) } self.attributedText = attributedString }
3. 捕获断言异常做兜底
由于Swift默认不捕获Objective-C断言类异常,需借助Objective-C工具类捕获崩溃,降级为普通文本展示:
// ExceptionCatcher.h #import <Foundation/Foundation.h> NS_INLINE BOOL catchException(void(^tryBlock)(void)) { @try { tryBlock(); return YES; } @catch (NSException *exception) { NSLog(@"Caught layout exception: %@", exception); return NO; } }
在Swift中调用:
func setHTMLContent(_ htmlContent: String, font: UIFont, textColor: UIColor) { let cleanedHTML = sanitizeHTML(htmlContent) guard let data = cleanedHTML.data(using: .utf8), let attributedString = try? NSMutableAttributedString(data: data, options: [.documentType: NSAttributedString.DocumentType.html], documentAttributes: nil) else { self.text = htmlContent self.font = font self.textColor = textColor return } attributedString.enumerateAttributes(in: NSRange(location: 0, length: attributedString.length), options: []) { attrs, range, _ in var mergedAttrs = attrs mergedAttrs[.font] = font mergedAttrs[.foregroundColor] = textColor attributedString.setAttributes(mergedAttrs, range: range) } // 尝试赋值,捕获异常 let success = catchException { self.attributedText = attributedString } if !success { // 崩溃降级 self.text = htmlContent self.font = font self.textColor = textColor } }
4. 替换为第三方HTML解析库
使用稳定的第三方库替代系统API,比如DTCoreText,这类库独立于系统解析引擎,版本兼容性更好。
内容的提问来源于stack exchange,提问作者Aditya Ramadhan
相关产品推荐
相关产品推荐

