Swift NSAttributedString,整体更改字体,但保留所有其他属性?

Swift NSAttributedString,整体更改字体,但保留所有其他属性?,swift,nsattributedstring,Swift,Nsattributedstring,假设我有一个nsmutableAttributeString 字符串的格式有多种组合: 以下是一个例子: 这个字符串在iOS中很难更改,它真的很糟糕 但是,字体本身不是您想要的字体 我想: 对于每个字符,将该字符更改为特定字体(例如Avenir) 但是 对于每个字符,保留以前在该字符上使用的其他属性(粗体、斜体、颜色等)的组合。 你到底是怎么做到的 注: 如果在整个范围内简单地添加一个属性“Avenir”:它只需删除所有其他属性范围,就会丢失所有格式。不幸的是,属性不是,事实上是“相加的”。重

假设我有一个
nsmutableAttributeString

字符串的格式有多种组合:

以下是一个例子:

这个字符串在iOS中很难更改,它真的很糟糕

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

我想:

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

但是 对于每个字符,保留以前在该字符上使用的其他属性(粗体、斜体、颜色等)的组合。

你到底是怎么做到的


注:


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

重要-

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

曼马尔的答案是最终的完美版本

纯粹为了历史记录这里大致介绍了你在过去的日子里是如何做的



脚注。只是为了澄清一个相关的问题。这类事情不可避免地以HTML字符串开始。这里有一个关于如何将html字符串转换为
NSattributedString
。。。。您将得到很好的属性范围(斜体、粗体等),但字体将是您不想要的字体


即使工作的这一部分不是琐碎的,也需要一些时间来处理。实际上,您必须将其设置为背景,以避免闪烁。

这里有一个更简单的实现,它将所有属性保留在适当的位置,包括所有字体属性,但允许您更改字体

请注意,这仅使用传入字体的字体面(名称)。大小与现有字体保持一致。如果还想将所有现有字体大小更改为新大小,请将
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))

由于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)
的问题在于,它删除了诸如
italic
bold
compressed
等符号特征,因为它会出于某种原因覆盖该字体的默认特征。为什么我完全无法理解这一点,甚至可能是苹果方面的疏忽;或者它“不是一个bug,而是一个特性”,因为我们免费获得新字体的特性

因此,我们要做的是创建一个字体描述符,该描述符具有原始字体的字体描述符的符号特征:
。with symbolicTraits(f.fontDescriptor.symbolicTraits)
。rmaddy的道具,用于我迭代的初始代码


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

让UITextField来做这项工作是否有效

这样,给定
attributedString
newfont

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


抱歉,我错了,它保留了“字符属性”,如NSForegroundColorAttributeName,例如颜色,但没有UIFontDescriptorSymbolicTraits,它描述粗体、斜体、浓缩等


这些属于字体,而不是“字符属性”。因此,如果你改变字体,你也在改变特征。很抱歉,我建议的解决方案无效。目标字体需要有所有可用的特征作为原始字体才能使用。

Obj-C版本的@manmal的答案

@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

我的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)

}

要删除
NSFontAttributeName
。您还可以使用枚举并决定是否删除字体。为什么不直接从AttributeString中获取字符串,并使用所需字体创建一个新的AttributeString呢?您说删除所有属性。我看到您更改了问题,然后取消了对y的回答(&D)我们最初的问题,最后写下你自己的答案,并接受它作为解决方案。糟糕的表演。伙计,无意冒犯,但你的格式很糟糕。你应该删除一些字体属性-双关语。老兄,我担心这可能不会真的起作用。正如其他人在下面报告的那样,有一个问题w/bold。这需要花费非常长的时间才能完成很遗憾,我和我的快乐伙伴们还没有机会对此进行全面调查,但一些快速测试似乎失败了。:O涉及的因素太多了,这真是一场噩梦;可能是我们的测试搞砸了。:OI真的可以发誓,我用粗体字测试了它,但肯定没用在一个使用Xcode 9.2的操场上。我已经更新了字体描述符的创建,现在它正在按预期工作。@LinusGeffarth是的,manmal修复了我的答案(并添加了对颜色的支持)。我更新了我的答案,修复了那一行代码,使其在所有条件下都能工作。它是
f.fontDescriptor.withFamily(font.familyName)。withSymbolicTraits(f.fontDescriptor.symbolicTraits)
这与rmaddy的代码不同。请比较他编辑历史记录中的代码。@Fattie Oh我已悄悄注销了一段时间,完全错过了您的评论,抱歉!感谢您的赏金:)与此同时,我已经发布了上面的代码片段作为生产应用程序的一部分,因此是的,它确实有效。我将在上面的帖子中尝试详细阐述一下。再次理解@manmal谢谢。是的,这真是一场噩梦!在一些应用程序中,你必须做iOS真正“不想做”的事情,这就是一个例子。我发现
@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
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)

}