J'essaye de changer une couleur SwiftUI en une instance d'UIColor.
Je peux facilement obtenir le RGBA de l'UIColor, mais je ne sais pas comment obtenir l'instance "Color" pour renvoyer les valeurs RVB et d'opacité correspondantes.
@EnvironmentObject var colorStore: ColorStore init() { let red = //get red value from colorStore.primaryThemeColor let green = //get green value from colorStore.primaryThemeColor let blue = //get blue value from colorStore.primaryThemeColor let alpha = //get alpha value from colorStore.primaryThemeColor let color = UIColor(red: red, green: green, blue: blue, alpha: alpha) UINavigationBar.appearance().tintColor = color }
... ou peut-être existe-t-il une meilleure façon d'accomplir ce que je recherche?
5 Réponses :
Ce n'est pas ainsi que SwiftUI fonctionne. Ce que vous essayez de faire est très similaire à UIKit. Dans SwiftUI, vous interrogez rarement une vue pour un paramètre. Pour le moment, Color
n'a pas de méthode ou de propriétés qui renvoient ses valeurs RVB. Et je doute qu'il y en ait jamais.
En général, avec SwiftUI, vous devez accéder à la source, c'est-à-dire à la variable que vous avez utilisée pour créer la couleur en premier lieu. Par exemple:
let mycolor = Color.red
Il n'y a rien de tel:
let green = g
Au lieu de cela, vous devez vérifier la variable g
(la variable que vous avez utilisée pour créer la couleur):
let green = mycolor.greenComponent()
Je sais que cela semble étrange, mais c'est ainsi que le cadre a été conçu. Cela peut prendre du temps pour s'y habituer, mais vous finirez par le faire.
Vous pouvez demander, mais que faire si mycolor a été créé comme:
let r = 0.9 let g = 0.4 let b = 0.7 let mycolor = Color(red: r, green: g, b, opacity: o)
Dans ce cas particulier, vous n'avez pas de chance :-(
Hmm, donc les couleurs de mon application changent dynamiquement à partir d'un magasin qui ne renvoie que des objets Color. Donc, même si la solution que j'ai essayée ne fonctionne pas, y a-t-il un autre moyen de changer la teinte de la UINavigationBar en valeur de couleur dynamique?
UINavigationBar est UIKit. Vous devez attendre que SwiftUI lui-même donne accès à la barre de navigation.
Et cette solution?
let uiColor = myColor.uiColor()
Usage:
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) } }
C'est un peu un hack, mais c'est au moins quelque chose jusqu'à ce que nous obtenions une méthode valide pour cela. La clé ici est self.description
qui donne une description hexadécimale de la couleur (si ce n'est pas dynamique, je devrais ajouter). Et le reste n'est que des calculs pour obtenir les composants de couleur et créer un UIColor
.
Cela peut être piraté, mais c'est la bonne réponse à mon avis. Il y a certainement des raisons de vouloir interroger le type Color. Par exemple, j'ai une extension sur Color qui renvoie une couleur de texte appropriée pour la couleur donnée comme arrière-plan. Quand tout ce avec quoi je dois travailler pour commencer, c'est la var accentColor: Color , alors je dois pouvoir interroger le maquillage de cette couleur. Voté pour la seule créativité et j'espère qu'Apple aura plus à ajouter à cette année prochaine.
NB: cela ne fonctionne pas si vous faites référence à une couleur dans un catalogue d'actifs (où .description
"NamedColor(name: \"Dark Roasts/Espresso100\", bundle: nil)"
ou similaire)
cela ne fonctionne pas si la couleur est .red
, la variable de result
renvoie false dans ce cas mais je ne sais pas pourquoi.
@JAHelia C'est parce que Color.red
est dynamique, c'est-à-dire qu'il change en fonction du mode actuel. Lorsque le mode sombre est activé, le rouge devient également plus sombre. Donc, il n'a pas de valeur unique, c'est pourquoi result
renvoie false.
Actuellement, cela n'est pas directement disponible dans l'API SwiftUI. Cependant, j'ai réussi à faire fonctionner un initialiseur de fortune qui utilise des impressions de débogage et un dump
. J'ai trouvé que toutes les autres solutions ne .displayP3
pas compte d'une Color
initialisée à partir d'un nom, d'un bundle, de l'espace colorimétrique .displayP3
, d'un UIColor
, d'un système statique Color
ou de toute couleur dont l'opacité était modifiée. Ma solution tient compte de toutes les baisses susmentionnées.
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) } }
Remarque: bien que ce ne soit pas une solution à long terme pour le problème actuel, car il dépend des détails de mise en œuvre de Color
qui peuvent changer à un moment donné, cela devrait fonctionner entre-temps pour la plupart des couleurs, sinon toutes.
Il existe un nouvel initialiseur qui prend une Color
et renvoie un UIColor
pour iOS ou NSColor
pour macOS maintenant. Donc:
UIColor(Color.red).cgColor /* For iOS */ NSColor(Color.red).cgColor /* For macOS */
NSColor(Color.red)
UIColor(Color.red)
Si vous recherchez des composants de couleur, vous pouvez trouver mes extensions utiles ici dans cette réponse
les graphiques de base semblent être faux. Faire exactement cela renvoie "Impossible d'appeler l'initialiseur pour le type 'CGColor' avec une liste d'arguments de type '(Couleur)'"
@turingtested Mise à jour de votre réponse pour vous débarrasser du long crash de tuple.
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) } }
Faites-moi savoir si vous trouvez un moyen. Je suis confronté au même problème. Je commence par swiftui mais il y a des endroits où j'ai besoin d'UIColor donc ce serait super d'avoir une extension à lancer entre eux comme avec UIColor-CGColor