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() |
这有点骇人听闻,但这至少是一些事情,直到我们得到一个有效的方法为止。这里的键是
目前,SwiftUI API中没有直接提供此功能。但是,我设法使用调试打印和
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) } } |
注意:虽然这不是解决当前问题的长期解决方案,因为它取决于
这不是SwiftUI的工作方式。您正在尝试做的非常类似于UIKit。在SwiftUI中,您很少询问任何参数的视图。目前,
通常,使用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() |
相反,您需要检查变量
1 | let green = g |
我知道这听起来很奇怪,但这就是框架的设计方式。可能需要一段时间才能使用它,但最终您会。
您可能会问,但是如果mycolor创建为:
1 | let mycolor = Color.red |
在这种情况下,您很不走运:-(