【问题标题】:NSAttributedString, change the font overall BUT keep all other attributes?NSAttributedString,整体更改字体但保留所有其他属性?
【发布时间】:2017-09-29 02:51:07
【问题描述】:

假设我有一个NSMutableAttributedString

字符串具有各种混合的格式:

这是一个例子:

这个字符串改变 在iOS中,它真的 糟透了 .

但是,字体本身并不是您想要的字体。

我想:

对于每个字符,将该字符更改为特定的字体(例如,Avenir)

但是,

对于每个字符,保留其他属性(粗体、斜体、颜色等)的混合以前在那个角色身上。

你到底是怎么做到的?


注意:

如果您在整个范围内简单地添加属性“Avenir”:它只是删除所有其他属性范围,您会丢失所有格式。不幸的是,属性不是,实际上是“附加的”。

【问题讨论】:

标签: swift nsattributedstring


【解决方案1】:

由于 rmaddy 的回答对我不起作用(f.fontDescriptor.withFace(font.fontName) 不保留粗体等特征),这里是更新的 Swift 4 版本,其中还包括颜色更新:

extension NSMutableAttributedString {
    func setFontFace(font: UIFont, color: UIColor? = nil) {
        beginEditing()
        self.enumerateAttribute(
            .font, 
            in: NSRange(location: 0, length: self.length)
        ) { (value, range, stop) in

            if let f = value as? UIFont, 
              let newFontDescriptor = f.fontDescriptor
                .withFamily(font.familyName)
                .withSymbolicTraits(f.fontDescriptor.symbolicTraits) {

                let newFont = UIFont(
                    descriptor: newFontDescriptor, 
                    size: font.pointSize
                )
                removeAttribute(.font, range: range)
                addAttribute(.font, value: newFont, range: range)
                if let color = color {
                    removeAttribute(
                        .foregroundColor, 
                        range: range
                    )
                    addAttribute(
                        .foregroundColor, 
                        value: color, 
                        range: range
                    )
                }
            }
        }
        endEditing()
    }
}

注意事项

f.fontDescriptor.withFace(font.fontName) 的问题在于它删除了像italicboldcompressed 这样的符号特征,因为它会出于某种原因覆盖那些具有该字体的默认特征的特征。为什么这完全让我难以理解,甚至可能是苹果公司的疏忽;或者它“不是错误,而是一个特性”,因为我们免费获得了新字体的特性。

所以我们要做的是创建一个字体描述符,它具有原始字体字体描述符的符号特征:.withSymbolicTraits(f.fontDescriptor.symbolicTraits)。为我迭代的初始代码提供 rmaddy 的道具。

我已经在一个生产应用程序中发布了这个,我们通过NSAttributedString.DocumentType.html 解析一个 HTML 字符串,然后通过上面的扩展更改字体和颜色。到目前为止没有问题。

【讨论】:

  • f.fontDescriptor.withFamily(font.familyName).withSymbolicTraits(f.fontDescriptor.symbolicTraits) 与 rmaddy 的代码不同。比较他的编辑历史中的代码。
  • @Fattie 哦,我有一段时间默默退出,完全错过了你的 cmets,对不起!感谢您的赏金 :) 同时,我已将上述 sn-p 作为生产应用程序的一部分发布,所以是的,它确实有效。我将在上面的帖子中尝试并详细说明。
  • 理解@manmal 再次感谢。是的,真是一场噩梦!在某些应用程序中,您必须做 iOS 真正“不想做”的事情,这就是一个例子。
  • 我在这个实现中发现了一个小错误。如果字符串的任何部分没有字体属性,则不会被替换。所以你需要在内部if 块之后添加} else { addAttribute(.font, value: font, range: range) }。如果你需要,颜色也一样。
  • @JAHelia 如果您使用的是 ios 附带的字体,您可以在此处查看是否有可用的粗体:iosfonts.com
【解决方案2】:

这是一个更简单的实现,它保留所有属性,包括所有字体属性,但它允许您更改字体。

请注意,这仅使用传入字体的字体(名称)。大小与现有字体保持一致。如果您还想将所有现有字体大小更改为新大小,请将f.pointSize 更改为font.pointSize

