7

I have a number of UILabels created in IB which all have attributed text. Each label's text contains multiple lines of different font sizes and colors.

At run-time, I want to be able to change just the font name of these labels without changing the existing font sizes or colors.

I have researched and could not find a straight forward method to achieve this. Any ideas?

Kashif
  • 4,642
  • 7
  • 44
  • 97
  • Possible duplicate of [ios swift: Is it possible to change the font style of a certain word in a string?](http://stackoverflow.com/questions/29165560/ios-swift-is-it-possible-to-change-the-font-style-of-a-certain-word-in-a-string) –  Feb 18 '17 at 17:02
  • and http://stackoverflow.com/questions/18365631/example-of-nsattributedstring-with-two-different-font-sizes –  Feb 18 '17 at 17:03
  • @Sneak: These questions/answers do not seem to tackle the core issue of leaving the original attributes intact like font color, font size etc. – Kashif Feb 18 '17 at 17:09
  • Not sure what you mean by font color? You want to change the UIFont or the textColor? If you want to just change the UIFont name of the attributed text, just keep a reference to it in an NSDictionary and swap the UIFont object maybe using https://developer.apple.com/reference/foundation/nsattributedstring/1412461-enumerateattribute . I don't think there is any other solution to just change a name of an UIFont somewhere and let everything swap itself out. –  Feb 18 '17 at 17:17
  • Possible duplicate of [Swift- Change font on an HTML string that has its own Styles](http://stackoverflow.com/questions/41412963/swift-change-font-on-an-html-string-that-has-its-own-styles) – Larme Feb 18 '17 at 20:05

3 Answers3

11

You first need to understand the lingo Apple uses to describe a typeface:

  • Helvetica is a family
  • Helvetica Bold, Helvetica Italic, Helvetica Bold Italic, Helvetica Display etc. are faces
  • Helvetica Bold, 12pt is a font

What you want is to replace the font family of an attributed string.

Swift 4

// Enumerate through all the font ranges
newAttributedString.enumerateAttribute(.font, in: NSMakeRange(0, newAttributedString.length), options: []) { value, range, stop in
    guard let currentFont = value as? UIFont else {
        return
    }

    // An NSFontDescriptor describes the attributes of a font: family name,
    // face name, point size, etc. Here we describe the replacement font as
    // coming from the "Hoefler Text" family
    let fontDescriptor = currentFont.fontDescriptor.addingAttributes([.family: "Hoefler Text"])

    // Ask the OS for an actual font that most closely matches the description above
    if let newFontDescriptor = fontDescriptor.matchingFontDescriptors(withMandatoryKeys: [.family]).first {
        let newFont = UIFont(descriptor: newFontDescriptor, size: currentFont.pointSize)
        newAttributedString.addAttributes([.font: newFont], range: range)
    }
}

label.attributedText = newAttributedString

Swift 3

let newAttributedString = NSMutableAttributedString(attributedString: label.attributedText)

// Enumerate through all the font ranges
newAttributedString.enumerateAttribute(NSFontAttributeName, in: NSMakeRange(0, newAttributedString.length), options: []) { value, range, stop in
    guard let currentFont = value as? UIFont else {
        return
    }

    // An NSFontDescriptor describes the attributes of a font: family name,
    // face name, point size, etc. Here we describe the replacement font as
    // coming from the "Hoefler Text" family
    let fontDescriptor = currentFont.fontDescriptor.addingAttributes([UIFontDescriptorFamilyAttribute: "Hoefler Text"])

    // Ask the OS for an actual font that most closely matches the description above
    if let newFontDescriptor = fontDescriptor.matchingFontDescriptors(withMandatoryKeys: [UIFontDescriptorFamilyAttribute]).first {
        let newFont = UIFont(descriptor: newFontDescriptor, size: currentFont.pointSize)
        newAttributedString.addAttributes([NSFontAttributeName: newFont], range: range)
    }
}

label.attributedText = newAttributedString

Original (San Francisco):

San Francisco

Replacement (Hoefler Text):

Hoefler Text

Code Different
  • 90,614
  • 16
  • 144
  • 163
2

The above works great but with Swift4 and Xcode 9.1 I got number of warnings that method names had changed. Below is the result of applying all those warnings. Otherwise I did not change anything.

let newAttributedString = NSMutableAttributedString(attributedString: label.attributedText!)

// Enumerate through all the font ranges
newAttributedString.enumerateAttribute(NSAttributedStringKey.font, in: NSMakeRange(0, newAttributedString.length), options: []) 
{   
    value, range, stop in
    guard let currentFont = value as? UIFont else {
        return
    }

    // An NSFontDescriptor describes the attributes of a font: family name, face name, point size, etc.
    // Here we describe the replacement font as coming from the "Hoefler Text" family
    let fontDescriptor = currentFont.fontDescriptor.addingAttributes([UIFontDescriptor.AttributeName.family: "Hoefler Text"])

    // Ask the OS for an actual font that most closely matches the description above
    if let newFontDescriptor = fontDescriptor.matchingFontDescriptors(withMandatoryKeys: [UIFontDescriptor.AttributeName.family]).first {
        let newFont = UIFont(descriptor: newFontDescriptor, size: currentFont.pointSize)
        newAttributedString.addAttributes([NSAttributedStringKey.font: newFont], range: range)
    }
}

label.attributedText = newAttributedString
Barden
  • 1,020
  • 1
  • 10
  • 17
2

More simple and short way:

    guard let atributedText = text else { return }
    let newAttributedString = NSMutableAttributedString(attributedString: atributedText)
    let newFont = UIFont.boldSystemFont(ofSize: fontSize.value)
    newAttributedString.addAttributes([.font: newFont], range: NSRange(0..<textLength))
    
    valueLabel.attributedText = newAttributedString
ikakooo
  • 21
  • 1