Using SF Symbols in SwiftUI: the type-safe way

Every SF Symbol you put in a SwiftUI view goes through one initializer:

Image(systemName: "square.and.arrow.up")

That argument is a String. Not an enum, not a generated constant — a string literal you typed by hand or pasted from the SF Symbols app. SwiftUI will happily compile Image(systemName: "square.and.arow.up") and render nothing at runtime. No warning. No crash. Just a blank space where your share icon should be.

In a toy project that’s fine. In an app with a few hundred icons across dozens of screens, “is every one of these strings spelled correctly, today and after the next refactor” becomes a real, recurring maintenance cost.

Why strings are the actual problem

The issue isn’t that SF Symbols are bad — they’re great. The issue is the interface. A String parameter means:

This is the textbook definition of stringly-typed code: using strings where a real type belongs, and paying for it in bugs that the compiler should have caught.

The hand-rolled fix, and why it decays

The common senior-engineer response is a constants file:

enum Symbols {
    static let share = "square.and.arrow.up"
    static let settings = "gear"
    // ...however many your app touches
}

Image(systemName: Symbols.share)

This works — until it doesn’t. Someone adds a feature, needs a new icon, and now hand-maintains another constant. The file covers the symbols you’ve thought of; the other ~7,000 are still raw strings the moment you need one. And when Apple ships new symbols next OS cycle, your file silently falls behind. You’ve moved the maintenance burden, not removed it.

The type-safe way

SFSymbolsKit is that constants file — except generated from Apple’s entire catalog, kept current by regeneration, and shipped as a Swift Package:

import SwiftUI
import SFSymbolsKit

struct Toolbar: View {
    var body: some View {
        HStack {
            Image(systemName: String.SFSymbols.squareAndArrowUp)
            Image(systemName: String.SFSymbols.gear)
            Image(systemName: String.SFSymbols.trash)
        }
    }
}

Now String.SFSymbols. triggers autocomplete listing every symbol Apple ships. A typo is a compile error, not a blank icon in production. Rename safely, jump-to-definition, find-all-usages — everything the type system gives you for normal Swift, now applied to icons.

You can also skip the systemName: round-trip entirely with the Image you actually want:

Label("Share", systemImage: String.SFSymbols.squareAndArrowUp)

FAQ

Does this change how SwiftUI renders the symbol? No. String.SFSymbols.gear is the string "gear" — a typed binding to the same literal. SwiftUI does exactly what it did before; you just can’t misspell it.

What about symbol variants (.fill, .circle)? Those are distinct symbols with their own names (gearshape.fill, xmark.circle), so they’re distinct typed properties too. Same safety, all variants.

Is there a runtime cost? No. Static let bindings to string literals compile away to the literal at the call site.


This is the SwiftUI-specific version of a broader argument. For UIKit, see SF Symbols in UIKit: a practical guide; for macOS, SF Symbols in AppKit (NSImage); for the full picture across every Apple framework, the tutorial covers all three APIs.