如何将SwiftUI颜色更改为UIColor?

Gav*_*sen 5 colors uicolor ios swiftui

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

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

@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
    }
Run Code Online (Sandbox Code Playgroud)

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

Moj*_*ini 40

SwiftUI 2.0

有一个新的初始化,需要一个Color和返回UIColor的iOSNSColor用于MacOS的现在。所以:

IOS

UIColor(Color.red)
Run Code Online (Sandbox Code Playgroud)

苹果系统

NSColor(Color.red)
Run Code Online (Sandbox Code Playgroud)

核心图形

UIColor(Color.red).cgColor /* For iOS */
NSColor(Color.red).cgColor /* For macOS */
Run Code Online (Sandbox Code Playgroud)

如果您正在寻找颜色组件,您可以在此答案中找到有用的扩展

另外,请查看如何将 UIColor 转换为 SwiftUI 的颜色


tur*_*ted 29

这个解决方案怎么样?

extension Color {
 
    func uiColor() -> UIColor {

        if #available(iOS 14.0, *) {
            return UIColor(self)
        }

        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)
    }
}
Run Code Online (Sandbox Code Playgroud)

用法:

let uiColor = myColor.uiColor()
Run Code Online (Sandbox Code Playgroud)

这有点黑客,但至少在我们获得有效方法之前它是一些东西。这里的关键是self.description它给出了颜色的十六进制描述(如果它不是动态的,我应该添加)。剩下的只是计算来获取颜色分量,并创建一个UIColor.

  • 注意:如果您引用资产目录中的颜色(其中 `.description` 产生 `"NamedColor(name: \"Dark Roasts/Espresso100\", bundle: nil)"` 或类似内容),则此方法不起作用 (7认同)
  • 如果颜色是“.red”,则这不起作用,“result”变量在这种情况下返回 false,但我不知道为什么。 (3认同)

Noa*_*der 9

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

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: #"^(?:.*\n){\#(4 * opacityLayerCount)}.*?base: "#, with: "", options: .regularExpression)

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

        let internalTypeRegex = try! NSRegularExpression(pattern: #"^.*\n.*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)
    }
}
Run Code Online (Sandbox Code Playgroud)

注意:虽然这不是手头问题的长期解决方案,因为它取决于Color可能会在某些时候改变的实现细节,但它应该在过渡期间适用于大多数颜色,如果不是所有颜色。


小智 5

@turingtested 更新了您的答案以摆脱长元组崩溃。

extension Color {
    func uiColor() -> UIColor {
        if #available(iOS 14.0, *) {
            return UIColor(self)
        }

        let scanner = Scanner(string: 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 UIColor(red: r, green: g, blue: b, alpha: a)
    }
}
Run Code Online (Sandbox Code Playgroud)


kon*_*iki -4

这不是 SwiftUI 的工作原理。你想做的事情非常像 UIKit。在 SwiftUI 中,您很少询问视图的任何参数。目前,Color没有任何方法或属性返回其 RGB 值。我怀疑永远不会有。

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

  let r = 0.9
  let g = 0.4
  let b = 0.7
  let mycolor = Color(red: r, green: g, b, opacity: o)
Run Code Online (Sandbox Code Playgroud)

没有这样的事情:

let green = mycolor.greenComponent()
Run Code Online (Sandbox Code Playgroud)

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

let green = g
Run Code Online (Sandbox Code Playgroud)

我知道这听起来很奇怪,但这就是框架的设计方式。可能需要一些时间来适应它,但你最终会的。

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

let mycolor = Color.red
Run Code Online (Sandbox Code Playgroud)

在这种特殊情况下,你运气不好:-(