extension NSMutableAttributedString {
    func replaceFont(with font: UIFont) {
        beginEditing()
        self.enumerateAttribute(.font, in: NSRange(location: 0, length: self.length)) { (value, range, stop) in
            if let f = value as? UIFont {
                let ufd = f.fontDescriptor.withFamily(font.familyName).withSymbolicTraits(f.fontDescriptor.symbolicTraits)!
                let newFont = UIFont(descriptor: ufd, size: f.pointSize)
                removeAttribute(.font, range: range)
                addAttribute(.font, value: newFont, range: range)
            }
        }
        endEditing()
    }
}

并使用它:

let someMutableAttributedString = ... // some attributed string with some font face you want to change
someMutableAttributedString.replaceFont(with: UIFont.systemFont(ofSize: 12))

【讨论】:

  • 伙计,我担心这实际上可能行不通。正如其他人在下面也报告的那样,存在一个带有粗体的问题。完全测试这些东西需要非常长的时间,不幸的是,我和我的快乐手下还没有机会完全调查它,但一些快速测试似乎失败了。 :O 涉及的因素太多了,这真是一场噩梦;可能是我们的测试搞砸了。 :O
  • 我真的可以发誓我用粗体字体测试了它,但确实它在 Xcode 9.2 的操场上不起作用。我已经更新了字体描述符的创建,它现在按预期工作。
  • @LinusGeffarth 是的,manmal 修复了我的答案(并添加了对颜色的支持)。我通过修复那一行代码更新了我的答案,使其适用于所有条件。
【解决方案3】:

