Skip to content

Commit 630bb9e

Browse files
committed
Picker: add radioGroup picker style for macOS
radioGroup was somehow missed when originally implementing Picker view Bonus: horizontalRadioGroupLayout option
1 parent e71cd89 commit 630bb9e

4 files changed

Lines changed: 65 additions & 5 deletions

File tree

ActionUI/Views/Picker.swift

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@
88
"options": [ "One", "Two", "Three" ] // Required. Two supported formats:
99
// 1. With simple array of strings we have titles only. Tags are automatically "1", "2", "3"... (1-based index as String)
1010
// 2. With array of dictionaries we have explicit control: [{"title": "Sure Thing", "tag": "yes"}, {"title": "Absolutely Not", "tag": "no"}]
11-
"pickerStyle": "menu", // Optional: "menu" (iOS/macOS/visionOS), "segmented" (iOS/macOS/visionOS), "wheel" (iOS/visionOS only); no default
11+
"pickerStyle": "menu", // Optional: "menu" (iOS/macOS/visionOS), "segmented" (iOS/macOS/visionOS), "wheel" (iOS/visionOS only), "radioGroup" (macOS only); no default
12+
"horizontalRadioGroupLayout": false, // Optional: Bool, applies .horizontalRadioGroupLayout() when pickerStyle is "radioGroup" (macOS only); defaults to false
1213
"actionID": "picker.selection", // Optional: String for action triggered on user-initiated selection change (inherited from View)
1314
}
1415
// Note: actionID is triggered via onChange for user-initiated changes. Baseline View properties (padding, hidden, foregroundColor, font, background, frame, opacity, cornerRadius, disabled, etc.) are inherited and applied via ActionUIRegistry.shared.applyModifiers.
@@ -71,7 +72,7 @@ struct Picker: ActionUIViewConstruction {
7172

7273
// Validate pickerStyle
7374
#if os(macOS)
74-
let validStyles = ["menu", "segmented"]
75+
let validStyles = ["menu", "segmented", "radioGroup"]
7576
#else
7677
let validStyles = ["menu", "segmented", "wheel"]
7778
#endif
@@ -140,6 +141,15 @@ struct Picker: ActionUIViewConstruction {
140141
modifiedView = modifiedView.pickerStyle(.menu)
141142
case "segmented":
142143
modifiedView = modifiedView.pickerStyle(.segmented)
144+
case "radioGroup":
145+
#if os(macOS)
146+
modifiedView = modifiedView.pickerStyle(.radioGroup)
147+
if properties["horizontalRadioGroupLayout"] as? Bool == true {
148+
modifiedView = modifiedView.horizontalRadioGroupLayout()
149+
}
150+
#else
151+
logger.log("radioGroup PickerStyle unavailable on this platform; ignoring", .warning)
152+
#endif
143153
default:
144154
break // Should not reach here due to validateProperties
145155
}

ActionUISwiftTestApp/Resources/Picker.json

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,31 @@
3535
"pickerStyle": "segmented",
3636
"actionID": "picker.selection"
3737
}
38+
},
39+
{
40+
"type": "Picker",
41+
"properties": {
42+
"title": "Radio Group",
43+
"options": [
44+
"Red",
45+
"Green",
46+
"Blue"
47+
],
48+
"pickerStyle": "radioGroup"
49+
}
50+
},
51+
{
52+
"type": "Picker",
53+
"properties": {
54+
"title": "Horizontal Radio",
55+
"options": [
56+
"Small",
57+
"Medium",
58+
"Large"
59+
],
60+
"pickerStyle": "radioGroup",
61+
"horizontalRadioGroupLayout": true
62+
}
3863
}
3964
]
4065
}

ActionUITests/Views/PickerTests.swift

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -134,12 +134,36 @@ final class PickerTests: XCTestCase {
134134
"options": ["Option1", "Option2"],
135135
"pickerStyle": "wheel"
136136
]
137-
137+
138138
let validated = Picker.validateProperties(properties, logger)
139-
139+
140140
XCTAssertNil(validated["pickerStyle"], "Wheel style should be nil on macOS")
141141
XCTAssertEqual(validated["options"] as? [String], ["Option1", "Option2"], "Options should remain valid")
142142
}
143+
144+
func testPickerRadioGroupStyleMacOS() {
145+
let properties: [String: Any] = [
146+
"options": ["Red", "Green", "Blue"],
147+
"pickerStyle": "radioGroup"
148+
]
149+
150+
let validated = Picker.validateProperties(properties, logger)
151+
152+
XCTAssertEqual(validated["pickerStyle"] as? String, "radioGroup", "radioGroup style should be valid on macOS")
153+
}
154+
155+
func testPickerRadioGroupHorizontalLayout() {
156+
let properties: [String: Any] = [
157+
"options": ["Small", "Medium", "Large"],
158+
"pickerStyle": "radioGroup",
159+
"horizontalRadioGroupLayout": true
160+
]
161+
162+
let validated = Picker.validateProperties(properties, logger)
163+
164+
XCTAssertEqual(validated["pickerStyle"] as? String, "radioGroup")
165+
XCTAssertEqual(validated["horizontalRadioGroupLayout"] as? Bool, true)
166+
}
143167
#endif
144168

145169
func testPickerActionHandling() throws {

Documentation/ActionUI-JSON-Specifications.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -750,7 +750,8 @@
750750
"options": [ "One", "Two", "Three" ] // Required. Two supported formats:
751751
// 1. With simple array of strings we have titles only. Tags are automatically "1", "2", "3"... (1-based index as String)
752752
// 2. With array of dictionaries we have explicit control: [{"title": "Sure Thing", "tag": "yes"}, {"title": "Absolutely Not", "tag": "no"}]
753-
"pickerStyle": "menu", // Optional: "menu" (iOS/macOS/visionOS), "segmented" (iOS/macOS/visionOS), "wheel" (iOS/visionOS only); no default
753+
"pickerStyle": "menu", // Optional: "menu" (iOS/macOS/visionOS), "segmented" (iOS/macOS/visionOS), "wheel" (iOS/visionOS only), "radioGroup" (macOS only); no default
754+
"horizontalRadioGroupLayout": false, // Optional: Bool, applies .horizontalRadioGroupLayout() when pickerStyle is "radioGroup" (macOS only); defaults to false
754755
"actionID": "picker.selection", // Optional: String for action triggered on user-initiated selection change (inherited from View)
755756
}
756757
// Note: actionID is triggered via onChange for user-initiated changes. Baseline View properties (padding, hidden, foregroundColor, font, background, frame, opacity, cornerRadius, disabled, etc.) are inherited and applied via ActionUIRegistry.shared.applyModifiers.

0 commit comments

Comments
 (0)