Tim Sanders
Tim Sanders

Reputation: 851

Add custom weight to iOS Font descriptor in Swift

This seems like it should be rudimentarily easy, but for whatever reason it's not working. I've read similar posts on SO and it seems like there might be an issue with the Font Traits dictionary? Is this an issue or am I just completely missing something? This is my first time messing around with fonts like this so I think it might be the latter.

I don't want to use the constants or supply a font with it's "-bold" variant. I would like to have fine grain control over the font weight.

let traits = [UIFontWeightTrait : 1.0]
imgFontDescriptor = UIFontDescriptor(fontAttributes: [UIFontDescriptorNameAttribute: "Helvetica"])
imgFontDescriptor = imgFontDescriptor.fontDescriptorByAddingAttributes([UIFontDescriptorTraitsAttribute:traits])
imgTextFont = UIFont(descriptor: imgFontDescriptor!, size: 24.0)

Research:

According to the iOS docs using a number value should work (I'm running iOS 9.2):

UIFontWeightTrait

The normalized weight value as an NSNumber object. The valid value range is from -1.0 to 1.0. The value of 0.0 corresponds to the regular or medium font weight. You can also use a font weight constant to specify a particular weight; for a list of constants you can use, see Font Weights. Available in iOS 7.0 and later.

Upvotes: 19

Views: 23310

Answers (6)

Honghao Z
Honghao Z

Reputation: 1537

A concise extension:

extension UIFont {

  /// Returns a new font with the weight specified.
  /// - Parameter weight: The new font weight
  func withWeight(_ weight: UIFont.Weight) -> UIFont {
    let newDescriptor = fontDescriptor.addingAttributes([.traits: [
      UIFontDescriptor.TraitKey.weight: weight]
    ])
    return UIFont(descriptor: newDescriptor, size: pointSize)
  }
}

Upvotes: 9

Ahmed M. Hassan
Ahmed M. Hassan

Reputation: 1286

Based on the Answers by. @pkamb and @Honghao Zhang

You can update font weight only using the following extension

public extension UIFont {
    /// Returns a new font with the weight specified
    ///
    /// - Parameter weight: The new font weight
    func fontWeight(_ weight: UIFont.Weight) -> UIFont {
        let fontDescriptor = UIFontDescriptor(fontAttributes: [
            UIFontDescriptor.AttributeName.size: pointSize,
            UIFontDescriptor.AttributeName.family: familyName
        ])

        // Add the font weight to the descriptor
        let weightedFontDescriptor = fontDescriptor.addingAttributes([
            UIFontDescriptor.AttributeName.traits: [
                UIFontDescriptor.TraitKey.weight: weight
            ]
        ])
        return UIFont(descriptor: weightedFontDescriptor, size: 0)
    }
}

Can be used by

UIFont.body.fontWeight(.light)

This is to match the new SwiftUI APIs naming https://developer.apple.com/documentation/swiftui/text/fontweight(_:)

Upvotes: 2

Huy Le
Huy Le

Reputation: 2513

UIFont(descriptor: imgFontDescriptor!, size: 24.0) is returning a Font what match with the descriptor. If it can't find a Font match with your description, it returns a default font. Therefore, you can't control your weight manually. It depends on the Font you use. If the Font is support that weight, it will return that.

One more thing, you should use [UIFontDescriptorFamilyAttribute: "Helvetica"]. So it will determine FontName base on FamilyName & your FontWeight.

The correct way is use the constant from Apple lib:

public let UIFontWeightUltraLight: CGFloat
@available(iOS 8.2, *)
public let UIFontWeightThin: CGFloat
@available(iOS 8.2, *)
public let UIFontWeightLight: CGFloat
@available(iOS 8.2, *)
public let UIFontWeightRegular: CGFloat
@available(iOS 8.2, *)
public let UIFontWeightMedium: CGFloat
@available(iOS 8.2, *)
public let UIFontWeightSemibold: CGFloat
@available(iOS 8.2, *)
public let UIFontWeightBold: CGFloat
@available(iOS 8.2, *)
public let UIFontWeightHeavy: CGFloat
@available(iOS 8.2, *)
public let UIFontWeightBlack: CGFloat*/

You should use http://iosfonts.com/ to determine which font weight that family name is supporting.

In case of Helvetica:

let traits = [UIFontWeightTrait: UIFontWeightLight] // UIFontWeightBold / UIFontWeightRegular
let imgFontDescriptor = UIFontDescriptor(fontAttributes: [UIFontDescriptorFamilyAttribute: "Helvetica"])
imgFontDescriptor = imgFontDescriptor.fontDescriptorByAddingAttributes([UIFontDescriptorTraitsAttribute: traits])

Upvotes: 16

pkamb
pkamb

Reputation: 35062

Here is Apple's own extension from UIFont+Extensions.swift in their CareKit framework:

https://github.com/carekit-apple/CareKit/

extension UIFont {
    static func preferredCustomFont(forTextStyle textStyle: TextStyle, weight: Weight) -> UIFont {
        let defaultDescriptor = UIFontDescriptor.preferredFontDescriptor(withTextStyle: textStyle)
        let size = defaultDescriptor.pointSize
        let fontDescriptor = UIFontDescriptor(fontAttributes: [
            UIFontDescriptor.AttributeName.size: size,
            UIFontDescriptor.AttributeName.family: UIFont.systemFont(ofSize: size).familyName
        ])

        // Add the font weight to the descriptor
        let weightedFontDescriptor = fontDescriptor.addingAttributes([
            UIFontDescriptor.AttributeName.traits: [
                UIFontDescriptor.TraitKey.weight: weight
            ]
        ])
        return UIFont(descriptor: weightedFontDescriptor, size: 0)
    }
}

Upvotes: 6

Ely
Ely

Reputation: 9141

With Swift 4.1, simply do something like this:

var descriptor = UIFontDescriptor(name: "Helvetica Neue", size: 24.0)
descriptor = descriptor.addingAttributes([UIFontDescriptor.AttributeName.traits : [UIFontDescriptor.TraitKey.weight : UIFont.Weight.light]])
let font = UIFont(descriptor: descriptor, size: 24.0)

Upvotes: 14

Andrii
Andrii

Reputation: 551

If you use existing fontDescriptor (for example from existing font), it may not working, because of explicit '.name' attribute in fontDescriptor.fontAttributes.

Solution, that works for me (Swift 4):

extension UIFont {
    var bold: UIFont { return withWeight(.bold) }
    var semibold: UIFont { return withWeight(.semibold) }

    private func withWeight(_ weight: UIFont.Weight) -> UIFont {
        var attributes = fontDescriptor.fontAttributes
        var traits = (attributes[.traits] as? [UIFontDescriptor.TraitKey: Any]) ?? [:]

        traits[.weight] = weight

        attributes[.name] = nil
        attributes[.traits] = traits
        attributes[.family] = familyName

        let descriptor = UIFontDescriptor(fontAttributes: attributes)

        return UIFont(descriptor: descriptor, size: pointSize)
    }
}

Usage:

let baseFont = UIFont(name: "American Typewriter", size: 20)!
let boldFont = baseFont.bold
let semibold = baseFont.semibold

Upvotes: 29

Related Questions