Say I have an NSMutableAttributedString
.
The string has a varied mix of formatting throughout:
Here is an example:
This string is hell to change in iOS, it really sucks.
However, the font per se is not the font you want.
I want to:
for each and every character, change that character to a specific font (say, Avenir)
for each and every character, keep the mix of other attributions (bold, italic, colors, etc etc) which was previously in place on that character.
How the hell do you do this?
Note:
if you trivially add an attribute "Avenir" over the whole range: it simply deletes all the other attribute ranges, you lose all formatting. Unfortunately, attributes are not, in fact "additive".
Since rmaddy's answer did not work for me (f.fontDescriptor.withFace(font.fontName)
does not keep traits like bold), here is an updated Swift 4 version that also includes color updating:
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() } }
The problem with f.fontDescriptor.withFace(font.fontName)
is that it removes symbolic traits like italic
, bold
or compressed
, since it will for some reason override those with default traits of that font face. Why this is so totally eludes me, it might even be an oversight on Apple's part; or it's "not a bug, but a feature", because we get the new font's traits for free.
So what we have to do is create a font descriptor that has the symbolic traits from the original font's font descriptor: .withSymbolicTraits(f.fontDescriptor.symbolicTraits)
. Props to rmaddy for the initial code on which I iterated.
I've already shipped this in a production app where we parse a HTML string via NSAttributedString.DocumentType.html
and then change the font and color via the extension above. No problems so far.
Here is a much simpler implementation that keeps all attributes in place, including all font attributes except it allows you to change the font face.
Note that this only makes use of the font face (name) of the passed in font. The size is kept from the existing font. If you want to also change all of the existing font sizes to the new size, change f.pointSize
to 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() } }
And to use it:
let someMutableAttributedString = ... // some attributed string with some font face you want to change someMutableAttributedString.replaceFont(with: UIFont.systemFont(ofSize: 12))
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With