Div
Div

Reputation: 1603

How to get RGB components from Color in SwiftUI

If I have a SwiftUI Color:

let col: Color = Color(red: 0.5, green: 0.5, blue: 0.5)

How do I get the RGB components from col?
Like this maybe:

print(col.components.red)

In UIKit, I could use UIColor.getRed but there doesn't seem to be an equivalent in SwiftUI.

Upvotes: 40

Views: 24879

Answers (8)

marcantonio
marcantonio

Reputation: 1069

Simple one-liner:

print(UIColor(Color.blue).cgColor.components)

You get an [CGFloat]? of [red, green, blue, alpha].

Update

Use NSColor for macOS

Upvotes: 18

LukeSideWalker
LukeSideWalker

Reputation: 7910

For MacOS 14.X Sonoma

func getRgbInfo(of color : Color) -> String {
    
    // 1: convert SwiftUI.Color into NSColor with RGB colorspace
    guard let nsColor: NSColor = NSColor(color).usingColorSpace(.sRGB) else { return "Error" }
    
    // 2: create variables to be filled
    var red   : CGFloat = 0
    var green : CGFloat = 0
    var blue  : CGFloat = 0
    var alpha : CGFloat = 0
    
    // 3: fill the variables
    nsColor.getRed(&red, green: &green, blue: &blue, alpha: &alpha)
    
    // 4: optional : Create Integer values
    let redValue   : Int = Int(red * 255)
    let greenValue : Int = Int(green * 255)
    let blueValue  : Int = Int(blue * 255)
    
    // 5: return the result
    return "(R: \(redValue), G: \(greenValue), B: \(blueValue), A: \(alpha)"
}

Upvotes: 0

Mojtaba Hosseini
Mojtaba Hosseini

Reputation: 119302

iOS 17/ macOS 14 (advanced but native)

You can ask for resolving Color components in the given environment, because colors are different in different environments (for example in dark and light environments). In the following sample, I resolved it using the current environment of the used color.

struct ContentView: View {
    @Environment(\.self) var environment
    @State private var color = Color.red
    @State private var components: Color.Resolved?

    var body: some View {
        VStack {
            ColorPicker("Select your favorite color", selection: $color)

            if let components {
                Text("R: \(components.red)")
                Text("G: \(components.green)")
                Text("B: \(components.blue)")
                Text("A: \(components.opacity)")
                Text("HEX: \(components.description)")
            }
        }
        .padding()
        .onChange(of: color, initial: true) { components = color.resolve(in: environment) }
    }
}

The code above has been written for iOS 17 beta 1 using Xcode 15 beta 1


iOS 14 / macOS 10.16

There is a new initializer that takes a Color and returns a UIColor for iOS or NSColor for macOS now. With the help of those you can implement the following extensions:

import SwiftUI

#if canImport(UIKit)
import UIKit
#elseif canImport(AppKit)
import AppKit
#endif

extension Color {
    var components: (red: CGFloat, green: CGFloat, blue: CGFloat, opacity: CGFloat) {

        #if canImport(UIKit)
        typealias NativeColor = UIColor
        #elseif canImport(AppKit)
        typealias NativeColor = NSColor
        #endif

        var r: CGFloat = 0
        var g: CGFloat = 0
        var b: CGFloat = 0
        var o: CGFloat = 0

        guard NativeColor(self).getRed(&r, green: &g, blue: &b, alpha: &o) else {
            // You can handle the failure here as you want
            return (0, 0, 0, 0)
        }

        return (r, g, b, o)
    }

    var hex: String {
        String(
            format: "#%02x%02x%02x%02x",
            Int(components.red * 255),
            Int(components.green * 255),
            Int(components.blue * 255),
            Int(components.opacity * 255)
        )
    }
}

Usage

Color.red.components.red // 0.9999999403953552 // <- SwiftUI Colors are not pure!

Upvotes: 50

Ivorius
Ivorius

Reputation: 365

Based on @Mojtaba's answer, I came up with a shorter, more flexible version:

#if canImport(UIKit)
import UIKit
#elseif canImport(AppKit)
import AppKit
#endif

