Skip to content

Commit bec36ca

Browse files
committed
feat(colors): add support for both DynamicColorIOS and PlatformColor
1 parent 46956cc commit bec36ca

File tree

5 files changed

+161
-110
lines changed

5 files changed

+161
-110
lines changed

RNSwiftUI.podspec

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ package = JSON.parse(File.read(File.join(__dir__, "package.json")))
44
folly_config = get_folly_config()
55
folly_compiler_flags = folly_config[:compiler_flags]
66

7-
Pod::UI.puts "[react-native-swiftui] Thank you for using react-native-swiftui ❤️! If you enjoy it, please consider sponsoring at https://github.com/sponsors/mgcrea"
7+
Pod::UI.puts "[react-native-swiftui] Thank you for using react-native-swiftui ❤️ ! If you enjoy it, please consider sponsoring at https://github.com/sponsors/mgcrea"
88

99
Pod::Spec.new do |s|
1010
s.name = "RNSwiftUI"

ios/components/StyleProps.swift

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,11 @@ import SwiftUI
22

33
public struct StyleProps: Decodable {
44
// ViewStyle
5-
public var color: Color? // alias for foregroundColor
6-
public var accentColor: Color?
7-
public var tintColor: Color?
8-
public var foregroundColor: Color?
9-
public var backgroundColor: Color?
5+
public var color: ColorValue? // alias for foregroundColor
6+
public var accentColor: ColorValue?
7+
public var tintColor: ColorValue?
8+
public var foregroundColor: ColorValue?
9+
public var backgroundColor: ColorValue?
1010
public var preferredColorScheme: ColorScheme?
1111
// - Frame
1212
public var width: Size?
@@ -30,7 +30,7 @@ public struct StyleProps: Decodable {
3030
public var paddingTop: CGFloat?
3131
public var paddingBottom: CGFloat?
3232
// - Border
33-
public var borderColor: Color?
33+
public var borderColor: ColorValue?
3434
public var borderWidth: CGFloat?
3535
public var borderRadius: CGFloat?
3636
public var cornerRadius: CGFloat?
@@ -49,11 +49,11 @@ public struct StyleProps: Decodable {
4949
let container = try decoder.container(keyedBy: CodingKeys.self)
5050

5151
// ViewStyle
52-
color = try container.decodeColorIfPresent(forKey: .color) // alias for foregroundColor
53-
accentColor = try container.decodeColorIfPresent(forKey: .accentColor)
54-
tintColor = try container.decodeColorIfPresent(forKey: .tintColor)
55-
foregroundColor = try container.decodeColorIfPresent(forKey: .foregroundColor)
56-
backgroundColor = try container.decodeColorIfPresent(forKey: .backgroundColor)
52+
color = try container.decodeIfPresent(ColorValue.self, forKey: .color) // alias for foregroundColor
53+
accentColor = try container.decodeIfPresent(ColorValue.self, forKey: .accentColor)
54+
tintColor = try container.decodeIfPresent(ColorValue.self, forKey: .tintColor)
55+
foregroundColor = try container.decodeIfPresent(ColorValue.self, forKey: .foregroundColor)
56+
backgroundColor = try container.decodeIfPresent(ColorValue.self, forKey: .backgroundColor)
5757
preferredColorScheme = try container.decodeColorSchemeIfPresent(forKey: .preferredColorScheme)
5858
// - Frame
5959
width = try container.decodeIfPresent(Size.self, forKey: .width)
@@ -77,7 +77,7 @@ public struct StyleProps: Decodable {
7777
paddingTop = try container.decodeIfPresent(CGFloat.self, forKey: .paddingTop)
7878
paddingBottom = try container.decodeIfPresent(CGFloat.self, forKey: .paddingBottom)
7979
// - Border
80-
borderColor = try container.decodeColorIfPresent(forKey: .borderColor)
80+
borderColor = try container.decodeIfPresent(ColorValue.self, forKey: .borderColor)
8181
borderWidth = try container.decodeIfPresent(CGFloat.self, forKey: .borderWidth)
8282
borderRadius = try container.decodeIfPresent(CGFloat.self, forKey: .borderRadius) // alias for cornerRadius
8383
cornerRadius = try container.decodeIfPresent(CGFloat.self, forKey: .cornerRadius)
@@ -92,12 +92,12 @@ public struct StyleProps: Decodable {
9292
}
9393

9494
extension KeyedDecodingContainer {
95-
func decodeColorIfPresent(forKey key: Key) throws -> Color? {
96-
if let colorString = try decodeIfPresent(String.self, forKey: key) {
97-
return Color(fromString: colorString)
98-
}
99-
return nil
100-
}
95+
// func decodeColorIfPresent(forKey key: Key) throws -> Color? {
96+
// if let colorString = try decodeIfPresent(String.self, forKey: key) {
97+
// return Color(fromString: colorString)
98+
// }
99+
// return nil
100+
// }
101101

102102
func decodeColorSchemeIfPresent(forKey key: Key) throws -> ColorScheme? {
103103
if let colorScheme = try decodeIfPresent(String.self, forKey: key) {

ios/enums/ColorValue.swift

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import Foundation
2+
3+
public enum ColorValue: Decodable {
4+
case string(String)
5+
case semantic(SemanticColor)
6+
case dynamic(DynamicColor)
7+
8+
// Represents a semantic color, e.g., {"semantic": ["systemGreen"]}
9+
public struct SemanticColor: Decodable {
10+
let semantic: [String]
11+
}
12+
13+
public enum ColorVariant: Decodable {
14+
case string(String)
15+
case semantic(SemanticColor)
16+
17+
public init(from decoder: Decoder) throws {
18+
let container = try decoder.singleValueContainer()
19+
if let str = try? container.decode(String.self) {
20+
self = .string(str)
21+
} else if let semantic = try? container.decode(SemanticColor.self) {
22+
self = .semantic(semantic)
23+
} else {
24+
throw DecodingError.dataCorruptedError(in: container, debugDescription: "Invalid color variant")
25+
}
26+
}
27+
}
28+
29+
// Represents the light/dark pair, e.g., {"light": {...}, "dark": {...}}
30+
public struct DynamicColor: Decodable {
31+
let light: ColorVariant
32+
let dark: ColorVariant
33+
}
34+
35+
// Handles the wrapper, e.g., {"dynamic": {...}}
36+
private struct DynamicWrapper: Decodable {
37+
let dynamic: DynamicColor
38+
}
39+
40+
public init(from decoder: Decoder) throws {
41+
let container = try decoder.singleValueContainer()
42+
if let str = try? container.decode(String.self) {
43+
self = .string(str)
44+
} else if let semantic = try? container.decode(SemanticColor.self) {
45+
self = .semantic(semantic)
46+
} else if let dynamicWrapper = try? container.decode(DynamicWrapper.self) {
47+
self = .dynamic(dynamicWrapper.dynamic)
48+
} else {
49+
throw DecodingError.dataCorruptedError(in: container, debugDescription: "Invalid color value")
50+
}
51+
}
52+
}

ios/extensions/Color+Parsing.swift

Lines changed: 83 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,89 @@
11

22
import SwiftUI
33

4+
extension ColorValue.ColorVariant {
5+
var asColorValue: ColorValue {
6+
switch self {
7+
case let .string(str):
8+
return .string(str)
9+
case let .semantic(semantic):
10+
return .semantic(semantic)
11+
}
12+
}
13+
}
14+
15+
extension Color {
16+
// Existing hex parser (if not present, add this)
17+
init?(hex: String) {
18+
let hex = hex.trimmingCharacters(in: CharacterSet.alphanumerics.inverted)
19+
var int: UInt64 = 0
20+
Scanner(string: hex).scanHexInt64(&int)
21+
let a, r, g, b: UInt64
22+
switch hex.count {
23+
case 3: // RGB (12-bit)
24+
(a, r, g, b) = (255, (int >> 8) * 17, (int >> 4 & 0xF) * 17, (int & 0xF) * 17)
25+
case 6: // RGB (24-bit)
26+
(a, r, g, b) = (255, int >> 16, int >> 8 & 0xFF, int & 0xFF)
27+
case 8: // ARGB (32-bit)
28+
(a, r, g, b) = (int >> 24, int >> 16 & 0xFF, int >> 8 & 0xFF, int & 0xFF)
29+
default:
30+
return nil
31+
}
32+
self.init(
33+
.sRGB,
34+
red: Double(r) / 255,
35+
green: Double(g) / 255,
36+
blue: Double(b) / 255,
37+
opacity: Double(a) / 255
38+
)
39+
}
40+
41+
// New initializer for ColorValue
42+
init(value: ColorValue) {
43+
switch value {
44+
case let .string(str):
45+
// Handle string colors (hex or asset catalog)
46+
if let uiColor = UIColor(named: str) {
47+
self = Color(uiColor)
48+
} else if let hexColor = Color(hex: str) { // Hex color
49+
self = hexColor
50+
} else {
51+
self = .clear // Fallback
52+
}
53+
case let .semantic(names):
54+
// Use first valid system color
55+
for name in names.semantic {
56+
if let uiColor = UIColor.systemColor(named: name) {
57+
self = Color(uiColor)
58+
return
59+
}
60+
}
61+
self = .clear // Fallback
62+
case let .dynamic(dynamic):
63+
let lightColor = Color(value: dynamic.light.asColorValue)
64+
let darkColor = Color(value: dynamic.dark.asColorValue)
65+
self = Color(uiColor: UIColor { traitCollection in
66+
traitCollection.userInterfaceStyle == .dark ? UIColor(darkColor) : UIColor(lightColor)
67+
})
68+
}
69+
}
70+
}
71+
72+
extension UIColor {
73+
static func systemColor(named name: String) -> UIColor? {
74+
switch name {
75+
case "systemGreen": return .systemGreen
76+
case "systemBlue": return .systemBlue
77+
case "systemOrange": return .systemOrange
78+
case "systemRed": return .systemRed
79+
case "systemYellow": return .systemYellow
80+
case "label": return .label
81+
// Add more system colors as needed
82+
default: return nil
83+
}
84+
}
85+
}
86+
487
extension Color {
588
// Backgrounds
689
static let systemBackground = Color(UIColor.systemBackground)
@@ -41,88 +124,4 @@ extension Color {
41124
static let systemGray4 = Color(UIColor.systemGray4)
42125
static let systemGray5 = Color(UIColor.systemGray5)
43126
static let systemGray6 = Color(UIColor.systemGray6)
44-
45-
private static let namedColors: [String: Color] = [
46-
"accentcolor": .accentColor,
47-
"black": .black,
48-
"blue": .blue,
49-
"brown": .brown,
50-
"clear": .clear,
51-
"cyan": .cyan,
52-
"gray": .gray,
53-
"green": .green,
54-
"indigo": .indigo,
55-
"mint": .mint,
56-
"orange": .orange,
57-
"pink": .pink,
58-
"primary": .primary,
59-
"purple": .purple,
60-
"red": .red,
61-
"secondary": .secondary,
62-
"teal": .teal,
63-
"white": .white,
64-
"yellow": .yellow,
65-
// System colors
66-
"systemBackground": .systemBackground,
67-
"secondarySystemBackground": .secondarySystemBackground,
68-
"tertiarySystemBackground": .tertiarySystemBackground,
69-
"systemGroupedBackground": .systemGroupedBackground,
70-
"secondarySystemGroupedBackground": .secondarySystemGroupedBackground,
71-
"tertiarySystemGroupedBackground": .tertiarySystemGroupedBackground,
72-
"label": .label,
73-
"secondaryLabel": .secondaryLabel,
74-
"tertiaryLabel": .tertiaryLabel,
75-
"quaternaryLabel": .quaternaryLabel,
76-
"placeholderText": .placeholderText,
77-
"systemFill": .systemFill,
78-
"secondarySystemFill": .secondarySystemFill,
79-
"tertiarySystemFill": .tertiarySystemFill,
80-
"quaternarySystemFill": .quaternarySystemFill,
81-
"systemRed": .systemRed,
82-
"systemBlue": .systemBlue,
83-
"systemGreen": .systemGreen,
84-
"systemOrange": .systemOrange,
85-
"systemYellow": .systemYellow,
86-
"systemPink": .systemPink,
87-
"systemPurple": .systemPurple,
88-
"systemTeal": .systemTeal,
89-
"systemIndigo": .systemIndigo,
90-
"systemGray": .systemGray,
91-
"systemGray2": .systemGray2,
92-
"systemGray3": .systemGray3,
93-
"systemGray4": .systemGray4,
94-
"systemGray5": .systemGray5,
95-
"systemGray6": .systemGray6,
96-
]
97-
private static func getNamedColor(_ color: String) -> Color? {
98-
let colorSanitized = color.trimmingCharacters(in: .whitespacesAndNewlines)
99-
if let namedColor = namedColors[colorSanitized] {
100-
return namedColor
101-
}
102-
return nil
103-
}
104-
105-
init?(fromString color: String) {
106-
if let namedColor = Self.getNamedColor(color) {
107-
self = namedColor
108-
} else if let hexColor = Self(hex: color) {
109-
self = hexColor
110-
} else {
111-
return nil
112-
}
113-
}
114-
115-
init?(hex: String) {
116-
var hexSanitized = hex.trimmingCharacters(in: .whitespacesAndNewlines)
117-
hexSanitized = hexSanitized.replacingOccurrences(of: "#", with: "")
118-
119-
var rgb: UInt64 = 0
120-
guard Scanner(string: hexSanitized).scanHexInt64(&rgb) else { return nil }
121-
122-
let red = Double((rgb & 0xFF0000) >> 16) / 255.0
123-
let green = Double((rgb & 0x00FF00) >> 8) / 255.0
124-
let blue = Double(rgb & 0x0000FF) / 255.0
125-
126-
self.init(red: red, green: green, blue: blue)
127-
}
128127
}

ios/extensions/View+Styling.swift

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -78,15 +78,15 @@ extension View {
7878
return
7979
AnyView(
8080
applyBoxStyles(style)
81-
.applyIf(style.backgroundColor != nil) { $0.background(style.backgroundColor!) }
82-
.applyIf(style.color != nil || style.foregroundColor != nil) { $0.foregroundStyle(style.color ?? style.foregroundColor!) }
83-
.applyIf(style.accentColor != nil) { $0.accentColor(style.accentColor!) }
84-
.applyIf(style.tintColor != nil) { $0.tint(style.tintColor!) }
81+
.applyIf(style.backgroundColor != nil) { $0.background(Color(value: style.backgroundColor!)) }
82+
.applyIf(style.color != nil || style.foregroundColor != nil) { $0.foregroundStyle(Color(value: style.color ?? style.foregroundColor!)) }
83+
.applyIf(style.accentColor != nil) { $0.accentColor(Color(value: style.accentColor!)) }
84+
.applyIf(style.tintColor != nil) { $0.tint(Color(value: style.tintColor!)) }
8585
.applyIf(style.preferredColorScheme != nil) { $0.preferredColorScheme(style.preferredColorScheme!) }
8686
.applyIf(style.borderWidth != nil) { view in
8787
view.overlay(
8888
RoundedRectangle(cornerRadius: style.cornerRadius ?? style.borderRadius ?? 0)
89-
.stroke(style.borderColor ?? .black, lineWidth: style.borderWidth!)
89+
.stroke(style.borderColor != nil ? Color(value: style.borderColor!) : .black, lineWidth: style.borderWidth!)
9090
)
9191
}
9292
.applyIf(style.cornerRadius ?? style.borderRadius != nil) {
@@ -96,7 +96,7 @@ extension View {
9696
}
9797

9898
private func applyTextStyles(_ style: StyleProps) -> some View {
99-
return applyIf(style.color != nil) { $0.foregroundStyle(style.color!).tint(style.color!) }
99+
return applyIf(style.color != nil) { $0.foregroundStyle(Color(value: style.color!)).tint(Color(value: style.color!)) }
100100
.applyIf(style.font != nil) { $0.font(style.font!) }
101101
.applyIf(style.fontFamily != nil) { $0.font(.custom(style.fontFamily!, size: style.fontSize ?? 17)) }
102102
.applyIf(style.fontFamily == nil && style.fontSize != nil) { $0.font(.system(size: style.fontSize!)) }
@@ -126,7 +126,7 @@ extension Shape {
126126
func applyShapeStyles(_ style: StyleProps?) -> some View {
127127
guard let style = style else { return AnyView(self) }
128128
return AnyView(
129-
applyIf(style.backgroundColor != nil) { $0.fill(style.backgroundColor!) }
129+
applyIf(style.backgroundColor != nil) { $0.fill(Color(value: style.backgroundColor!)) }
130130
.applyBoxStyles(style))
131131
}
132132
}

0 commit comments

Comments
 (0)