美文网首页
怎么应对各种富文本表现需求?

怎么应对各种富文本表现需求?

作者: forping | 来源:发表于2024-01-28 14:14 被阅读0次

    在 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;

    相关文章

      网友评论

          本文标题:怎么应对各种富文本表现需求?

          本文链接:https://www.haomeiwen.com/subject/widrzktx.html