关于ios:如何将SwiftUI颜色更改为UIColor?

How can I change a SwiftUI Color to UIColor?

尝试将SwiftUI颜色更改为UIColor的实例。

我可以很容易地从UIColor中获取RGBA,但是我不知道如何获取" Color"实例以返回相应的RGB和不透明度值。

1
2
3
4
5
6
7
8
9
10
11
12
@EnvironmentObject var colorStore: ColorStore

    init() {

        let red = //get red value from colorStore.primaryThemeColor
        let green = //get red value from colorStore.primaryThemeColor
        let blue = //get red value from colorStore.primaryThemeColor
        let opacity = //get red value from colorStore.primaryThemeColor

        let color = UIColor(red: red, green: green, blue: blue, alpha: opacity)
        UINavigationBar.appearance().tintColor = color
    }

...或者也许有更好的方法来完成我想要的。


这个解决方案怎么样?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
extension Color {

    func uiColor() -> UIColor {

        let components = self.components()
        return UIColor(red: components.r, green: components.g, blue: components.b, alpha: components.a)
    }

    private func components() -> (r: CGFloat, g: CGFloat, b: CGFloat, a: CGFloat) {

        let scanner = Scanner(string: self.description.trimmingCharacters(in: CharacterSet.alphanumerics.inverted))
        var hexNumber: UInt64 = 0
        var r: CGFloat = 0.0, g: CGFloat = 0.0, b: CGFloat = 0.0, a: CGFloat = 0.0

        let result = scanner.scanHexInt64(&hexNumber)
        if result {
            r = CGFloat((hexNumber & 0xff000000) >> 24) / 255
            g = CGFloat((hexNumber & 0x00ff0000) >> 16) / 255
            b = CGFloat((hexNumber & 0x0000ff00) >> 8) / 255
            a = CGFloat(hexNumber & 0x000000ff) / 255
        }
        return (r, g, b, a)
    }
}

用法:

1
let uiColor = myColor.uiColor()

这有点骇人听闻,但这至少是一些事情,直到我们得到一个有效的方法为止。这里的键是self.description,它给出颜色的十六进制描述(如果不是动态的,我应该添加)。剩下的只是获取颜色分量并创建UIColor的计算。


目前,SwiftUI API中没有直接提供此功能。但是,我设法使用调试打印和dump进行了临时初始化。我发现所有其他解决方案都无法说明从名称,捆绑,.displayP3颜色空间,UIColor,静态系统Color或任何不透明性改变的颜色初始化的Color。我的解决方案解决了上述所有问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
fileprivate struct ColorConversionError: Swift.Error {
    let reason: String
}

extension Color {

    @available(*, deprecated, message:"This is fragile and likely to break at some point. Hopefully it won't be required for long.")
    var uiColor: UIColor {
        do {
            return try convertToUIColor()
        } catch let error {
            assertionFailure((error as! ColorConversionError).reason)
            return .black
        }
    }
}

fileprivate extension Color {

    var stringRepresentation: String { description.trimmingCharacters(in: .whitespacesAndNewlines) }
    var internalType: String {"\\(type(of: Mirror(reflecting: self).children.first!.value))".replacingOccurrences(of:"ColorBox<(.+)>", with:"$1", options: .regularExpression) }

    func convertToUIColor() throws -> UIColor  {
        if let color = try OpacityColor(color: self) {
            return try UIColor.from(swiftUIDescription: color.stringRepresentation, internalType: color.internalType).multiplyingAlphaComponent(by: color.opacityModifier)
        }
        return try UIColor.from(swiftUIDescription: stringRepresentation, internalType: internalType)
    }
}

