Mastering SF Symbols Rendering Modes in SwiftUI

Mastering SF Symbols Rendering Modes in SwiftUI
I love SF Symbols so much I hardly even bother with anything else at this point. I understand that cross platform development expects different iconology and different libraries but I just don't care anymore. SF Symbols looks the cleanest, achieves non-verbal communication in an unbeatable way, and offers the most flexibility for customization. The others don't even close. At this point I know the library I want to use, and I copy the svgs from the SF Symbols library with paths colored properly, pick my size and boom we are done. Decision made in less than a second.
Let me show you some of the flexibility which makes it so good.
The Three Rendering Modes
SF Symbols in SwiftUI support three distinct rendering modes:
- Multicolor - Uses Apple's built-in color schemes
- Palette - Custom multi-layer coloring
- Monochrome - Single color rendering
Each mode serves a specific purpose and knowing when to use them will level up your SwiftUI UI game.
Multicolor: The Easy Win
For most weather icons, multicolor mode is perfect. Apple has already designed beautiful color schemes that look great out of the box:
Image(systemName: "cloud.sun.fill")
.symbolRenderingMode(.multicolor)
.font(.system(size: 32))
This gives you:
- Sun icons in yellow/orange
- Rain with blue water and gray clouds
- Snow with appropriate blue/white coloring
- And many more pre-designed combinations
The multicolor mode is Apple's gift to developers who want beautiful icons without the design work.
Palette: Custom Layer Control
Sometimes you need more control. The thunderstorm icon (cloud.bolt.fill) in multicolor mode doesn't quite pop the way we want. This is where palette mode shines:
Image(systemName: "cloud.bolt.fill")
.symbolRenderingMode(.palette)
.foregroundStyle(
.white.opacity(0.9), // Cloud layer
Color(hex: "FDB813") // Bolt layer
)
Palette mode lets you control each layer of the symbol independently. For cloud.bolt.fill:
- First color = cloud
- Second color = bolt
This gives us a white cloud with a bright yellow lightning bolt that really stands out.
Monochrome: Keep It Simple
For some icons, a single solid color is exactly what you need. The snow icon works beautifully as pure white:
Image(systemName: "snow")
.foregroundColor(.white)
Clean, simple, effective.
Putting It All Together
Here's how I structured the weather icon rendering logic in Border Times:
@ViewBuilder
private var weatherIconImage: some View {
switch weather.condition {
case .thunderstorm:
// Use palette mode for custom colors
Image(systemName: weather.condition.sfSymbol)
.symbolRenderingMode(.palette)
.foregroundStyle(
.white.opacity(0.9),
Color(hex: "FDB813")
)
case .snow:
// Use solid white
Image(systemName: weather.condition.sfSymbol)
.foregroundColor(.white)
default:
// Use SF Symbols' built-in multicolor
Image(systemName: weather.condition.sfSymbol)
.symbolRenderingMode(.multicolor)
}
}
This approach gives us:
- Maximum flexibility - Each icon can be customized as needed
- Clean code -
@ViewBuilderwith a switch keeps it readable - Beautiful defaults - Multicolor mode for most cases
- Custom control - Palette and monochrome when we need them
The Complete Weather Card
The full implementation includes matching gradient backgrounds for each weather condition. The key is ensuring your background provides proper contrast for your icon choice:
struct WeatherCardView: View {
let weather: WeatherData
var body: some View {
VStack(spacing: 4) {
conditionText
weatherIcon
temperatureText
}
.background(weather.condition.gradient)
.cornerRadius(12)
.shadow(color: .black.opacity(0.15), radius: 10, x: 0, y: 5)
}
private var weatherIcon: some View {
weatherIconImage
.font(.system(size: 32))
.shadow(color: .black.opacity(0.15), radius: 6, x: 0, y: 2)
}
// ... weatherIconImage implementation above
}
Pack Icon Logic Into Your Model
Here's where things get really clean: instead of scattering SF Symbol names and gradients throughout your views, pack everything into your model layer. This makes your view code trivial and your icon logic reusable.
enum WeatherCondition: String, Codable {
case clear, partlyCloudy, cloudy, rain, thunderstorm, snow
// ... other cases
var sfSymbol: String {
switch self {
case .clear: return "sun.max.fill"
case .partlyCloudy: return "cloud.sun.fill"
case .rain: return "cloud.rain.fill"
case .thunderstorm: return "cloud.bolt.fill"
case .snow: return "snow"
// ... other cases
}
}
var gradient: LinearGradient {
switch self {
case .clear:
return LinearGradient(
colors: [Color(hex: "E8A906"), Color(hex: "F4B71C")],
startPoint: .topLeading,
endPoint: .bottomTrailing
)
case .rain:
return LinearGradient(
colors: [Color(hex: "4A90E2"), Color(hex: "6BA4E8")],
startPoint: .topLeading,
endPoint: .bottomTrailing
)
// ... other cases
}
}
}
Now your view code becomes ridiculously simple:
Image(systemName: weather.condition.sfSymbol)
.symbolRenderingMode(.multicolor)
// And backgrounds:
.background(weather.condition.gradient)
No magic strings scattered around. No repeated color definitions. Just clean, centralized logic that makes your entire codebase easier to maintain.
Try It Yourself
The full code is in production at Border Times - check out the weather cards in the details section per crossing section. You can see all 11 weather conditions rendered with these techniques. If you're interested in full code snippets reach out at support@border-times.com.
SF Symbols are incredibly powerful when you understand all three rendering modes. Don't settle for monochrome when multicolor and palette modes can make your icons pop.
Happy coding!
Want to see more SwiftUI tips? Check out my other posts on collapsible indicators, pulsing live indicators, and status trackers.