NSAttributedString,整体更改字体但保留所有其他属性?
NSAttributedString, change the font overall BUT keep all other attributes?
假设我有一个 NSMutableAttributedString
.
该字符串具有 多种混合 格式:
这是一个例子:
This string is hell to change in iOS, it really sucks.
但是,字体本身并不是你想要的字体。
我想:
对于每个字符,将该字符更改为特定的字体(例如,Avenir)
但是,
对于每个角色,保持其他的混合 ] 属性(粗体、斜体、颜色等),该属性以前存在于该字符上。
你到底是怎么做到的?
注:
如果您在整个范围内简单地添加一个属性“Avenir”:它只会删除所有其他属性范围,您将丢失所有格式。不幸的是,属性 不是 ,实际上是“附加的”。
重要-
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
}
}
。
即使这部分工作很重要,也需要一些时间来处理。在实践中,你必须将它设置为背景以避免闪烁。
这是一个更简单的实现,它保留所有属性,包括所有字体属性,但它允许您更改字体。
请注意,这仅使用传入字体的字体(名称)。大小与现有字体保持一致。如果您还想将所有现有字体大小更改为新大小,请将 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
,因为它会出于某种原因覆盖具有默认特征的那些那个字体。为什么这完全让我难以理解,甚至可能是苹果公司的疏忽;或者是 "not a bug, but a feature",因为我们免费获得了新字体的特征。
所以我们要做的是创建一个字体描述符,它具有原始字体的字体描述符的符号特征:.withSymbolicTraits(f.fontDescriptor.symbolicTraits)
。为我迭代的初始代码向 rmaddy 提供支持。
我已经在生产应用程序中发布了这个,我们通过 NSAttributedString.DocumentType.html
解析 HTML 字符串,然后通过上面的扩展更改字体和颜色。目前没有问题。
让 UITextField 完成工作是否有效?
像这样,给定 attributedString
和 newfont
:
let textField = UITextField()
textField.attributedText = attributedString
textField.font = newFont
let resultAttributedString = textField.attributedText
对不起,我错了,它保留了 "Character Attributes" 就像 NSForegroundColorAttributeName,例如颜色,但不是 UIFontDescriptorSymbolicTraits,它描述粗体、斜体、压缩等。
那些属于字体而不是 "Character Attributes"。所以如果你改变字体,你也在改变特征。抱歉,我提出的解决方案不起作用。目标字体需要具有与原始字体一样可用的所有特征才能正常工作。
@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
我 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)
}
假设我有一个 NSMutableAttributedString
.
该字符串具有 多种混合 格式:
这是一个例子:
This string is hell to change in iOS, it really sucks.
但是,字体本身并不是你想要的字体。
我想:
对于每个字符,将该字符更改为特定的字体(例如,Avenir)
但是,
对于每个角色,保持其他的混合 ] 属性(粗体、斜体、颜色等),该属性以前存在于该字符上。
你到底是怎么做到的?
注:
如果您在整个范围内简单地添加一个属性“Avenir”:它只会删除所有其他属性范围,您将丢失所有格式。不幸的是,属性 不是 ,实际上是“附加的”。
重要-
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
}
}
。
即使这部分工作很重要,也需要一些时间来处理。在实践中,你必须将它设置为背景以避免闪烁。
这是一个更简单的实现,它保留所有属性,包括所有字体属性,但它允许您更改字体。
请注意,这仅使用传入字体的字体(名称)。大小与现有字体保持一致。如果您还想将所有现有字体大小更改为新大小,请将 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
,因为它会出于某种原因覆盖具有默认特征的那些那个字体。为什么这完全让我难以理解,甚至可能是苹果公司的疏忽;或者是 "not a bug, but a feature",因为我们免费获得了新字体的特征。
所以我们要做的是创建一个字体描述符,它具有原始字体的字体描述符的符号特征:.withSymbolicTraits(f.fontDescriptor.symbolicTraits)
。为我迭代的初始代码向 rmaddy 提供支持。
我已经在生产应用程序中发布了这个,我们通过 NSAttributedString.DocumentType.html
解析 HTML 字符串,然后通过上面的扩展更改字体和颜色。目前没有问题。
让 UITextField 完成工作是否有效?
像这样,给定 attributedString
和 newfont
:
let textField = UITextField()
textField.attributedText = attributedString
textField.font = newFont
let resultAttributedString = textField.attributedText
对不起,我错了,它保留了 "Character Attributes" 就像 NSForegroundColorAttributeName,例如颜色,但不是 UIFontDescriptorSymbolicTraits,它描述粗体、斜体、压缩等。
那些属于字体而不是 "Character Attributes"。所以如果你改变字体,你也在改变特征。抱歉,我提出的解决方案不起作用。目标字体需要具有与原始字体一样可用的所有特征才能正常工作。
@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
我 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)
}