在 iOS 开发中,富文本的展示是一个非常常见的需求。
简单来说,富文本就是一段有属性的字符串,可以包含不同字体、不同字号、不同背景、不同颜色、不同字间距的文字,还可以设置段落、图文混排等等属性。
对于展示富文本,有两种方案
WebView
当富文本的内容是html的时候,使用 WebView 显示只需要创建一个 UIWebView 对象(现在使用WKWebView),进行一些基本滚动相关的设置,然后读取 HTML 字符串就可以了,具体实现代码如下:
self.wbView = [[UIWebView alloc] init];
self.wbView.delegate = self;
[self.view addSubview:self.wbView];
[self.wbView mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.left.right.bottom.equalTo(self.view);
}];
self.wbView.scalesPageToFit = YES; // 确保网页的显示尺寸和屏幕大小相同
self.wbView.scrollView.directionalLockEnabled = YES; // 只在一个方向滚动
self.wbView.scrollView.showsHorizontalScrollIndicator = NO; // 不显示左右滑动
[self.wbView setOpaque:NO]; // 默认是透明的
// 读取文章 html 字符串进行展示
[self.wbView loadHTMLString:articleString baseURL:nil];
和 UIWebView 的 loadRequest 相比,UIWebView 通过 loadHTMLString 直接读取 HTML 代码,省去了网络请求的时间,展示的速度非常快。不过,HTML 里的图片资源还是需要通过网络请求来获取。
所以,如果能够在文章展示之前就缓存下图片,那么无需等待,就能够快速完整地展示丰富的文章内容了。
在 Cocoa 层使用 NSURLProtocol 可以拦截所有 HTTP 的请求,因此我可以利用 NSURLProtocol 来缓存文章中的图片。
首先,在所有网络请求的入口 canInitWithRequest 方法中加上过滤条件
// User-Agent来过滤
if (sModel.whiteUserAgent.length > 0) {
// 在 HTTP header 里取出 User Agent
NSString *uAgent = [request.allHTTPHeaderFields objectForKey:@"User-Agent"];
if (uAgent) {
// 不在白名单中返回 NO,不会进行缓存
if (![uAgent hasSuffix:sModel.whiteUserAgent]) {
return NO;
}
} else {
return NO;
}
}
UserAgent 白名单过滤会通过 request 的 allHTTPHeaderFields 获取到当前网络请求的 UserAgent,然后和已经设置的 UserAgent 白名单做比较:如果在白名单中就进行缓存;否则,就不会缓存。
还可以根据域名进行过滤,这样可以灵活、精确地控制缓存范围。如果你设置了域名白名单,那么只有在白名单里的域名下的网络请求才会执行缓存,过滤代码如下:
//对于域名白名单的过滤
if (sModel.whiteListsHost.count > 0) {
id isExist = [sModel.whiteListsHost objectForKey:request.URL.host];
// 如果当前请求的域名不在白名单中也会返回 NO
if (!isExist) {
return NO;
}
}
当前网络请求的域名可以通过 request.URL.host 属性获取到,获取到网络请求的域名后,再去看域名白名单里是否有,如果有就缓存,没有就返回 NO,不进行缓存操作。
在 canInitWithRequest 方法中满足缓存条件后,开始缓存的方法是 startLoading。startLoading 方法会判断已缓存和未缓存的情况,如果没有缓存会发起网络请求,将请求到的数据保存在本地。如果有缓存,则会直接从本地读取缓存,实现代码如下:
// 从缓存里读取数据
NSData *data = [NSData dataWithContentsOfFile:self.filePath];
NSURLResponse *response = [[NSURLResponse alloc] initWithURL:self.request.URL MIMEType:[otherInfo objectForKey:@"MIMEType"] expectedContentLength:data.length textEncodingName:[otherInfo objectForKey:@"textEncodingName"]];
[self.client URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed];
// 使用 NSURLProtocolClient 的 URLProtocol:didLoadData 方法加载本地数据
[self.client URLProtocol:self didLoadData:data];
[self.client URLProtocolDidFinishLoading:self];
STMURLProtocol 先通过缓存的路径获取到缓存的数据,再使用 NSURLProtocolClient 的 URLProtocol:didLoadData 方法加载本地缓存数据,以减少网络请求。
显示文章内容时使用 NSURLProtocol,对于那些已经缓存过图片的文章就不用发起图片的网络请求,显示的速度跟本地加载显示速度一样快。
WKWebView 思路类似,使用WKURLSchemeHandler就行
-[WKWebViewConfiguration setURLSchemeHandler:forURLScheme:]
YYText
YYText 对于富文本的图文混排使用的是自定义的 NSMutableAttributedString 分类,自定义分类不光简化了 NSMutableAttributedString,还增加了功能,除了图片外,可以嵌入 UIView 和 CALayer。
属性字符串
NSAttributedString 共有21个属性
// 上面例子中用过这个属性,字体 - 默认字体:Helvetica(Neue),字号:12
1. NSFontAttributeName
// 段落 - 取值为 NSParagraphStyle 对象,文本段落排版格式,行间距等
2. NSParagraphStyleAttributeName
// 字体颜色 - 默认为黑色
3. NSForegroundColorAttributeName
// 字体背景颜色 - 默认为无背景色
4. NSBackgroundColorAttributeName
// 连体字符 - 该属性所对应的值是一个 NSNumber 对象(整数)。连体字符是指某些连在一起的字符,它们采用单个的图元符号。0 表示没有连体字符。1 表示使用默认的连体字符。2表示使用所有连体符号。默认值为 1(注意,iOS 不支持值为 2)。
5. NSLigatureAttributeName
// 设置字符间距,取值为 NSNumber 对象(整数),正值间距加宽,负值间距变窄
6. NSKernAttributeName
// 删除线 - 取值为 NSNumber 对象(整数)
7. NSStrikethroughStyleAttributeName
// 设置删除线颜色,取值为 UIColor 对象,默认值为黑色
8. NSStrikethroughColorAttributeName
// 下划线,取值为 NSNumber 对象(整数),枚举常量 NSUnderlineStyle中的值,与删除线类似
9. NSUnderlineStyleAttributeName
// 下划线颜色,UIColor 对象,默认值为黑色
10. NSUnderlineColorAttributeName
// 笔画宽度(粗细),取值为 NSNumber 对象(整数),负值填充效果,正值中空效果
11. NSStrokeWidthAttributeName
// 填充部分颜色,不是字体颜色,取值为
12. NSStrokeColorAttributeName
// 阴影属性,取值为 NSShadow 对象
13. NSShadowAttributeName
// 文本特殊效果,取值为 NSString 对象,目前只有图版印刷效果可用
14. NSTextEffectAttributeName
// 设置基线偏移值,取值为 NSNumber (float),正值上偏,负值下偏
15. NSBaselineOffsetAttributeName
// 设置字形倾斜度,取值为 NSNumber (float),正值右倾,负值左倾
16. NSObliquenessAttributeName
// 文本横向拉伸属性,取值为 NSNumber (float),正值横向拉伸文本,负值横向压缩文本
17. NSExpansionAttributeName
// 设置文字书写方向,从左向右书写或者从右向左书写
18. NSWritingDirectionAttributeName
// 文字排版方向,取值为 NSNumber 对象(整数),0 表示横排文本,1 表示竖排文本
19. NSVerticalGlyphFormAttributeName
// 设置链接属性,点击后调用浏览器打开指定URL地址
20. NSLinkAttributeName
// 设置文本附件,取值为NSTextAttachment对象,常用于文字图片混排
21. NSAttachmentAttributeName
关于第二条NSParagraphStyleAttributeName
对应的NSParagraphStyle
对象,
包括以下属性:
1.alignment // 对齐方式
2.firstLineHeadIndent // 首行缩进
3.headIndent // 缩进
4.tailIndent // 尾部缩进
5.lineBreakMode // 断行方式
6.maximumLineHeight // 最大行高
7.minimumLineHeight // 最低行高
8.lineSpacing // 行距
9.paragraphSpacing // 段距
10.paragraphSpacingBefore // 段首空间
11.baseWritingDirection // 句子方向
12.lineHeightMultiple // 可变行高,乘因数。
13.hyphenationFactor // 连字符属性
实例
NSParagraphStyleAttributeName - 字段落
NSMutableParagraphStyle *style = [[NSMutableParagraphStyle alloc] init];
style.firstLineHeadIndent = 30;
style.lineSpacing = 10;
style.lineBreakMode = NSLineBreakByWordWrapping;
[attrString addAttribute:NSParagraphStyleAttributeName value:style range:NSMakeRange(0, str.length)];
[图片上传失败...(image-4bfb35-1706508864214)]
NSForegroundColorAttributeName - 字色
[attrString addAttribute:NSForegroundColorAttributeName value:[UIColor colorWithRed:0.90 green:0.44 blue:0.38 alpha:1.00] range:NSMakeRange(0, str.length)];
NSBackgroundColorAttributeName - 字背景色
[attrString addAttribute:NSBackgroundColorAttributeName value:[UIColor colorWithRed:0.53 green:0.77 blue:0.48 alpha:1.00] range:NSMakeRange(0, str.length)];
[图片上传失败...(image-6a92da-1706508864214)]
NSKernAttributeName - 字间距
[attrString addAttribute:NSKernAttributeName value:@3 range:NSMakeRange(0, str.length)];
NSStrikethroughStyleAttributeName - 删除线
[attrString addAttribute:NSStrikethroughStyleAttributeName value:@1 range:NSMakeRange(22, 10)];
NSStrikethroughColorAttributeName - 删除线颜色
[attrString addAttribute:NSStrikethroughColorAttributeName value:[UIColor whiteColor] range:NSMakeRange(24, 6)];
[图片上传失败...(image-ee1695-1706508864214)]
NSUnderlineStyleAttributeName
[attrString addAttribute:NSUnderlineStyleAttributeName value:@3 range:NSMakeRange(33, 10)];
NSUnderlineColorAttributeName
[attrString addAttribute:NSUnderlineColorAttributeName value:[UIColor colorWithRed:1.00 green:0.30 blue:0.00 alpha:1.00] range:NSMakeRange(36, 4)];
[图片上传失败...(image-700aeb-1706508864214)]
NSStrokeWidthAttributeName
[attrString addAttribute:NSStrokeWidthAttributeName value:@10 range:NSMakeRange(40, 6)];
NSStrokeColorAttributeName
[attrString addAttribute:NSStrokeColorAttributeName value:[UIColor colorWithRed:1.00 green:0.89 blue:0.18 alpha:1.00] range:NSMakeRange(40, 6)];
NSShadowAttributeName
NSShadow *shadow = [[NSShadow alloc]init];
shadow.shadowOffset = CGSizeMake(10, 10);
shadow.shadowColor = [UIColor redColor];
[attrString addAttribute:NSShadowAttributeName value:shadow range:NSMakeRange(0, str.length)];
[图片上传失败...(image-390339-1706508864214)]
NSTextEffectAttributeName
[attrString addAttribute:NSTextEffectAttributeName value:NSTextEffectLetterpressStyle range:NSMakeRange(50, 10)];
NSBaselineOffsetAttributeName
[attrString addAttribute:NSBaselineOffsetAttributeName value:@1 range:NSMakeRange(10, 10)];
NSObliquenessAttributeName
[attrString addAttribute:NSObliquenessAttributeName value:@0.5 range:NSMakeRange(10, 20)];
NSExpansionAttributeName
[attrString addAttribute:NSExpansionAttributeName value:@1.0 range:NSMakeRange(10, 10)];
NSWritingDirectionAttributeName
[attrString addAttribute:NSWritingDirectionAttributeName value:@[@2] range:NSMakeRange(10, 10)];
NSVerticalGlyphFormAttributeName
[mAttStr addAttribute:NSVerticalGlyphFormAttributeName value:@0 range:NSMakeRange(10, 10)];
NSLinkAttributeName
/**
* 此属性的值是NSURL对象(首选)或一个NSString对象。此属性的默认值为nil,表示没有链接。
* UILabel无法使用该属性, 可以使用UITextView 控件.
*/
UITextView *textView = [[UITextView alloc] initWithFrame:CGRectMake(50, 50, 50, 50)];
[self.view addSubview:textView];
textView.backgroundColor = [UIColor redColor];
NSString *strLink = @"百度链接";
NSAttributedString *attStrUrl = [[NSAttributedString alloc] initWithString:strLink attributes:@{NSLinkAttributeName: [NSURL URLWithString:@"http://www.baidu.com"]}];
textView.editable = NO;
/* 签订协议, 指定代理人之后. 但点击链接时, 会回调协议方法 (- textView:shouldInteractWithURL:inRange:) */
textView.delegate = self;
textView.attributedText = attStr;
- (BOOL)textView:(UITextView *)textView shouldInteractWithURL:(NSURL *)URL inRange:(NSRange)characterRange {
NSLog(@"%s", __FUNCTION__);
NSLog(@"url: %@", URL);
return YES;
}
NSAttachmentAttributeName
/* 这个属性的值是一个NSTextAttachment对象。此属性的默认值为nil,表示无附件。*/
/**
* 关于NSTextAttachment类的简单说明
*
* NSTextAttachment 类有一个指定的初始化方法(- initWithData:ofType:), 需要指定附件文档的数据和附件文件的类型. 如果附件文档数据指定nil, 那么系统将会默认指定为image对象作为值. 因此, 也可以通过这个特性实现图文混排.
* 下面就以附件为image对象来说明NSAttachmentAttributeName的使用.
*
*/
UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(50, 50, 50, 50)];
label.backgroundColor = [UIColor redColor];
[self.view addSubview:label];
/* 下面实现在百度两个汉字之间插入一个照片 */
NSString *stiAtt = @"百度";
NSTextAttachment *attach = [[NSTextAttachment alloc] initWithData:nil ofType:nil];
attach.bounds = CGRectMake(0, 0, 50, 50);
attach.image = [UIImage imageNamed:@"taobao.jpg"];
NSAttributedString *strAtt = [NSAttributedString attributedStringWithAttachment:attach];
NSMutableAttributedString *strMatt = [[NSMutableAttributedString alloc] initWithString:stiAtt];
[strMatt insertAttributedString:strAtt atIndex:1];
label.attributedText = strMatt;
self.titleLabel.attributedText = mAttStr;
[self.titleLabel sizeToFit];
在iOS中如何正确的实现行间距与行高
https://www.51cto.com/article/569439.html
系统的留白
label.font.lineHeight - label.font.pointSize
设置行间距
NSMutableParagraphStyle *paragraphStyle = [NSMutableParagraphStyle new];
paragraphStyle.lineSpacing = 间距 - (label.font.lineHeight - label.font.pointSize);
行高
NSMutableParagraphStyle *paragraphStyle = [NSMutableParagraphStyle new];
paragraphStyle.maximumLineHeight = lineHeight;
paragraphStyle.minimumLineHeight = lineHeight;
NSMutableDictionary *attributes = [NSMutableDictionary dictionary];
[attributes setObject:paragraphStyle forKey:NSParagraphStyleAttributeName];
CGFloat baselineOffset = (lineHeight - label.font.lineHeight) / 4;
网友评论