我在 OSX/AppKit 上的两分钱(改变

extension NSAttributedString {
// replacing font to all:
func setFont(_ font: NSFont, range: NSRange? = nil)-> NSAttributedString {
    let mas = NSMutableAttributedString(attributedString: self)
    let range = range ?? NSMakeRange(0, self.length)
    mas.addAttributes([.font: font], range: range)
    return NSAttributedString(attributedString: mas)
}


// keeping foot, but changing size:
func setFont(size: CGFloat, range: NSRange? = nil)-> NSAttributedString {
    let mas = NSMutableAttributedString(attributedString: self)
    let range = range ?? NSMakeRange(0, self.length)


    mas.enumerateAttribute(.font, in: range) { value, range, stop in
        if let font = value as? NSFont {
            let name = font.fontName
            let newFont = NSFont(name: name, size: size)
            mas.addAttributes([.font: newFont!], range: range)
        }
    }
    return NSAttributedString(attributedString: mas)

}

【讨论】:

    【解决方案4】:

    重要 -

    rmaddy 发明了一种全新的技术来解决 iOS 中这个恼人的问题。

    manmal的答案是最终完善的版本。

    纯粹为了历史记录,这里大致是你过去的做法......


    // carefully convert to "our" font - "re-doing" any other formatting.
    // change each section BY HAND.  total PITA.
    
    func fixFontsInAttributedStringForUseInApp() {
    
        cachedAttributedString?.beginEditing()
    
        let rangeAll = NSRange(location: 0, length: cachedAttributedString!.length)
    
        var boldRanges: [NSRange] = []
        var italicRanges: [NSRange] = []
    
        var boldANDItalicRanges: [NSRange] = [] // WTF right ?!
    
        cachedAttributedString?.enumerateAttribute(
                NSFontAttributeName,
                in: rangeAll,
                options: .longestEffectiveRangeNotRequired)
                    { value, range, stop in
    
                    if let font = value as? UIFont {
    
                        let bb: Bool = font.fontDescriptor.symbolicTraits.contains(.traitBold)
                        let ii: Bool = font.fontDescriptor.symbolicTraits.contains(.traitItalic)
    
                        // you have to carefully handle the "both" case.........
    
                        if bb && ii {
    
                            boldANDItalicRanges.append(range)
                        }
    
                        if bb && !ii {
    
                            boldRanges.append(range)
                        }
    
                        if ii && !bb {
    
                            italicRanges.append(range)
                        }
                    }
                }
    
        cachedAttributedString!.setAttributes([NSFontAttributeName: font_f], range: rangeAll)
    
        for r in boldANDItalicRanges {
            cachedAttributedString!.addAttribute(NSFontAttributeName, value: font_fBOTH, range: r)
        }
    
        for r in boldRanges {
            cachedAttributedString!.addAttribute(NSFontAttributeName, value: font_fb, range: r)
        }
    
        for r in italicRanges {
            cachedAttributedString!.addAttribute(NSFontAttributeName, value: font_fi, range: r)
        }
    
        cachedAttributedString?.endEditing()
    }
    


    脚注。只是为了清楚地说明相关点。这种事情不可避免地从 HTML 字符串开始。这是关于如何将 html 字符串转换为 NSattributedString 的注释 .... 您最终会得到很好的属性范围(斜体、粗体等),但字体将是您不想要的字体。

    fileprivate extension String {
        func htmlAttributedString() -> NSAttributedString? {
            guard let data = self.data(using: String.Encoding.utf16, allowLossyConversion: false) else { return nil }
            guard let html = try? NSMutableAttributedString(
                data: data,
                options: [NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType],
                documentAttributes: nil) else { return nil }
            return html
        }
    }  
    

    即使这部分工作很重要,也需要一些时间来处理。在实践中,您必须将其设置为背景以避免闪烁。

    【讨论】:

    • 我刚刚看到你的脚注。正如我在回答中的(新写的)注释中提到的,我也在使用扩展来覆盖从 HTML 加载的文本的属性。我也在后台处理甚至缓存它,因为它太贵了。如果你需要这个,这里有一个要点:gist.github.com/manmal/c11ef79e306ffab25251ed911b3b0e80
    • 绝对是一个巨大的 PITA!
    【解决方案5】:

    @manmal 答案的 Obj-C 版本

    @implementation NSMutableAttributedString (Additions)
    
    - (void)setFontFaceWithFont:(UIFont *)font color:(UIColor *)color {
        [self beginEditing];
        [self enumerateAttribute:NSFontAttributeName
                         inRange:NSMakeRange(0, self.length)
                         options:0
                      usingBlock:^(id  _Nullable value, NSRange range, BOOL * _Nonnull stop) {
                          UIFont *oldFont = (UIFont *)value;
                          UIFontDescriptor *newFontDescriptor = [[oldFont.fontDescriptor fontDescriptorWithFamily:font.familyName] fontDescriptorWithSymbolicTraits:oldFont.fontDescriptor.symbolicTraits];
                          UIFont *newFont = [UIFont fontWithDescriptor:newFontDescriptor size:font.pointSize];
                          if (newFont) {
                              [self removeAttribute:NSFontAttributeName range:range];
                              [self addAttribute:NSFontAttributeName value:newFont range:range];
                          }
    
                          if (color) {
                              [self removeAttribute:NSForegroundColorAttributeName range:range];
                              [self addAttribute:NSForegroundColorAttributeName value:newFont range:range];
                          }
                      }];
        [self endEditing];
    }
    
    @end
    

    【讨论】:

      【解决方案6】:

      让 UITextField 完成这项工作是否有效?

      像这样,给定attributedStringnewfont

      let textField = UITextField()
      textField.attributedText = attributedString
      textField.font = newFont
      let resultAttributedString = textField.attributedText
      

      对不起,我错了,它保留了像 NSForegroundColorAttributeName 这样的“字符属性”,例如颜色,但不是 UIFontDescriptorSymbolicTraits,它描述粗体、斜体、浓缩等。

      那些属于字体而不是“字符属性”。所以如果你改变字体,你也在改变特征。对不起,但我提出的解决方案不起作用。目标字体需要具有所有可用的特征作为原始字体才能工作。

      【讨论】:

      • 我只尝试了一个颜色属性,它确实有效,所以希望其他的也能正常工作,没有时间完全测试它。
      • 对不起,我错了,它保留了像NSForegroundColorAttributeName这样的“字符属性”,例如颜色,但不是UIFontDescriptorSymbolicTraits,它描述了属于字体而不是“字符属性”的粗体、斜体、浓缩等。所以如果你改变字体,你也在改变特征。对不起,但我提出的解决方案不起作用。目标字体需要具有所有可用的特征作为原始字体才能工作。
      • 与其添加说明答案错误的编辑,不如删除答案(或修复它,如果可以的话)。
      猜你喜欢
      • 1970-01-01
      • 2013-11-15
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-10-10
      • 1970-01-01
      • 2019-02-19
      相关资源
      最近更新 更多