How can I change a SwiftUI Color to UIColor?

后端 未结 5 997
醉酒成梦
醉酒成梦 2021-01-07 17:02

I\'m trying to change a SwiftUI Color to an instance of UIColor.

I can easily get the RGBA from the UIColor, but I don\'t know how to get the "Color" instan

5条回答
  •  夕颜
    夕颜 (楼主)
    2021-01-07 17:22

    Currently, this is not directly made available in the SwiftUI API. However, I managed to get a makeshift initializer working that utilizes debug prints and dump. I found that all of the other solutions failed to account for a Color initialized from a name, a bundle, the .displayP3 color space, a UIColor, a static system Color, or any color whose opacity altered. My solution accounts for all of the aforementioned downfalls.

    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.."#)
            let matches = internalTypeRegex.matches(in: dumpStr, options: [], range: NSRange(dumpStr.startIndex.. 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.."#)
                let bundlePath = bundleRegex.matches(in: description, options: [], range: NSRange(description.startIndex.. 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.. Self? {
            guard hexPair.count == 2, let value = Int(hexString: hexPair) else {
                return nil
            }
            return Self(value) / Self(255)
        }
    }
    

    Note: While this is not a long-term solution for the problem at hand since it hinges on the implementation details of Color which may change at some point, it should work in the interim for most, if not all colors.

提交回复
热议问题