Typed vs stringly-typed SF Symbols in Swift
If you’ve decided hand-typed symbol strings are a liability (they are — that’s the rest of this blog), the next question is which typed approach to use. There are a few, they’re not equivalent, and the differences are about maintenance, not syntax.
The options
1. Raw strings. The default. Image(systemName: "gear"). No autocomplete, no compile check, silent runtime failure. This is the baseline everything else improves on.
2. A hand-rolled enum. You write enum Symbols { static let gear = "gear" }. Typed at the call site, but you maintain the list by hand. It covers what you remembered; it drifts behind Apple’s releases; new symbols need manual entry. Moves the maintenance cost, doesn’t remove it.
3. SFSafeSymbols. A well-established, well-maintained package that gives you a generated enum of symbol names with availability annotations. It solves the core problem — no more raw strings — and it’s a solid, popular choice. Credit where due: it’s been doing this longer than most.
4. SFSymbolsKit. Also generated from Apple’s full catalog, with a slightly different surface area: typed String, ready UIImage, and ready NSImage accessors plus a CaseIterable enum — the goal being one package that covers the name and the image on every Apple framework.
What actually differs
The raw-string and hand-rolled options fail for reasons covered elsewhere on this blog. Between the two generated approaches (SFSafeSymbols and SFSymbolsKit), the honest differences are surface area and ergonomics, not “one is safe and one isn’t” — both eliminate the stringly-typed failure mode:
// Hand-rolled enum — you maintain this list forever Image(systemName: MySymbols.gear) // SFSafeSymbols — generated, enum-based Image(systemSymbol: .gear) // SFSymbolsKit — generated; typed String + UIImage + NSImage Image(systemName: String.SFSymbols.gear) let ui = UIImage.SFSymbols.gear let ns = NSImage.SFSymbols.gear
How to choose:
- You want the longest track record and a large community already using it: SFSafeSymbols is a great, safe pick.
- You want one dependency that hands you the typed name and a configured
UIImage/NSImageacross UIKit and AppKit, plusCaseIterableenumeration: that’s the surface SFSymbolsKit aims at. - You’re tempted to hand-roll your own enum: don’t. Both generated packages exist precisely so you don’t maintain a 7,000-entry list by hand. That’s the option with the real long-term cost.
The point that matters more than the comparison
Whichever generated package you pick, the win is the same and it’s large: the symbol catalog becomes typed data in your program instead of opaque strings. Autocomplete works. Typos are compile errors. Refactors are safe. Enumeration is possible. The choice between SFSafeSymbols and SFSymbolsKit is real but secondary — the choice that actually costs you is staying on raw strings or hand-maintained lists.
FAQ
Can I migrate between typed packages later? Yes — both replace raw strings with typed references, so moving from one to the other is mechanical (and far easier than the original raw-string mess). The hard, valuable migration is getting off raw strings in the first place; see migrating off hardcoded SF Symbol strings.
Is a hand-rolled enum ever the right call? For a toy app with five icons, sure. At any real scale, you’re signing up to manually track Apple’s catalog forever — the exact chore generated packages remove.
Does adding a package for this bloat my app? Generated symbol packages are static name data; there’s no runtime engine. The footprint is negligible relative to the class of bugs removed.
This is the “which fix” question; the rest of the blog is the “why you need a fix at all” argument — SwiftUI, UIKit, AppKit, and the full tutorial.