将HTML解析为NSAttributedText - 如何设置字体?

157

我试图让在HTML格式中的文本片段在UITableViewCell上以良好的方式在iPhone上显示。

到目前为止,我有这个:

NSError* error;
NSString* source = @"<strong>Nice</strong> try, Phil";
NSMutableAttributedString* str = [[NSMutableAttributedString alloc] initWithData:[source dataUsingEncoding:NSUTF8StringEncoding]
                                                           options:@{NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType,
                                                                     NSCharacterEncodingDocumentAttribute: [NSNumber numberWithInt:NSUTF8StringEncoding]}
                                                              documentAttributes:nil error:&error];

这样做有些效果。我得到了一些文本,其中“Nice”加粗了!但是... 它也将字体设置为Times Roman!这不是我想要的字体。 我想我需要在documentAttributes中设置一些内容,但是我无法找到任何示例。


1
注意:NSHTMLTextDocumentType 可能会很慢。请参考 https://dev59.com/JmEi5IYBdhLWcg3wjs5j - finneycanhelp
重要提示:如果您正在使用自定义字体,请查看此答案 https://stackoverflow.com/a/60786178/1223897。 - Yuvrajsinh
17个回答

149

Swift 2版本,基于Javier Querol的回答。

extension UILabel {
    func setHTMLFromString(text: String) {
        let modifiedFont = NSString(format:"<span style=\"font-family: \(self.font!.fontName); font-size: \(self.font!.pointSize)\">%@</span>", text) as String

        let attrStr = try! NSAttributedString(
            data: modifiedFont.dataUsingEncoding(NSUnicodeStringEncoding, allowLossyConversion: true)!,
            options: [NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType, NSCharacterEncodingDocumentAttribute: NSUTF8StringEncoding],
            documentAttributes: nil)

        self.attributedText = attrStr
    }
}

Swift 3.0 和 iOS 9+

extension UILabel {
    func setHTMLFromString(htmlText: String) {
        let modifiedFont = String(format:"<span style=\"font-family: '-apple-system', 'HelveticaNeue'; font-size: \(self.font!.pointSize)\">%@</span>", htmlText)

        let attrStr = try! NSAttributedString(
            data: modifiedFont.data(using: .unicode, allowLossyConversion: true)!,
            options: [NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType, NSCharacterEncodingDocumentAttribute: String.Encoding.utf8.rawValue],
            documentAttributes: nil)

        self.attributedText = attrStr
    }
}

Swift 5 和 iOS 11+

extension UILabel {
    func setHTMLFromString(htmlText: String) {
        let modifiedFont = String(format:"<span style=\"font-family: '-apple-system', 'HelveticaNeue'; font-size: \(self.font!.pointSize)\">%@</span>", htmlText)

        let attrStr = try! NSAttributedString(
            data: modifiedFont.data(using: .unicode, allowLossyConversion: true)!,
            options: [.documentType: NSAttributedString.DocumentType.html, .characterEncoding:String.Encoding.utf8.rawValue],
            documentAttributes: nil)

        self.attributedText = attrStr
    }
}

1
不改变当前字体,这正是我所期望的,谢谢! - Mohammad Zaid Pathan
3
这个可行。你可以立即将修改后的字符串设置为一个String,并省略NSString的初始化,即"<span style="font-family: (self.font!.fontName); font-size: (self.font!.pointSize)">(text)</span>"。 - Matthew Korporaal
2
为了使这个工作正常运行(它确实非常好),我不得不在font-family值周围添加单引号,因此<div style="font-family: '(self.font!.fontName)';... - geraldcor
4
我认为自iOS9以来,最好使用font-family: '-apple-system', 'HelveticaNeue';(它可行且向后兼容)。如果你只支持iOS9,则可以使用font-family: -apple-system; - Daniel
1
还有一个很方便的功能是设置文本颜色,只需将颜色添加到样式属性中,并使用十六进制字符串格式进行赋值 color: #000000。请参考此链接将UIColor转换为十六进制字符串:https://gist.github.com/yannickl/16f0ed38f0698d9a8ae7 - Miroslav Hrivik
显示剩余4条评论

115
#import "UILabel+HTML.h"

@implementation UILabel (HTML)

- (void)jaq_setHTMLFromString:(NSString *)string {

    string = [string stringByAppendingString:[NSString stringWithFormat:@"<style>body{font-family: '%@'; font-size:%fpx;}</style>",
                                              self.font.fontName,
                                              self.font.pointSize]];
    self.attributedText = [[NSAttributedString alloc] initWithData:[string dataUsingEncoding:NSUnicodeStringEncoding]
                                                           options:@{NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType,
                                                                     NSCharacterEncodingDocumentAttribute: @(NSUTF8StringEncoding)}
                                                documentAttributes:nil
                                                             error:nil];
}


