如何在SwiftUI中从Color获取RGB组件

brccelvz  于 2023-06-28  发布在  Swift
关注(0)|答案(7)|浏览(168)

如果我有一个SwiftUI Color

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

如何从col中获取RGB分量?
就像这样:

print(col.components.red)

在UIKit中,我可以使用UIColor.getRed,但在SwiftUI中似乎没有等效的。

yc0p9oo0

yc0p9oo01#

iOS 17/ macOS 14(高级但原生)

您可以要求在给定的环境中解析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) }
    }
}

上面的代码是使用Xcode 15 beta 1为iOS 17 beta 1编写的

iOS 14 / macOS 10.16

现在有一个新的初始化器,它接受Color并返回UIColor(对于iOS)或NSColor(对于macOS)。在这些工具的帮助下,您可以实现以下扩展:

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)
        )
    }
}

用法

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

fhg3lkii2#

简单的一行程序:

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

你会得到一个[CGFloat]? [red,绿色,blue,alpha]。

xa9qqrwz

xa9qqrwz3#

我在简单的rgba情况下滥用了CustomStringConvertible协议,其中颜色描述格式为#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)"

正如你所看到的,像www.example.com这样的东西Color.red只是转储“红色”,但如果你正在使用由代码生成的简单RGB颜色(即从颜色选择器),那么这还不算太糟糕

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
    }
}
jm2pwxwz

jm2pwxwz4#

我发现@Mojtaba Hosseinis的答案工作正常,除非你在资产中声明了颜色,颜色有亮有暗。
然后我发现在使用UIColor(self)时,黑暗的外观不知何故丢失了。以下是我提出的一个解决方案:
注意,这只适用于iOS,因为我的应用程序只适用于iOS,当然你也可以像@Mojtaba Hosseini一样做,并将其调整为macOS

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))
    }
}

这个想法是使用UIColor(named:)初始化器,其中所有外观都是正确的。幸运的是,我们在assets中设置的名称保存在Color的描述中。我们只需要抽象它,因为还有其他信息,即bundle等。

oipij1gg

oipij1gg5#

基于@Mojtaba的回答,我想出了一个更短,更灵活的版本:

#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
    }
}

做asNative.redComponent等也可能有用顺便说一句

3zwtqj6y

3zwtqj6y6#

答案是no-目前还没有API这样做,但是...
大多数SwiftUI结构都有private字段,就像Color一样。
您可以使用Mirror来提取此类信息-但请记住,它效率不高。
以下是如何提取SwiftUI Color的十六进制表示-用于教育目的。
复制并粘贴到Xcode 11Playground。

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

.red.white等颜色,似乎没有太多的信息,当dumped
只是他们的“系统”名称。

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

使用red/blue/green组件示例化的Color则会这样做。

▿ #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

在Playground,你会看到:

  • systemColor.hexRepresentation返回nil
  • color.hexRepresentation返回"#4C80FFFF"
ccgok5k5

ccgok5k57#

您可以使用UIColor并将UIColor转换为Color after。代码:

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)
    }
}

相关问题