# UITextView-html-demo **Repository Path**: ihongren/UITextView-html-demo ## Basic Information - **Project Name**: UITextView-html-demo - **Description**: UITextView 加载 HTML 文本字符串的一些优化和注意事项 Demo - **Primary Language**: Objective-C - **License**: MIT - **Default Branch**: main - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2025-10-31 - **Last Updated**: 2025-11-08 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # iOS UITextView 加载 HTML 时的问题与优化 #### 在 iOS 中如果想加载显示 HTML 文本,一般有以下的几种方案: 1. 使用 WKWebView ,偏重、性能较差 2. 将 HTML 字符串转换为 `NSAttributedString` 对象,使用 UITextView,UILabel... 3. 使用一些三方库,如 DTCoreText、SwiftSoup 4. 自己去解析标签实现,较复杂。 对于一些详情原生页面,加载一段功能简单的 html 标签文本,使用 `NSAttributedString + UITextView` 是一种相对轻量的选择,本文也只讨论这种方式。 ### 然而在实际开发的过程中,我们很容易发现一些问题: ##### 1、html 字符串转 NSAttributedString 是同步的,文本稍大一点,就会阻塞主线程,页面卡死。 这个问题好解决,直接将转换操作放到子线程去做就好 ```objective-c dispatch_async(dispatch_get_global_queue(0, 0), ^{ NSAttributedString *att = [htmlString htmlToAttr]; dispatch_async(dispatch_get_main_queue(), ^{ self.textView.attributedText = att; }); }); ``` ##### 2、如果只是一些片段 html 标签,转换后的样式可能不太美观,可以加一些 CSS 来美化。 比如字体默认太小,图片显示太宽等。这时我们可以自己拼接一些 CSS 进去,下面代码我们增加默认字体大小 16px,图片宽度为 textView 宽度,高度自适应。 ```objective-c CGFloat contentWidth = self.textView.bounds.size.width; NSString *newHtml = [NSString stringWithFormat:@"%@",@"{font-size:16px;}",contentWidth,html]; ``` 你也可以去遍历 `NSParagraphStyleAttributeName` 属性,来设置一些 style。 ```objective-c // 设置行高 - (NSAttributedString*)addLineHeight:(CGFloat)lineHeight attr:(NSAttributedString*)attr { [attr enumerateAttribute:NSParagraphStyleAttributeName inRange:NSMakeRange(0, attr.length) options:(NSAttributedStringEnumerationLongestEffectiveRangeNotRequired) usingBlock:^(NSMutableParagraphStyle *style, NSRange range, BOOL * _Nonnull stop) { NSAttributedString *att = [attr attributedSubstringFromRange:range]; // 忽略 table 标签 if (![[att description] containsString:@"NSTextTableBlock"]) { style.lineSpacing = lineHeight; } }]; return attr; } ``` ##### 3、加载图片过大、多图时,首次显示很慢,拼网络了。 这种时候,我们可以先使用正则找出所有的 `` ,然后有两个种方案选择: 1、将所有 `` 删除, 先显示无图片的文本内容,再去加载原始带图片的 html。 ```objective-c NSString *pattern = @"]*>"; NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:pattern options:0 error:nil]; NSString *resultString = [regex stringByReplacingMatchesInString:html options:0 range:NSMakeRange(0, html.length) withTemplate:@""]; ``` 2、将所有 `` 替换为本地的默认图片,先显示带默认图片的,再去加载原始的 html。 ```objective-c // 使用占位图 NSString *fileUrl = [[NSBundle mainBundle] URLForResource:@"default_cover" withExtension:@"png"].absoluteString; NSString *replacement =[NSString stringWithFormat:@"", fileUrl]; NSString *pattern = @"<\\s*img\\s+[^>]*?src\\s*=\\s*[\'\"](.*?)[\'\"]\\s*(alt=[\'\"](.*?)[\'\"])?[^>]*?\\/?\\s*>"; NSRegularExpression *regexImg = [NSRegularExpression regularExpressionWithPattern:pattern options:NSRegularExpressionCaseInsensitive error:nil]; NSString *resultString = [regexImg stringByReplacingMatchesInString:html options:0 range:NSMakeRange(0, html.length) withTemplate:replacement]; ``` 这样,我们能减少首次显示的时间。 ##### 4、实现图片点击,能查看大图 在设置 UITextViewDelegate 代理后,通过代理方法去拦截。 ```objective-c self.textView.delegate = self; - (BOOL)textView:(UITextView *)textView shouldInteractWithTextAttachment:(NSTextAttachment *)textAttachment inRange:(NSRange)characterRange interaction:(UITextItemInteraction)interaction { // 拦截到了点击,但是获取不到点击的图片 } ``` 虽然能拦截到图片点击了,但是拿不到图片的 url,以及点击的是第几张图片。不过,如果加载的图片来自 fileURL,那就能拿到文件名 `NSString *fileName = textAttachment.fileWrapper.filename;` 我们完全可以实现一套 HTML 里的图片缓存,使用正则匹配出所有 `` 获取到图片 url , 借助 SDWebImage 去下载图片保存到本地,再用这个图片 fileURL 去替换掉原 src的内容。即达到了使用自己缓存的目的,这样加载出来的图片,点击时,可以知道点击的图片名 `filename`。 ```objective-c // 仅部分代码,完整的请看 demo: https://github.com/iHongRen/UITextView-html-demo // 找到所有图片url,imgs NSMutableArray *imgs = [NSMutableArray array]; for (NSTextCheckingResult *match in matches) { NSRange matchRange = [match rangeAtIndex:1]; NSString *imageUrl = [html substringWithRange:matchRange]; [imgs addObject:imageUrl]; } // 下载完成后进行 url 替换 __block NSString *newHtml = html; for (NSInteger i=0; i