@end
这样你就不需要指定想要的字体,它会使用标签的字体和大小。

2
这非常优雅! - Merlevede
2
不错。在我看来,它更适合作为NSAttributedString的一个类别。 - Dimitris
你可以使用NSUnicodeStringEncoding将字符串编码为数据,然后再使用NSUTF8StringEncoding将数据解码为字符。这样可以吗? - Timur Bernikovich
这个解决方案在使用 UILabel 时不会增加额外的空白(当使用 divspan 时会出现这种情况)。 - thijsonline
1
抱歉 - 这个解决方案对我不起作用,字体没有设置为所需的字体。 - 不使用self.font.fontName,而是使用self.font.familyName可以设置所需的字体,但是HTML标签不会被保留。请参见下面的解决方案,它可以正常工作,并且不依赖于任何HTML样式。-rrh - Richie Hyatt
显示剩余2条评论

54

实际上,我找到了解决这个问题的方法:

在HTML响应字符串被解析之前,更改字体。

NSString *aux = [NSString stringWithFormat:@"<span style=\"font-family: YOUR_FONT_NAME; font-size: SIZE\">%@</span>", htmlResponse];

例子:

NSString *aux = [NSString stringWithFormat:@"<span style=\"font-family: HelveticaNeue-Thin; font-size: 17\">%@</span>", [response objectForKey:@"content"]];

Swift版本:

let aux = "<span style=\"font-family: YOUR_FONT_NAME; font-size: SIZE\">\(htmlResponse)</span>"

4
最简单的解决方案...其他答案可能是正确的,但用更困难的方式来做事情并不明智.. :) - Sameera Chathuranga
2
最佳和聪明的答案 - Tariq
最聪明的答案,同意!干杯 - Jim Tierney
你好,实际上这个很好用,但是如果我将这个属性文本转换回HTML,那么在HTML中字体大小会增加。 - Mehul Thakkar
@MehulThakkar 是的,我认为这只是单向的。 - Teodor Ciuraru
1
实际上,通过在stackoverflow上寻求其他帖子的帮助,我能够将属性文本转换为HTML,并且除了字体大小几乎翻倍之外,一切都正常运行。 - Mehul Thakkar

49

一种更为通用的方法是在枚举字体特征时查看,并创建具有相同特征(加粗、斜体等)的字体:

extension NSMutableAttributedString {

    /// Replaces the base font (typically Times) with the given font, while preserving traits like bold and italic
    func setBaseFont(baseFont: UIFont, preserveFontSizes: Bool = false) {
        let baseDescriptor = baseFont.fontDescriptor
        let wholeRange = NSRange(location: 0, length: length)
        beginEditing()
        enumerateAttribute(.font, in: wholeRange, options: []) { object, range, _ in
            guard let font = object as? UIFont else { return }
            // Instantiate a font with our base font's family, but with the current range's traits
            let traits = font.fontDescriptor.symbolicTraits
            guard let descriptor = baseDescriptor.withSymbolicTraits(traits) else { return }
            let newSize = preserveFontSizes ? descriptor.pointSize : baseDescriptor.pointSize
            let newFont = UIFont(descriptor: descriptor, size: newSize)
            self.removeAttribute(.font, range: range)
            self.addAttribute(.font, value: newFont, range: range)
        }
        endEditing()
    }
}

2
尽管这不是非常简洁,但似乎比使用更多的HTML包装HTML来解决问题更加稳定。 - syvex

42

搞定了。可能有些复杂,也许不是最佳答案。

这段代码将遍历所有的字体更改。我知道它现在使用的字体是"Times New Roman"和"Times New Roman BoldMT"。 但无论如何,这段代码将找到粗体字并允许我重置它们。我也可以一并重置字体大小。

我真心希望/认为有一种方法可以在解析时设置它,但如果有的话,我找不到。

- (void)changeFont:(NSMutableAttributedString*)string
{
    NSRange range = (NSRange){0,[string length]};
    [string enumerateAttribute:NSFontAttributeName inRange:range options:NSAttributedStringEnumerationLongestEffectiveRangeNotRequired usingBlock:^(id value, NSRange range, BOOL *stop) {
    UIFont* currentFont = value;
    UIFont *replacementFont = nil;

    if ([currentFont.fontName rangeOfString:@"bold" options:NSCaseInsensitiveSearch].location != NSNotFound) {
        replacementFont = [UIFont fontWithName:@"HelveticaNeue-CondensedBold" size:25.0f];
    } else {
        replacementFont = [UIFont fontWithName:@"HelveticaNeue-Thin" size:25.0f];
    }

    [string addAttribute:NSFontAttributeName value:replacementFont range:range];
}];

}