fileprivate struct OpacityColor {

    let stringRepresentation: String
    let internalType: String
    let opacityModifier: CGFloat

    init(stringRepresentation: String, internalType: String, opacityModifier: CGFloat) {
        self.stringRepresentation = stringRepresentation
        self.internalType = internalType
        self.opacityModifier = opacityModifier
    }

    init?(color: Color) throws {
        guard color.internalType =="OpacityColor" else {
            return nil
        }
        let string = color.stringRepresentation

        let opacityRegex = try! NSRegularExpression(pattern: #"(\\d+% )"#)
        let opacityLayerCount = opacityRegex.numberOfMatches(in: string, options: [], range: NSRange(string.startIndex..<string.endIndex, in: string))
        var dumpStr =""
        dump(color, to: &dumpStr)
        dumpStr = dumpStr.replacingOccurrences(of: #"^(?:.*\
){\\#(4 * opacityLayerCount)}.*?base:"#, with:"", options: .regularExpression)

        let opacityModifier = dumpStr.split(separator:"\
")
            .suffix(1)
            .lazy
            .map { $0.replacingOccurrences(of: #"\\s+-\\s+opacity:"#, with:"", options: .regularExpression) }
            .map { CGFloat(Double($0)!) }
            .reduce(1, *)

        let internalTypeRegex = try! NSRegularExpression(pattern: #"^.*\
.*ColorBox<.*?([A-Za-z0-9]+)>"#)
        let matches = internalTypeRegex.matches(in: dumpStr, options: [], range: NSRange(dumpStr.startIndex..<dumpStr.endIndex, in: dumpStr))
        guard let match = matches.first, matches.count == 1, match.numberOfRanges == 2 else {
            throw ColorConversionError(reason:"Could not parse internalType from \"\\(dumpStr)\"")
            try! self.init(color: Color.black.opacity(1))
        }

        self.init(
            stringRepresentation: String(dumpStr.prefix { !$0.isNewline }),
            internalType: String(dumpStr[Range(match.range(at: 1), in: dumpStr)!]),
            opacityModifier: opacityModifier
        )
    }
}

fileprivate extension UIColor {

    static func from(swiftUIDescription description: String, internalType: String) throws -> UIColor {
        switch internalType {
        case"SystemColorType":
            guard let uiColor = UIColor.from(systemColorName: description) else {
                throw ColorConversionError(reason:"Could not parse SystemColorType from \"\\(description)\"")
            }

            return uiColor

        case"_Resolved":
            guard description.range(of:"^#[0-9A-F]{8}$", options: .regularExpression) != nil else {
                throw ColorConversionError(reason:"Could not parse hex from \"\\(description)\"")
            }

            let components = description
                .dropFirst()
                .chunks(of: 2)
                .compactMap { CGFloat.decimalFromHexPair(String($0)) }

            guard components.count == 4, let cgColor = CGColor(colorSpace: CGColorSpace(name: CGColorSpace.linearSRGB)!, components: components) else {
                throw ColorConversionError(reason:"Could not parse hex from \"\\(description)\"")
            }

            return UIColor(cgColor: cgColor)

        case"UIColor":
            let sections = description.split(separator:"")
            let colorSpace = String(sections[0])
            let components = sections[1...]
                .compactMap { Double($0) }
                .map { CGFloat($0) }

            guard components.count == 4 else {
                throw ColorConversionError(reason:"Could not parse UIColor components from \"\\(description)\"")
            }
            let (r, g, b, a) = (components[0], components[1], components[2], components[3])
            return try UIColor(red: r, green: g, blue: b, alpha: a, colorSpace: colorSpace)

        case"DisplayP3":
            let regex = try! NSRegularExpression(pattern: #"^DisplayP3\\(red: (-?\\d+(?:\\.\\d+)?), green: (-?\\d+(?:\\.\\d+)?), blue: (-?\\d+(?:\\.\\d+)?), opacity: (-?\\d+(?:\\.\\d+)?)"#)
            let matches = regex.matches(in: description, options: [], range: NSRange(description.startIndex..<description.endIndex, in: description))
            guard let match = matches.first, matches.count == 1, match.numberOfRanges == 5 else {
                throw ColorConversionError(reason:"Could not parse DisplayP3 from \"\\(description)\"")
            }

            let components = (0..<match.numberOfRanges)
                .dropFirst()
                .map { Range(match.range(at: $0), in: description)! }
                .compactMap { Double(String(description[$0])) }
                .map { CGFloat($0) }

            guard components.count == 4 else {
                throw ColorConversionError(reason:"Could not parse DisplayP3 components from \"\\(description)\"")
            }

            let (r, g, b, a) = (components[0], components[1], components[2], components[3])
            return UIColor(displayP3Red: r, green: g, blue: b, alpha: a)

        case"NamedColor":
            guard description.range(of: #"^NamedColor\\(name:"(.*)", bundle: .*\\)$"#, options: .regularExpression) != nil else {
                throw ColorConversionError(reason:"Could not parse NamedColor from \"\\(description)\"")
            }

            let nameRegex = try! NSRegularExpression(pattern: #"name:"(.*)""#)
            let name = nameRegex.matches(in: description, options: [], range: NSRange(description.startIndex..<description.endIndex, in: description))
                .first
                .flatMap { Range($0.range(at: 1), in: description) }
                .map { String(description[$0]) }

            guard let colorName = name else {
                throw ColorConversionError(reason:"Could not parse NamedColor name from \"\\(description)\"")
            }

            let bundleRegex = try! NSRegularExpression(pattern: #"bundle: .*NSBundle <(.*)>"#)
            let bundlePath = bundleRegex.matches(in: description, options: [], range: NSRange(description.startIndex..<description.endIndex, in: description))
                .first
                .flatMap { Range($0.range(at: 1), in: description) }
                .map { String(description[$0]) }
            let bundle =  bundlePath.map { Bundle(path: $0)! }

            return UIColor(named: colorName, in: bundle, compatibleWith: nil)!

        default:
            throw ColorConversionError(reason:"Unhandled type \"\\(internalType)\"")
        }
    }

    static func from(systemColorName: String) -> UIColor? {
        switch systemColorName {
        case"clear": return .clear
        case"black": return .black
        case"white": return .white
        case"gray": return .systemGray
        case"red": return .systemRed
        case"green": return .systemGreen
        case"blue": return .systemBlue
        case"orange": return .systemOrange
        case"yellow": return .systemYellow
        case"pink": return .systemPink
        case"purple": return .systemPurple
        case"primary": return .label
        case"secondary": return .secondaryLabel
        default: return nil
        }
    }

    convenience init(red: CGFloat, green: CGFloat, blue: CGFloat, alpha: CGFloat, colorSpace: String) throws {
        if colorSpace =="UIDisplayP3ColorSpace" {
            self.init(displayP3Red: red, green: green, blue: blue, alpha: alpha)
        } else if colorSpace =="UIExtendedSRGBColorSpace" {
            self.init(red: red, green: green, blue: blue, alpha: alpha)
        } else if colorSpace =="kCGColorSpaceModelRGB" {
            let colorSpace = CGColorSpace(name: CGColorSpace.linearSRGB)!
            let components = [red, green, blue, alpha]
            let cgColor = CGColor(colorSpace: colorSpace, components: components)!
            self.init(cgColor: cgColor)
        } else {
            throw ColorConversionError(reason:"Unhandled colorSpace \"\\(colorSpace)\"")
        }
    }

    func multiplyingAlphaComponent(by multiplier: CGFloat?) -> UIColor {
        var a: CGFloat = 0
        getWhite(nil, alpha: &a)
        return withAlphaComponent(a * (multiplier ?? 1))
    }
}


// MARK: Helper extensions

extension StringProtocol {

    func chunks(of size: Int) -> [Self.SubSequence] {
        stride(from: 0, to: count, by: size).map {
            let start = index(startIndex, offsetBy: $0)
            let end = index(start, offsetBy: size, limitedBy: endIndex) ?? endIndex
            return self[start..<end]
        }
    }
}

extension Int {

    init?(hexString: String) {
        self.init(hexString, radix: 16)
    }
}

extension FloatingPoint {

    static func decimalFromHexPair(_ hexPair: String) -> Self? {
        guard hexPair.count == 2, let value = Int(hexString: hexPair) else {
            return nil
        }
        return Self(value) / Self(255)
    }
}

注意:虽然这不是解决当前问题的长期解决方案,因为它取决于Color的实现细节,该实现细节可能会在某个时候发生变化,但对于大多数(即使不是全部)颜色,它应该可以临时使用。


这不是SwiftUI的工作方式。您正在尝试做的非常类似于UIKit。在SwiftUI中,您很少询问任何参数的视图。目前,Color没有任何返回其RGB值的方法或属性。我怀疑是否还会存在。

通常,使用SwiftUI,您需要转到源代码,即首先用于创建颜色的变量。例如:

1
2
3
4
  let r = 0.9
  let g = 0.4
  let b = 0.7
  let mycolor = Color(red: r, green: g, b, opacity: o)

没有这样的东西:

1
let green = mycolor.greenComponent()

相反,您需要检查变量g(用于创建颜色的变量):

1
let green = g

我知道这听起来很奇怪,但这就是框架的设计方式。可能需要一段时间才能使用它,但最终您会。

您可能会问,但是如果mycolor创建为:

1
let mycolor = Color.red

在这种情况下,您很不走运:-(