Updated for Xcode 16.4
New in iOS 17
SwiftUI’s Color view doesn’t always hold one specific color, and instead is only resolved to a specific value when it’s being drawn on the screen. This allows the system to use slight variations between light and dark mode to ensure the optimal experience, but it also means the only way to get actual red, green, and blue (RGB) components out is to ask for the resolved color – ask the system “given this environment, what actual color values are being used?”
Resolving takes two steps: gaining access to the current environment, and pass that into a call to resolve(in:) on your color. You can then save the resulting data using Codable or whatever other data form you want.
For example, this lets the user choose any color they want, and displays its red, green, and blue components:
struct ContentView: View { @Environment(\.self) var environment @State private var color = Color.red @State private var resolvedColor: Color.Resolved? var body: some View { VStack { ColorPicker("Select your favorite color", selection: $color) if let resolvedColor { Text("Red: \(resolvedColor.red)") Text("Green: \(resolvedColor.green)") Text("Blue: \(resolvedColor.blue)") Text("Opacity: \(resolvedColor.opacity)") } } .padding() .onChange(of: color, initial: true, getColor) } func getColor() { resolvedColor = color.resolve(in: environment) } }
Download this as an Xcode project

Important: The data is provided as Float rather than Double.
In that code, resolved gets set to a Color.Resolved type, which can be converted back into a new Color object or be converted to JSON or similar using Codable.
For example, we could convert our resolved color to JSON like this:
struct ContentView: View { @Environment(\.self) var environment @State private var color = Color.red @State private var resolvedColor: Color.Resolved? @State private var colorJSON = "" var body: some View { VStack { ColorPicker("Select your favorite color", selection: $color) if let resolvedColor { Text("Red: \(resolvedColor.red)") Text("Green: \(resolvedColor.green)") Text("Blue: \(resolvedColor.blue)") Text("Opacity: \(resolvedColor.opacity)") } Text("Color JSON: \(colorJSON)") } .padding() .onChange(of: color, initial: true, getColor) } func getColor() { resolvedColor = color.resolve(in: environment) if let colorData = try? JSONEncoder().encode(resolvedColor) { colorJSON = String(decoding: colorData, as: UTF8.self) } } }
Download this as an Xcode project

Note: We’re dealing with floating-point numbers, so you can expect some microscopic variations.
If you’ve loaded a resolved color and want to convert it back to a Color instance, just pass it into the initializer like this:
let resolvedColor = Color.Resolved(red: 0, green: 0.6, blue: 0.9, opacity: 1) Rectangle() .fill(Color(resolvedColor).gradient) .ignoresSafeArea()
Download this as an Xcode project

SAVE 50% All our books and bundles are half price for Black Friday, so you can take your Swift knowledge further for less! Get my all-new book Everything but the Code to make more money with apps, get the Swift Power Pack to build your iOS career faster, get the Swift Platform Pack to builds apps for macOS, watchOS, and beyond, or get the Swift Plus Pack to learn Swift Testing, design patterns, and more.