2
在字体名称中寻找“BOLD”一词是一个可怕的hack!这也会破坏其他字体属性,如斜体。 - HughHughTeotl
1
一种更通用的方法是在枚举字体特征时进行查看,并创建具有相同特征的字体。我将在下面发布我的代码。 - markiv
警告:这种方式会破坏 Markdown 的格式。 - Nike Kov

25

UILabel扩展的Swift 4+更新


extension UILabel {
    func setHTMLFromString(text: String) {
        let modifiedFont = NSString(format:"<span style=\"font-family: \(self.font!.fontName); font-size: \(self.font!.pointSize)\">%@</span>" as NSString, text)

        let attrStr = try! NSAttributedString(
            data: modifiedFont.data(using: String.Encoding.unicode.rawValue, allowLossyConversion: true)!,
            options: [NSAttributedString.DocumentReadingOptionKey.documentType:NSAttributedString.DocumentType.html, NSAttributedString.DocumentReadingOptionKey.characterEncoding: String.Encoding.utf8.rawValue],
            documentAttributes: nil)

        self.attributedText = attrStr
    }
}

iOS 9+

extension UILabel {
    func setHTMLFromString(htmlText: String) {
        let modifiedFont = NSString(format:"<span style=\"font-family: '-apple-system', 'HelveticaNeue'; font-size: \(self.font!.pointSize)\">%@</span>" as NSString, htmlText) as String


        //process collection values
        let attrStr = try! NSAttributedString(
            data: modifiedFont.data(using: .unicode, allowLossyConversion: true)!,
            options: [NSAttributedString.DocumentReadingOptionKey.documentType:NSAttributedString.DocumentType.html, NSAttributedString.DocumentReadingOptionKey.characterEncoding: String.Encoding.utf8.rawValue],
            documentAttributes: nil)


        self.attributedText = attrStr
    }
}

23

是的,有一个更简单的解决方案。在HTML源代码中设置字体!

NSError* error;
NSString* source = @"<strong>Nice</strong> try, Phil";
source = [source stringByAppendingString:@"<style>strong{font-family: 'Avenir-Roman';font-size: 14px;}</style>"];
NSMutableAttributedString* str = [[NSMutableAttributedString alloc] initWithData:[source dataUsingEncoding:NSUTF8StringEncoding]
                                                           options:@{NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType,
                                                                     NSCharacterEncodingDocumentAttribute: [NSNumber numberWithInt:NSUTF8StringEncoding]}
                                                              documentAttributes:nil error:&error];

希望这可以帮到您。


11
以上的答案都可以在创建NSAttributedString时进行转换。但我认为更好的解决方案是使用下面的分类,该分类适用于字符串本身,因此不需要访问输入内容:
extension NSMutableAttributedString
{
    func convertFontTo(font: UIFont)
    {
        var range = NSMakeRange(0, 0)

        while (NSMaxRange(range) < length)
        {
            let attributes = attributesAtIndex(NSMaxRange(range), effectiveRange: &range)
            if let oldFont = attributes[NSFontAttributeName]
            {
                let newFont = UIFont(descriptor: font.fontDescriptor().fontDescriptorWithSymbolicTraits(oldFont.fontDescriptor().symbolicTraits), size: font.pointSize)
                addAttribute(NSFontAttributeName, value: newFont, range: range)
            }
        }
    }
}

用途:

let desc = NSMutableAttributedString(attributedString: *someNSAttributedString*)
desc.convertFontTo(UIFont.systemFontOfSize(16))

适用于iOS 7及以上版本


到处搜索了一遍...!! 谢谢..! - Irshad Qureshi

5

在Victor的解决方案上进行改进,包括颜色:

extension UILabel {
      func setHTMLFromString(text: String) {
          let modifiedFont = NSString(format:"<span style=\"color:\(self.textColor.toHexString());font-family: \(self.font!.fontName); font-size: \(self.font!.pointSize)\">%@</span>", text) as String

          let attrStr = try! NSAttributedString(
              data: modifiedFont.dataUsingEncoding(NSUnicodeStringEncoding, allowLossyConversion: true)!,
              options: [NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType, NSCharacterEncodingDocumentAttribute: NSUTF8StringEncoding],
              documentAttributes: nil)

          self.attributedText = attrStr
      }
  }

为了使此方法生效,您还需要 YLColor.swift 进行 uicolor 到十六进制转换。请从以下链接下载:https://gist.github.com/yannickl/16f0ed38f0698d9a8ae7。请注意保留 HTML 格式标签。

5

使用NSHTMLTextDocumentType很慢,而且控制样式很困难。我建议您尝试我的库,名为Atributika。它有自己非常快的解析器。此外,您可以拥有任何标签名称并为其定义任何样式。

示例:

let str = "<strong>Nice</strong> try, Phil".style(tags:
    Style("strong").font(.boldSystemFont(ofSize: 15))).attributedString

label.attributedText = str

你可以在这里找到它:https://github.com/psharanda/Atributika

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接