NSAttributedString, change the font overall BUT keep all other attributes?
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()
}
}
Or, if your mix-of-attributes does not include font,
then you don't need to remove old font:
let myFont: UIFont = .systemFont(ofSize: UIFont.systemFontSize);
myAttributedText.addAttributes(
[NSAttributedString.Key.font: myFont],
range: NSRange(location: 0, length: myAttributedText.string.count));
Notes
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.
Change only fontsize of NSAttributedString
If you only want to change the size of any given font found in the attributed string then you can do:
let newStr = someAttributedString.mutableCopy() as! NSMutableAttributedString
newStr.beginEditing()
newStr.enumerateAttribute(.font, in: NSRange(location: 0, length: newStr.string.utf16.count)) { (value, range, stop) in
if let oldFont = value as? UIFont {
let newFont = oldFont.withSize(20) // whatever size you need
newStr.addAttribute(.font, value: newFont, range: range)
}
}
newStr.endEditing()
print(newStr)
This will keep all other attributes in place.
If you want to replace all fonts in a given attributed string with a single font of a given size but keep all other attributes such as bold and italic, see:
NSAttributedString, change the font overall BUT keep all other attributes?
Looping Through NSAttributedString Attributes to Increase Font SIze
Something like this should work:
NSMutableAttributedString *res = [self.richTextEditor.attributedText mutableCopy];
[res beginEditing];
__block BOOL found = NO;
[res enumerateAttribute:NSFontAttributeName inRange:NSMakeRange(0, res.length) options:0 usingBlock:^(id value, NSRange range, BOOL *stop) {
if (value) {
UIFont *oldFont = (UIFont *)value;
UIFont *newFont = [oldFont fontWithSize:oldFont.pointSize * 2];
[res removeAttribute:NSFontAttributeName range:range];
[res addAttribute:NSFontAttributeName value:newFont range:range];
found = YES;
}
}];
if (!found) {
// No font was found - do something else?
}
[res endEditing];
self.richTextEditor.attributedText = res;
At this point res
has a new attributed string with all fonts being twice their original size.
How to set font size on NSAttributedString
let myString = "Swift Attributed String"
let myAttribute = [ NSForegroundColorAttributeName: UIColor.blue ]
let myAttrString = NSAttributedString(string: myString, attributes: myAttribute)
// set attributed text on a UILabel
myLabel.attributedText = myAttrString
Font
let myAttribute = [ NSFontAttributeName: UIFont(name: "Chalkduster", size: 18.0)! ]
Shadow
let myShadow = NSShadow()
myShadow.shadowBlurRadius = 3
myShadow.shadowOffset = CGSize(width: 3, height: 3)
myShadow.shadowColor = UIColor.gray
let myAttribute = [ NSShadowAttributeName: myShadow ]
Underline
let myAttribute = [ NSUnderlineStyleAttributeName: NSUnderlineStyle.StyleSingle.rawValue ]
Textcolor
let myAttribute = [ NSForegroundColorAttributeName: UIColor.blue ]
Background Color
let myAttribute = [ NSBackgroundColorAttributeName: UIColor.yellow ]
NSAttributedString background color is not solid when other attributes are applied
There is an attribute called expansion
that adjusts the font's expansion factor using a float value. The problem only occurs where two ranges with different font sizes meet, and this is a graphical rendering issue that cannot be overridden. But if you explicitly zero out the expansion factor, or even apply a small negative value, the gaps will not be noticeable.
attributedString.addAttribute(.expansion, value: NSNumber(value: 0), range: nsText.range(of: "Hello + World!"))
https://developer.apple.com/documentation/foundation/nsattributedstring/key/1524652-expansion?language=objc__5
Related Topics
Non-'@Objc' Method Does Not Satisfy Optional Requirement of '@Objc' Protocol
Uncaught Error/Exception Handling in Swift
Using Decodable in Swift 4 With Inheritance
Formatting Input For Currency With Nsnumberformatter in Swift
Strange Swift Numbers Type Casting
How to Set Associated Objects in Swift
Opt Out of Uiscenedelegate/Swiftui on Ios
Swift - Resolving a Math Operation in a String
Firestore Search Array Contains For Multiple Values
Overriding Superclass Property With Different Type in Swift
How to Convert Unix Epoch Time to Date and Time in iOS Swift
How to Add Type Constraints to a Swift Protocol Conformance Extension