Customizing the appearance of symbol images in SwiftUI
Symbol images are vector-based icons from Apple's SF Symbols library, designed for use across Apple platforms. These scalable images adapt to different sizes and weights, ensuring consistent, high-quality icons throughout our apps. Using symbol images in SwiftUI is straightforward with the Image
view and the system name of the desired symbol. Here's a quick example:
import SwiftUI
struct ContentView: View {
var body: some View {
Image(systemName: "star")
}
}
![Screenshot of the star system image](/static/blog/CustomizingTheAppearanceOfSymbolImagesInSwiftUI/symbol.rQbIbk8B_QAxYeaIygxGnllvhc24dYMAKmhbkZXICIo.png)
![Screenshot of the star system image](/static/blog/CustomizingTheAppearanceOfSymbolImagesInSwiftUI/symbol-light.MogaxwOWZ9JufXYL3FjGYyg7J2uD4Z6ldeZn-hUSHcA.png)
There are various ways to customize the appearance of symbol images. Let's explore how to do that in SwiftUI.
# Size
Even though a symbol is placed inside an Image
view, it should be treated more like text. To adjust the size of a symbol, we can apply the font() modifier, just like with a Text
view. This allows us to align the size of the symbol with different text styles, ensuring visual consistency in our UI.
HStack {
Image(systemName: "star")
.font(.title)
Image(systemName: "star")
.font(.body)
Image(systemName: "star")
.font(.caption)
}
![Screenshot of the star system image in three different fonts: title, body and caption](/static/blog/CustomizingTheAppearanceOfSymbolImagesInSwiftUI/font.BRL_GZ1NDuwDl-nrYvhmYImhIk8dlNMQPd7CtrIxRu4.png)
![Screenshot of the star system image in three different fonts: title, body and caption](/static/blog/CustomizingTheAppearanceOfSymbolImagesInSwiftUI/font-light.9_7yf2gDcCgX6uKJz1PfK9-uenYHZuFmeuztyZQJtc0.png)
We can adjust the weight of the symbol using the fontWeight() modifier. This modifier changes the thickness of the symbol's strokes, allowing us to match or contrast the symbol with the surrounding text.
HStack {
Image(systemName: "star")
.fontWeight(.light)
Image(systemName: "star")
.fontWeight(.bold)
Image(systemName: "star")
.fontWeight(.black)
}
![Screenshot of the star system image in three different weights: light, bold and black](/static/blog/CustomizingTheAppearanceOfSymbolImagesInSwiftUI/weight.47G0XWN8clSLJUlDRqfJo2fbM6FMlIaJj0LF1Dm9KcM.png)
![Screenshot of the star system image in three different weights: light, bold and black](/static/blog/CustomizingTheAppearanceOfSymbolImagesInSwiftUI/weight-light.iPyyy_hVA7_2BLVLwa_R9mcX-TSEG9X_HWEuv52Ru70.png)
To scale the image relative to its font size, we should use the imageScale() modifier. There are three options small
, medium
, and large
, which scale the symbol proportionally based on its font size. If the font is not set explicitly, the symbol will inherit the font from the current environment.
HStack {
Image(systemName: "star")
.imageScale(.small)
Image(systemName: "star")
.imageScale(.medium)
Image(systemName: "star")
.imageScale(.large)
}
.font(.headline)
![Screenshot of the star system image in three different scales: small, medium and large](/static/blog/CustomizingTheAppearanceOfSymbolImagesInSwiftUI/scale.5o3vFIqHj3Ww98V_Q1q8OmduyCAoh2nsTmMOwO1tU5o.png)
![Screenshot of the star system image in three different scales: small, medium and large](/static/blog/CustomizingTheAppearanceOfSymbolImagesInSwiftUI/scale-light.EdaXCq1kir62cAALALlckxEVJFjPKQ3XkzMYOernZFw.png)
It's not recommended to resize symbol images by applying the resizable()
modifier and setting a frame, as we would with other images. When resizable()
is used, the image stops being a symbol image, which can affect its layout and alignment with text.
# Color
Customizing the color of symbol images in SwiftUI is straightforward using the foregroundStyle() view modifier. This modifier allows us to set the color of the symbol image directly.
Image(systemName: "star")
.foregroundStyle(.orange)
![Screenshot of the star system image in orange color](/static/blog/CustomizingTheAppearanceOfSymbolImagesInSwiftUI/color.R9AszAFv3ZSsiPnRL8Irp85A0DtWIgeXjJ_7LS1PNUU.png)
![Screenshot of the star system image in orange color](/static/blog/CustomizingTheAppearanceOfSymbolImagesInSwiftUI/color-light.F6gb4d2r0P0YTpR_rM4AhzBwp_48UPQD18kZ5u3XalE.png)
The foregroundStyle()
modifier can take any ShapeStyle
, including gradients, which opens up a wide range of customization possibilities for our symbol images. In this example, the star symbol uses a LinearGradient
with yellow and red colors, transitioning from the top to the bottom.
Image(systemName: "star")
.foregroundStyle(
LinearGradient(
colors: [.yellow, .red],
startPoint: .top,
endPoint: .bottom
)
)
![A glowing neon star icon with a gradient from yellow to red](/static/blog/CustomizingTheAppearanceOfSymbolImagesInSwiftUI/gradient.9lLiAzJMl-mf71SEHlEjOJ_NcALeuL-FHoBiOyrVPtE.png)
![A glowing neon star icon with a gradient from yellow to red](/static/blog/CustomizingTheAppearanceOfSymbolImagesInSwiftUI/gradient-light.RQtj-EZRbXh4Yi4IfJYEcBijHSKHHM3GZxqrxYF3or0.png)
# Rendering mode
We can customize the appearance of symbol images further by using different rendering modes. SF Symbols have four different rendering modes that change the symbol’s colors and appearance. Some rendering modes keep the entire icon the same color, while others allow for multiple colors.
To set the preferred rendering mode for a symbol image in SwiftUI, we use the symbolRenderingMode() modifier.
# Monochrome
Monochrome is the default rendering mode. In this mode, each layer of the symbol is the same color.
Image(systemName: "thermometer.snowflake")
.symbolRenderingMode(.monochrome)
![An icon depicting a thermometer with a snowflake symbol next to it](/static/blog/CustomizingTheAppearanceOfSymbolImagesInSwiftUI/monochrome.LPL0xRx4oAcYh-4_INnO9g8O3kYgIFZP_kbBFNsFM3g.png)
![An icon depicting a thermometer with a snowflake symbol next to it](/static/blog/CustomizingTheAppearanceOfSymbolImagesInSwiftUI/monochrome-light.CG7yJmGatOl6mjt7p9Gszd8AwNm60UidV6HK0cLhwu8.png)
# Hierarchical
Hierarchical mode renders symbols as multiple layers, with different opacities applied to the foreground style. The hierarchy of the layers and their opacities are predefined within each symbol, but we can still customize the color of the layers using the foregroundStyle()
modifier.
HStack {
Image(systemName: "thermometer.snowflake")
Image(systemName: "thermometer.snowflake")
.foregroundStyle(.indigo)
}
.symbolRenderingMode(.hierarchical)
The symbolRenderingMode()
modifier can be applied either directly to an Image
view or set in the environment by applying it to a parent view containing multiple symbol images. This way, all symbol images inside the parent will be affected.
![Two icons depicting thermometers with snowflakes next to them. The left icon has a white thermometer with a gray snowflake, while the right icon has a blue thermometer with a light blue snowflake.](/static/blog/CustomizingTheAppearanceOfSymbolImagesInSwiftUI/hierarchical.lnhvuGdiwUl2PIzLCraqbluAkLsySxnRJCzeLF-EIZw.png)
![Two icons depicting thermometers with snowflakes next to them. The left icon has a black thermometer with a gray snowflake, while the right icon has a blue thermometer with a light blue snowflake.](/static/blog/CustomizingTheAppearanceOfSymbolImagesInSwiftUI/hierarchical-light.PKYkUuzEOaG5Mf1NdrO7fenKWYI_GVN93T15dOBCTWg.png)
# Palette
Palette mode allows symbols to be rendered with multiple layers, each layer having a different color. This mode is ideal for creating colorful, multi-layered icons.
Image(systemName: "thermometer.snowflake")
.symbolRenderingMode(.palette)
.foregroundStyle(.blue, .teal, .gray)
Interestingly, we don't need to explicitly specify the palette rendering mode. If we apply more than one style inside the foregroundStyle()
modifier, palette mode will be activated automatically.
Image(systemName: "thermometer.snowflake")
.foregroundStyle(.blue, .teal, .gray)
![An icon depicting a thermometer with a blue bulb and a cyan snowflake next to it](/static/blog/CustomizingTheAppearanceOfSymbolImagesInSwiftUI/palette.NNk5iBK7QhcRjCvIvHGyAlqU5lsJCVkRAkAtVCLf8_8.png)
![An icon depicting a thermometer with a blue bulb and a cyan snowflake next to it](/static/blog/CustomizingTheAppearanceOfSymbolImagesInSwiftUI/palette-light.624Ap8YkVVEe9G4efw7X49_e82xMwcg8Tpos4DcFDbU.png)
If we specify only two colors for a symbol that defines three levels of hierarchy, the secondary and tertiary layers will use the same color.
Image(systemName: "thermometer.snowflake")
.foregroundStyle(.blue, .gray)
![An icon depicting a thermometer with a blue bulb and a gray snowflake next to it](/static/blog/CustomizingTheAppearanceOfSymbolImagesInSwiftUI/palette-2.OnWVe9wFOJyFHwPkSaElGk72pBXg3vgzU4UAFE1Iheg.png)
![An icon depicting a thermometer with a blue bulb and a gray snowflake next to it](/static/blog/CustomizingTheAppearanceOfSymbolImagesInSwiftUI/palette-2-light.c9C62i0tSheP0oDDRqnIfJSHur41gEWzqIk19r3xr3Y.png)
# Multicolor
Multicolor mode renders symbols with their inherent styles, using a fixed set of colors defined by Apple. When using multicolor rendering, we cannot customize the colors of the symbol, it will use the predefined colors, which we can preview in the SF Symbols app. Even if we set a foregroundStyle()
while the multicolor rendering mode is activated, the foreground style customization will be ignored.
HStack {
Image(systemName: "thermometer.snowflake")
Image(systemName: "thermometer.sun.fill")
}
.symbolRenderingMode(.multicolor)
![Two icons depicting thermometers with weather symbols next to them. The left icon shows a thermometer with a blue bulb and a white snowflake. The right icon displays a thermometer with a red bulb and a yellow sun.](/static/blog/CustomizingTheAppearanceOfSymbolImagesInSwiftUI/multicolor.DbE_EHkj2NLBKzGRh1Lhvh4g0O1pXULTqsbbbN-63g4.png)
![Two icons depicting thermometers with weather symbols next to them. The left icon shows a thermometer with a blue bulb and a white snowflake. The right icon displays a thermometer with a red bulb and a yellow sun.](/static/blog/CustomizingTheAppearanceOfSymbolImagesInSwiftUI/multicolor-light.vB2nGrrwj7qYJX7gy_f88n-p6NxnpAkbiq6kQQ9KdRo.png)
It's worth noting that since these colors are fixed, they don't adapt to light and dark mode. For example, our thermometer symbol has white outlines that will be invisible on a white background.
Not all symbols support every rendering mode. Symbols with fewer layers may look the same across modes, with hierarchical and palette modes appearing similar to monochrome.
# Variable value
When displaying a symbol image in SwiftUI, we can provide an optional value between 0.0 and 1.0 that the rendered image can use to customize its appearance. If the symbol doesn’t support variable values, this parameter has no effect. We should check in the SF Symbols app to determine which symbols support variable values.
HStack {
Image(systemName: "speaker.wave.3", variableValue: 0)
Image(systemName: "speaker.wave.3", variableValue: 0.3)
Image(systemName: "speaker.wave.3", variableValue: 0.6)
Image(systemName: "speaker.wave.3", variableValue: 0.9)
}
![A series of five speaker icons with sound wave symbols next to them, representing increasing volume levels](/static/blog/CustomizingTheAppearanceOfSymbolImagesInSwiftUI/value.H93FdtDjNnjjbW9RbvwBCqKoLPKlVcQEory8xtaXm84.png)
![A series of five speaker icons with sound wave symbols next to them, representing increasing volume levels](/static/blog/CustomizingTheAppearanceOfSymbolImagesInSwiftUI/value-light.9PTOR-njopuLBGwEFcf3RJDsqLDEyiVetJlGW-Qu8HQ.png)
With variable values, we can represent a characteristic that can change over time, like capacity or strength. This allows the symbol's appearance to change dynamically based on the state of our application.
struct ContentView: View {
@State private var value = 0.5
var body: some View {
VStack {
Image(
systemName: "speaker.wave.3",
variableValue: value
)
Slider(value: $value, in: 0...1)
.padding()
}
.padding()
}
}
In this example, the symbol speaker.wave.3
changes its appearance based on the value provided by the Slider
.
![A volume control interface showing a speaker icon with sound waves and a horizontal slider below it](/static/blog/CustomizingTheAppearanceOfSymbolImagesInSwiftUI/slider.NyhEPZ3J4OcHoT7J8PSal3cV0DxAnUzPV37LeaoCIq0.png)
![A volume control interface showing a speaker icon with sound waves and a horizontal slider below it](/static/blog/CustomizingTheAppearanceOfSymbolImagesInSwiftUI/slider-light.G2qM5oUwIm_fiiq1X_5rFr90jlITjB9RDBxt3vFd9Z0.png)
We should use variable values to communicate changes in status, such as volume, battery level, or signal strength, providing users with a clear visual representation of dynamic states. To convey depth and visual hierarchy, we should use the hierarchical rendering mode, which elevates certain layers and distinguishes foreground and background elements within a symbol.
# Design variants
Symbols can come in different design variants, such as fill and slash, for example, to help communicate specific states and actions. The slash variant can indicate that an item or action is unavailable, while the fill variant can signify selection.
In SwiftUI, we can use the symbolVariant() modifier to apply these variants.
HStack {
Image(systemName: "heart")
Image(systemName: "heart")
.symbolVariant(.slash)
Image(systemName: "heart")
.symbolVariant(.fill)
}
![Three heart icons representing different states. The first icon is an outlined heart, the second icon is an outlined heart with a diagonal slash through it, and the third icon is a solid heart.](/static/blog/CustomizingTheAppearanceOfSymbolImagesInSwiftUI/variant.3GAcADVCPSCJSyABS-pTKlnk6LRH2Il2o4goHdSTVn4.png)
![Three heart icons representing different states. The first icon is an outlined heart, the second icon is an outlined heart with a diagonal slash through it, and the third icon is a solid heart.](/static/blog/CustomizingTheAppearanceOfSymbolImagesInSwiftUI/variant-light.iPQFPlHBT52QmJce3pM5QdrRPPW96zFEoWIszN9gPIw.png)
Additionally, some symbols can be enclosed within shapes such as circles, squares, or rectangles to enhance their visual context
HStack {
Image(systemName: "heart")
.symbolVariant(.circle)
Image(systemName: "heart")
.symbolVariant(.square)
Image(systemName: "heart")
.symbolVariant(.rectangle)
}
![Three icons depicting hearts inside different shapes. The first icon shows a solid heart inside a circle, the second icon shows a solid heart inside a square, and the third icon shows a solid heart inside a rounded rectangle.](/static/blog/CustomizingTheAppearanceOfSymbolImagesInSwiftUI/shape.4reC0FId14aBW4PyXTZGT1Iq-JJVhFFhDq1C2pw-i_E.png)
![Three icons depicting hearts inside different shapes. The first icon shows a solid heart inside a circle, the second icon shows a solid heart inside a square, and the third icon shows a solid heart inside a rounded rectangle.](/static/blog/CustomizingTheAppearanceOfSymbolImagesInSwiftUI/shape-light.XjlCPIF06SocVntx-PiYyodR9ASeu1zILES_rS2xxAI.png)
Different symbol variants serve various design purposes. The outline variant is effective in toolbars, navigation bars, and lists, where symbols are often displayed alongside text. Enclosing symbols in shapes like circles or squares can enhance legibility, especially at smaller sizes. The fill variant, with its solid areas, gives symbols more visual emphasis, making it suitable for iOS tab bars, swipe actions, and scenarios where an accent color indicates selection.
In many cases, the view displaying the symbol automatically chooses the appropriate variant. For example, an iOS tab bar typically uses the fill variant, while a navigation bar prefers the outline variant. This automatic selection ensures that symbols are used effectively in different contexts without needing explicit specification.
Enhancing symbol images in SwiftUI can significantly improve our app's look and feel. By adjusting size, color, rendering modes, variable values, and design variants, we can create icons that make our app more intuitive and visually appealing. SwiftUI makes these adjustments straightforward, enabling us to easily implement and refine these customizations for a better user experience.
If you have older iOS apps and want to update them with modern SwiftUI features, check out my book Integrating SwiftUI into UIKit Apps. It provides detailed guidance on gradually adopting SwiftUI in your UIKit projects. Additionally, if you're eager to enhance your Swift programming skills, my latest book Swift Gems offers over a hundred advanced tips and techniques, including optimizing collections, handling strings, mastering asynchronous programming, and debugging, to take your Swift code to the next level.