iOS
storyboard
attributed string
custom fonts
troubleshooting

Attributed string with custom fonts in storyboard does not load correctly

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

Introduction

Custom fonts set on attributed strings in Interface Builder (Storyboard) often revert to the system font at runtime. This happens because Xcode serializes attributed strings with a font descriptor that may not match the installed custom font at load time. The fix involves setting the attributed string in code rather than in the Storyboard, or ensuring the font is properly registered in Info.plist with the correct PostScript name. This is a long-standing UIKit behavior, not a bug in your code.

The Problem

swift
// In Storyboard: UILabel with attributed text using "Avenir-Heavy"
// At runtime: label shows system font (San Francisco) instead of Avenir-Heavy

Interface Builder serializes attributed strings into the Storyboard XML. When the view loads, UIKit deserializes the attributed string and attempts to resolve the font. If the font name in the serialized data does not exactly match the installed font's PostScript name, UIKit falls back to the system font silently.

Fix 1: Set Attributed String in Code

swift
1class ViewController: UIViewController {
2    @IBOutlet weak var titleLabel: UILabel!
3
4    override func viewDidLoad() {
5        super.viewDidLoad()
6
7        let attributes: [NSAttributedString.Key: Any] = [
8            .font: UIFont(name: "Avenir-Heavy", size: 24)!,
9            .foregroundColor: UIColor.label,
10            .kern: 1.5,
11        ]
12
13        titleLabel.attributedText = NSAttributedString(
14            string: "Hello World",
15            attributes: attributes
16        )
17    }
18}

Setting the attributed string in code is the most reliable fix. It bypasses the Storyboard serialization/deserialization issue entirely.

Fix 2: Register Custom Fonts Correctly

xml
1<!-- Info.plist — add font files -->
2<key>UIAppFonts</key>
3<array>
4    <string>MyCustomFont-Regular.ttf</string>
5    <string>MyCustomFont-Bold.ttf</string>
6</array>
swift
1// Verify the font is available at runtime
2for family in UIFont.familyNames.sorted() {
3    for name in UIFont.fontNames(forFamilyName: family) {
4        print("  \(name)")
5    }
6}
7// Look for your font's PostScript name in the output
8// Use that exact name in UIFont(name:size:)

The font file must be:

  1. Added to the Xcode project (drag into the project navigator)
  2. Included in the target's "Copy Bundle Resources" build phase
  3. Listed in Info.plist under UIAppFonts (key: "Fonts provided by application")

Fix 3: Use the Correct PostScript Name

swift
1// The file name and the font name are often different
2// File: "MyFont-Bold.otf"
3// PostScript name: "MyFont-Bold" or "MyFontBold" — check with the print loop above
4
5// WRONG — using the file name
6let font = UIFont(name: "MyFont-Bold.otf", size: 16)  // Returns nil
7
8// CORRECT — using the PostScript name
9let font = UIFont(name: "MyFont-Bold", size: 16)  // Works

The font name used in code and Storyboard must be the PostScript name, not the file name. Open the font in Font Book (macOS) and check the "PostScript Name" field.

Fix 4: NSAttributedString with Multiple Styles

swift
1func styledText() -> NSAttributedString {
2    let result = NSMutableAttributedString()
3
4    let titleAttrs: [NSAttributedString.Key: Any] = [
5        .font: UIFont(name: "Avenir-Heavy", size: 20) ?? .boldSystemFont(ofSize: 20),
6        .foregroundColor: UIColor.label,
7    ]
8
9    let bodyAttrs: [NSAttributedString.Key: Any] = [
10        .font: UIFont(name: "Avenir-Book", size: 16) ?? .systemFont(ofSize: 16),
11        .foregroundColor: UIColor.secondaryLabel,
12    ]
13
14    result.append(NSAttributedString(string: "Title\n", attributes: titleAttrs))
15    result.append(NSAttributedString(string: "Body text goes here.", attributes: bodyAttrs))
16
17    return result
18}
19
20// Usage
21titleLabel.attributedText = styledText()

Provide a fallback font with ?? in case the custom font fails to load. This prevents invisible text.

Fix 5: IBDesignable Preview

swift
1@IBDesignable
2class StyledLabel: UILabel {
3    @IBInspectable var fontName: String = "Avenir-Heavy"
4    @IBInspectable var fontSize: CGFloat = 16
5
6    override func awakeFromNib() {
7        super.awakeFromNib()
8        applyStyle()
9    }
10
11    override func prepareForInterfaceBuilder() {
12        super.prepareForInterfaceBuilder()
13        applyStyle()
14    }
15
16    private func applyStyle() {
17        if let customFont = UIFont(name: fontName, size: fontSize) {
18            font = customFont
19        }
20    }
21}

An @IBDesignable subclass applies the custom font both at runtime and in Interface Builder, giving you a visual preview without the serialization bug.

SwiftUI Alternative

swift
1import SwiftUI
2
3struct StyledText: View {
4    var body: some View {
5        Text("Hello World")
6            .font(.custom("Avenir-Heavy", size: 24))
7            .foregroundColor(.primary)
8
9        // With attributed string (iOS 15+)
10        Text(attributedString)
11    }
12
13    var attributedString: AttributedString {
14        var result = AttributedString("Hello ")
15        result.font = .custom("Avenir-Heavy", size: 24)
16
17        var world = AttributedString("World")
18        world.font = .custom("Avenir-Book", size: 24)
19        world.foregroundColor = .secondary
20
21        result.append(world)
22        return result
23    }
24}

SwiftUI's Font.custom() resolves fonts at runtime without Storyboard serialization issues.

Common Pitfalls

  • Font file not in Copy Bundle Resources: Adding the font file to the project is not enough. Verify it appears in Build Phases > Copy Bundle Resources. Missing entries cause UIFont(name:size:) to return nil.
  • Wrong font name in Info.plist: The UIAppFonts array must list the exact file names (e.g., "MyFont-Bold.ttf"), not the PostScript names. One is for registration, the other is for usage in code.
  • Storyboard attributed strings silently falling back: When the font fails to load, UIKit substitutes the system font without logging a warning. Always test custom fonts on a real device, not just in the Storyboard preview.
  • Font caching in Simulator: The iOS Simulator caches fonts aggressively. After adding a new font, clean the build folder (Cmd+Shift+K) and delete the app from the Simulator to force a fresh install.
  • Using Display Name instead of PostScript Name: Font Book shows both "Display Name" (e.g., "Avenir Heavy") and "PostScript Name" (e.g., "Avenir-Heavy"). UIKit requires the PostScript name with exact casing.

Summary

  • Custom fonts in Storyboard attributed strings often revert to the system font at runtime
  • Set attributed strings in code with UIFont(name:size:) for the most reliable approach
  • Register fonts in Info.plist (UIAppFonts) and include them in Copy Bundle Resources
  • Use the PostScript name (not file name or display name) when referencing fonts in code
  • Print all available fonts at runtime to verify your custom font is loaded correctly
  • Always provide a fallback font with ?? to prevent invisible text if the custom font fails

Related reading
Free course
Beginner
7 lessons
2 hours
Tackling System Design Interview Problems

A short course that equips you with the skills to approach system design interviews methodically.

Start the free course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions