Reputation: 3640
I've been trying to apply combinations of NSFontAttributes to NSMutableAttributedString's lately and I simply can't find a thorough explanation on how to do it without removing other attributes.
I've searched a bunch, and found this question pertaining to how to do it with HTML, and then this question about how to find where text has been bolded or italicized, but nothing on how to actually do it.
Currently, I try to format stings as follows:
Italics:
[mutableAttributedString addAttribute: NSFontAttributeName value:[fontAttributes valueForKey:CXItalicsFontAttributeName] range:r];
Bold: [mutableAttributedString addAttribute:NSFontAttributeName value:[fontAttributes valueForKey:CXBoldFontAttributeName] range:r];
Where the constants CXItalicsFontAttributeName
and CXBoldAttributeName
extract the following two values from a dictionary respectfully:
UIFont *italicsFont = [UIFont fontWithName:@"Avenir-BookOblique" size:14.0f];
UIFont *boldFont = [UIFont fontWithName:@"Avenir-Heavy" size:14.0f];
I know this mustn't be the right way to go about formatting, as the NSAttributedString standard attributes don't include a ItalicsFontAttribute or BoldFontAttribute, but I can't find the properly way to do this. Can anyone assist me?
Upvotes: 29
Views: 31955
Reputation: 1848
Not a direct answer to the question, per-se, but SwiftUI, as of iOS 15, supports Markdown natively in Text views. This may suffice if your need is only emboldening or italicising a portion of a string (and you're using SwiftUI, of course!) e.g.
VStack {
Text("This is regular text.")
Text("* This is **bold** text, this is *italic* text, and this is ***bold, italic*** text.")
Text("~~A strikethrough example~~")
Text("`Monospaced works too`")
Text("Visit Apple: [click here](https://apple.com)")
}
(The example is lifted straight from hackingwithswift.com)
Upvotes: 1
Reputation: 2542
In Swift and using an extension:
extension UIFont {
func withTraits(_ traits: UIFontDescriptor.SymbolicTraits) -> UIFont {
// create a new font descriptor with the given traits
guard let fd = fontDescriptor.withSymbolicTraits(traits) else {
// the given traits couldn't be applied, return self
return self
}
// return a new font with the created font descriptor
return UIFont(descriptor: fd, size: pointSize)
}
func italics() -> UIFont {
return withTraits(.traitItalic)
}
func bold() -> UIFont {
return withTraits(.traitBold)
}
func boldItalics() -> UIFont {
return withTraits([ .traitBold, .traitItalic ])
}
}
Example:
if let font = UIFont(name: "Avenir", size: 30) {
let s = NSAttributedString(string: "Hello World!", attributes: [ NSFontAttributeName: font.italic() ])
let t = NSAttributedString(string: "Hello World!", attributes: [ NSFontAttributeName: font.boldItalic()) ])
}
Upvotes: 41
Reputation: 25294
extension UIFont {
class func systemFont(ofSize fontSize: CGFloat, symbolicTraits: UIFontDescriptor.SymbolicTraits) -> UIFont? {
return UIFont.systemFont(ofSize: fontSize).including(symbolicTraits: symbolicTraits)
}
func including(symbolicTraits: UIFontDescriptor.SymbolicTraits) -> UIFont? {
var _symbolicTraits = self.fontDescriptor.symbolicTraits
_symbolicTraits.update(with: symbolicTraits)
return withOnly(symbolicTraits: _symbolicTraits)
}
func excluding(symbolicTraits: UIFontDescriptor.SymbolicTraits) -> UIFont? {
var _symbolicTraits = self.fontDescriptor.symbolicTraits
_symbolicTraits.remove(symbolicTraits)
return withOnly(symbolicTraits: _symbolicTraits)
}
func withOnly(symbolicTraits: UIFontDescriptor.SymbolicTraits) -> UIFont? {
guard let fontDescriptor = fontDescriptor.withSymbolicTraits(symbolicTraits) else { return nil }
return .init(descriptor: fontDescriptor, size: pointSize)
}
}
font = UIFont.italicSystemFont(ofSize: 15).including(symbolicTraits: .traitBold)
font = UIFont.systemFont(ofSize: 15, symbolicTraits: [.traitBold, .traitItalic])
font = font.excluding(symbolicTraits: [.traitBold]
font = font.withOnly(symbolicTraits: [])
Do not forget to paste the solution code here.
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
addLabel(origin: .init(x: 20, y: 20), font: .systemFont(ofSize: 15, symbolicTraits: [.traitBold, .traitItalic]))
addLabel(origin: .init(x: 20, y: 40), font: UIFont.italicSystemFont(ofSize: 15).including(symbolicTraits: .traitBold))
guard let font = UIFont.systemFont(ofSize: 15, symbolicTraits: [.traitBold, .traitItalic]) else { return }
addLabel(origin: .init(x: 20, y: 60), font: font.excluding(symbolicTraits: [.traitBold]))
addLabel(origin: .init(x: 20, y: 80), font: font.withOnly(symbolicTraits: []))
}
private func addLabel(origin: CGPoint, font: UIFont?) {
guard let font = font else { return }
let label = UILabel(frame: .init(origin: origin, size: .init(width: 200, height: 40)))
label.attributedText = NSAttributedString(string: "Hello World!", attributes: [.font: font, .foregroundColor: UIColor.black ])
view.addSubview(label)
}
}
Upvotes: 4
Reputation: 423
Checkout NSAttributedString.Key.obliqueness
. Setting the value of this key to values other than 0 make the text italics with different degree.
Upvotes: 6
Reputation: 3640
The best solution to my problem so far has been to make use of the UIFontDescriptor
class to provide me with the necessary UIFont
for each case I encounter.
For instance, as I want to use Avenir-Book
as my primary font with size 14
, I can create a UIFontDescriptor
as follows:
UIFontDescriptor *fontDescriptor = [UIFontDescriptor fontDescriptorWithName:@"Avenir-Book" size:14.0f];
Next, if I wish to obtain the Italicized, Bolded, or combination of both, I have simple to do as follows:
NSString *normalFont = [[fontDescriptor fontAttributes]valueForKey:UIFontDescriptorNameAttribute];
NSString *italicsFont = [[[fontDescriptor fontDescriptorWithSymbolicTraits:UIFontDescriptorTraitItalic]fontAttributes]valueForKey:UIFontDescriptorNameAttribute];
NSString *boldFont = [[[fontDescriptor fontDescriptorWithSymbolicTraits:UIFontDescriptorTraitBold]fontAttributes]valueForKey:UIFontDescriptorNameAttribute];
NSString *boldAndItalicsFont = [[[fontDescriptor fontDescriptorWithSymbolicTraits:UIFontDescriptorTraitBold | UIFontDescriptorTraitItalic]fontAttributes]valueForKey:UIFontDescriptorNameAttribute];
And this indeed produces the required fonts when printed:
NSLog(@"Normal Font: %@ Size: %@\n",normalFont,[[fontDescriptor fontAttributes]valueForKey:UIFontDescriptorSizeAttribute]);
NSLog(@"Italics Font: %@\n",italicsFont);
NSLog(@"Bold Font: %@\n",boldFont);
NSLog(@"Bold and Italics Font: %@\n",boldAndItalicsFont);
Output:
Normal Font: Avenir-Book Size: 14
Italics Font: Avenir-BookOblique
Bold Font: Avenir-Black
Bold and Italics Font: Avenir-BlackOblique
The advantage here is that I no longer need to create the individual font types myself, and a font from the family is sufficient to do so.
Upvotes: 8
Reputation:
If you're applying each (bold or italic) trait individually, you need to make sure that bold and italic ranges don't overlap or one trait will overwrite the other.
The only way to apply both bold and italic traits to a range is to use a font which is both bold and italic, and apply the both traits at once.
let str = "Normal Bold Italics BoldItalics"
let font = UIFont(name: "Avenir", size: 14.0)!
let italicsFont = UIFont(name: "Avenir-BookOblique", size: 14.0)!
let boldFont = UIFont(name: "Avenir-Heavy", size: 14.0)!
let boldItalicsFont = UIFont(name: "Avenir-HeavyOblique", size: 14.0)!
let attributedString = NSMutableAttributedString(string: str, attributes: [NSFontAttributeName : font])
attributedString.addAttribute(NSFontAttributeName, value: boldFont, range: NSMakeRange(7, 4))
attributedString.addAttribute(NSFontAttributeName, value: italicsFont, range: NSMakeRange(12, 7))
attributedString.addAttribute(NSFontAttributeName, value: boldItalicsFont, range: NSMakeRange(20, 11))
Upvotes: 15