extension Color {
    #if canImport(UIKit)
    var asNative: UIColor { UIColor(self) }
    #elseif canImport(AppKit)
    var asNative: NSColor { NSColor(self) }
    #endif

    var rgba: (red: CGFloat, green: CGFloat, blue: CGFloat, alpha: CGFloat) {
        let color = asNative.usingColorSpace(.deviceRGB)!
        var t = (CGFloat(), CGFloat(), CGFloat(), CGFloat())
        color.getRed(&t.0, green: &t.1, blue: &t.2, alpha: &t.3)
        return t
    }

    var hsva: (hue: CGFloat, saturation: CGFloat, value: CGFloat, alpha: CGFloat) {
        let color = asNative.usingColorSpace(.deviceRGB)!
        var t = (CGFloat(), CGFloat(), CGFloat(), CGFloat())
        color.getHue(&t.0, saturation: &t.1, brightness: &t.2, alpha: &t.3)
        return t
    }
}

Doing asNative.redComponent etc. might also work, FYI.

Upvotes: 2

Kai Zheng
Kai Zheng

Reputation: 8130

I have found that @Mojtaba Hosseinis answer is working fine, except when you have your colors declared inside assets with light and dark appearances.

Then I found that the dark appearance somehow gets lost when using UIColor(self). Here is a workaround I came up with:

Note, this is only for iOS since my app is iOS only, you could of course do the same as @Mojtaba Hosseini and adapt it to macOS as well.

extension Color {

    var components: (r: Double, g: Double, b: Double, o: Double)? {
        let uiColor: UIColor
        
        var r: CGFloat = 0
        var g: CGFloat = 0
        var b: CGFloat = 0
        var o: CGFloat = 0
        
        if self.description.contains("NamedColor") {
            let lowerBound = self.description.range(of: "name: \"")!.upperBound
            let upperBound = self.description.range(of: "\", bundle")!.lowerBound
            let assetsName = String(self.description[lowerBound..<upperBound])
            
            uiColor = UIColor(named: assetsName)!
        } else {
            uiColor = UIColor(self)
        }

        guard uiColor.getRed(&r, green: &g, blue: &b, alpha: &o) else { return nil }
        
        return (Double(r), Double(g), Double(b), Double(o))
    }
}

The idea is to use the UIColor(named:) initializer instead, where all appearances are correct. Fortunately, the name we set in assets is saved in the description of the Color. We only have to abstract it since there is also other information, namely bundle, etc.

Upvotes: 4

Rodrigo Alvarez
Rodrigo Alvarez

Reputation: 78

You can use UIColor and transform the UIColor to Color after. Code:

extension UIColor {
    func hexValue() -> String {
        let values = self.cgColor.components
        var outputR: Int = 0
        var outputG: Int = 0
        var outputB: Int = 0
        var outputA: Int = 1

        switch values!.count {
            case 1:
                outputR = Int(values![0] * 255)
                outputG = Int(values![0] * 255)
                outputB = Int(values![0] * 255)
                outputA = 1
            case 2:
                outputR = Int(values![0] * 255)
                outputG = Int(values![0] * 255)
                outputB = Int(values![0] * 255)
                outputA = Int(values![1] * 255)
            case 3:
                outputR = Int(values![0] * 255)
                outputG = Int(values![1] * 255)
                outputB = Int(values![2] * 255)
                outputA = 1
            case 4:
                outputR = Int(values![0] * 255)
                outputG = Int(values![1] * 255)
                outputB = Int(values![2] * 255)
                outputA = Int(values![3] * 255)
            default:
                break
        }
        return "#" + String(format:"%02X", outputR) + String(format:"%02X", outputG) + String(format:"%02X", outputB) + String(format:"%02X", outputA)
    }
}

Upvotes: 1

Nicola Ferruzzi
Nicola Ferruzzi

Reputation: 368

Waiting for an API I've abused CustomStringConvertible protocol for the simple rgba case where the color description format is #rrggbbaa

debugPrint(Color.red)
debugPrint(Color(red: 1.0, green: 0.0, blue: 0.0))
debugPrint(Color(red: 1.0, green: 0.3, blue: 0.0))
debugPrint(Color(.sRGB, red: 1.0, green: 0.0, blue: 0.5, opacity: 0.3))
debugPrint(Color(hue: 1.0, saturation: 0.0, brightness: 1.0))
debugPrint(Color(.displayP3, red: 1.0, green: 0.0, blue: 0.0, opacity: 1.0).description)

