Skip to content

System Icons, Multi-Color Icons, and SF Symbols in SwiftUI | SwiftUI Bootcamp #6

SF Symbols is Apple's library of over 5,000 vector icons that scale perfectly with text, support multiple rendering modes, and look native on every Apple platform. After this lesson you'll know how to find, display, and style SF Symbols — and when to use .original rendering mode to get multi-color icons for free.

What You'll Learn

  • How to use Image(systemName:) to display any SF Symbol
  • The three rendering modes: .template, .original, and .hierarchical/.multicolor (iOS 15+)
  • How to scale SF Symbol icons using .font() instead of .resizable()

Mental Model

Think of SF Symbols as scalable vector icons that speak the same size language as text. Instead of setting a pixel width, you describe their size relative to text: .font(.title) makes an icon the same size as a title-weight word, and .font(.system(size: 200)) makes it huge. This is fundamentally different from regular images where you call .resizable() first — SF Symbols are inherently resizable because they're vector glyphs rendered by the same engine as system fonts.

The rendering mode is like choosing between a tinted stamp (.template — you pick the color), a full-color stamp (.original — the symbol's built-in colors show through), or a layered stamp (.hierarchical — multiple layers each with their own opacity).

Detailed Explanation

SF Symbols are loaded with Image(systemName: "symbolName") where the string exactly matches a symbol name from the SF Symbols app (available free from Apple). The symbol name uses dot-notation like "person.fill.badge.plus" — each segment describes a layer of the icon.

By default, SF Symbols use .template rendering — the icon is treated as a single mask and filled with the current foreground color. This works great for monochromatic icons that should match your app's accent color.

.renderingMode(.original) tells SwiftUI to use the symbol's built-in multi-layer colors. Some symbols like "person.fill.badge.plus" have specially designed multi-color variants where each layer has its own color. These look great in contexts like notifications, settings cells, and badges.

On iOS 15+ you can use .symbolRenderingMode(.hierarchical) for a single-color layered look where primary layers are full opacity and secondary layers are reduced, or .symbolRenderingMode(.multicolor) to enable the symbol's palette colors explicitly. The symbolVariant modifier applies stylistic variants like .fill, .circle, .slash without changing the symbol name.

Scaling SF Symbols: use .font(.system(size: N)) to control size — not .resizable(). .resizable() is for bitmap images (Image("name")). Calling it on an SF Symbol typically causes layout issues.

Code Structure

The sample is in IconsBootcamp.swift and shows a person.fill.badge.plus symbol with .renderingMode(.original) enabled, scaled to .largeTitle size. The commented lines demonstrate resizing, scaling modes, custom colors, and frame clipping — all useful for different icon use cases.

Complete Code

IconsBootcamp.swift

swift
import SwiftUI

struct IconsBootcamp: View {
    var body: some View {
        Image(systemName: "person.fill.badge.plus") // SF Symbol: person with a plus badge (multi-color capable)
            .renderingMode(.original) // shows the symbol's built-in multi-color design instead of a flat tint
            .font(.largeTitle)        // scales the vector icon to match the largeTitle text size
            //.resizable()            // DO NOT use resizable() on SF Symbols — use .font() for sizing
            //.aspectRatio(contentMode: .fit)
            //.scaledToFit()
            //.scaledToFill()
            //.font(.caption)          // scales icon down to caption size
            //.font(.system(size: 200)) // explicit point size for precise control
            //.foregroundColor(Color(#colorLiteral(red: 0.9254902005, green: 0.2352941185, blue: 0.1019607857, alpha: 1))) // custom tint (only works in .template mode)
            //.frame(width: 300, height: 300)
            //.clipped() // clips anything outside the frame boundary
    }
}

struct IconsBootcamp_Previews: PreviewProvider {
    static var previews: some View {
        IconsBootcamp()
    }
}

Code Walkthrough

  1. Image(systemName: "person.fill.badge.plus") — Creates an SF Symbol image. The string must exactly match a symbol name. Use Apple's free SF Symbols app to browse all symbols and copy their exact names. Symbols are built into the OS, so your app bundle doesn't need to include them.
  2. .renderingMode(.original) — Switches from the default monochromatic tint mode to the symbol's built-in multi-color design. For "person.fill.badge.plus", this shows the human figure in one color and the plus badge in another. Remove this modifier to see the flat tinted version.
  3. .font(.largeTitle) — The idiomatic way to size SF Symbols. Because they're glyphs, the font system controls their size and weight. .largeTitle corresponds to ~34pt and looks good for prominent icons in headers or empty states.
  4. .font(.system(size: 200)) (commented out) — For large decorative icons, set a specific point size directly. This bypasses Dynamic Type scaling, which is acceptable for purely decorative icons but not for functional ones users rely on.
  5. .foregroundColor(...) in template mode — When renderingMode is .template (the default), .foregroundColor() controls the icon's single tint color. In .original mode, this modifier has no visible effect because the symbol's own colors take over.
  6. .clipped() — Clips the image to the bounds of its frame. Useful when a large icon overflows a fixed-size container.

Common Mistakes

Mistake: Calling .resizable() on an SF Symbol SF Symbols are vector glyphs rendered by the font engine — they don't use the same sizing model as bitmap images. Calling .resizable() on a symbol often produces unexpected layout behavior. Always use .font(.system(size:)) to control the size of Image(systemName:).

Mistake: Using .foregroundColor() with .renderingMode(.original) and wondering why it has no effect In .original rendering mode, the symbol uses its own built-in layer colors and ignores the foregroundColor. If you want to tint the entire icon a single color, either switch to the default .template mode or omit .renderingMode() entirely.

Mistake: Hardcoding a symbol name that doesn't exist on older OS versions SF Symbols are versioned. A symbol introduced in SF Symbols 3 (iOS 15) won't exist on iOS 14. Use the SF Symbols app to check the "Availability" column and ensure the minimum availability matches your app's deployment target. Provide a fallback view for older OS versions if needed.

Key Takeaways

  • Scale SF Symbols with .font(), not .resizable() — they're glyphs, not bitmap images
  • .renderingMode(.original) reveals the symbol's built-in multi-color design; the default .template mode uses a single foreground color
  • Check symbol availability in the SF Symbols app — symbols are versioned and not all exist on older iOS versions

Last updated: June 27, 2026

Released under the MIT License.