red
#FF0000FF
#FF4C00FF
#FF00804D
#FFFFFFFF
"DisplayP3(red: 1.0, green: 0.0, blue: 0.0, opacity: 1.0)"

as you can see, things like Color.red just dump "red" but if you are working with simple RGB colors generated by code (ie from a color picker) then this is not too bad

extension SwiftUI.Color {
    var redComponent: Double? {
        let val = description
        guard val.hasPrefix("#") else { return nil }
        let r1 = val.index(val.startIndex, offsetBy: 1)
        let r2 = val.index(val.startIndex, offsetBy: 2)
        return Double(Int(val[r1...r2], radix: 16)!) / 255.0
    }

    var greenComponent: Double? {
        let val = description
        guard val.hasPrefix("#") else { return nil }
        let g1 = val.index(val.startIndex, offsetBy: 3)
        let g2 = val.index(val.startIndex, offsetBy: 4)
        return Double(Int(val[g1...g2], radix: 16)!) / 255.0
    }

    var blueComponent: Double? {
        let val = description
        guard val.hasPrefix("#") else { return nil }
        let b1 = val.index(val.startIndex, offsetBy: 5)
        let b2 = val.index(val.startIndex, offsetBy: 6)
        return Double(Int(val[b1...b2], radix: 16)!) / 255.0
    }

    var opacityComponent: Double? {
        let val = description
        guard val.hasPrefix("#") else { return nil }
        let b1 = val.index(val.startIndex, offsetBy: 7)
        let b2 = val.index(val.startIndex, offsetBy: 8)
        return Double(Int(val[b1...b2], radix: 16)!) / 255.0
    }
}

Upvotes: 6

Matteo Pacini
Matteo Pacini

Reputation: 22846

The answer is no - there's no API do so (yet), but...

Most of SwiftUI structs have fields that are private, like in Color.

You can use Mirror to extract such informations - but keep in mind it is not efficient.

Here's how to extract the hexadecimal representation of a SwiftUI Color - for educational purpose.

Copy and paste this into a Xcode 11 playground.

import UIKit
import SwiftUI

let systemColor = Color.red
let color = Color(red: 0.3, green: 0.5, blue: 1)

extension Color {

    var hexRepresentation: String? {
        let children = Mirror(reflecting: color).children
        let _provider = children.filter { $0.label == "provider" }.first
        guard let provider = _provider?.value else {
            return nil
        }
        let providerChildren = Mirror(reflecting: provider).children
        let _base = providerChildren.filter { $0.label == "base" }.first
        guard let base = _base?.value else {
            return nil
        }
        var baseValue: String = ""
        dump(base, to: &baseValue)
        guard let firstLine = baseValue.split(separator: "\n").first,
              let hexString = firstLine.split(separator: " ")[1] as Substring? else {
            return nil
        }
        return hexString.trimmingCharacters(in: .newlines)
    }

}

systemColor.hexRepresentation
color.hexRepresentation

Colors like .red, .white, etc., don't seem to have many information in them, when dumped.

Just their "system" name.

▿ red
  ▿ provider: SwiftUI.(unknown context at $1297483bc).ColorBox<SwiftUI.SystemColorType> #0
    - super: SwiftUI.(unknown context at $129748300).AnyColorBox
    - base: SwiftUI.SystemColorType.red

A Color instantiated with red/blue/green components does instead.

▿ #4C80FFFF
  ▿ provider: SwiftUI.(unknown context at $11cd2e3bc).ColorBox<SwiftUI.Color._Resolved> #0
    - super: SwiftUI.(unknown context at $11cd2e300).AnyColorBox
    ▿ base: #4C80FFFF
      - linearRed: 0.073238954
      - linearGreen: 0.21404114
      - linearBlue: 1.0
      - opacity: 1.0

In the Playground, you will see:

  • systemColor.hexRepresentation returning nil
  • color.hexRepresentation returning "#4C80FFFF"

Upvotes: 1

Related Questions