40

I would like to react on a choice of a user. Something similar to this example: 4 Radiobuttons

In a 2nd stage would I like to show additional content below each radiobutton, e.g. moving the buttons 2 and 3 from each other in order to give a list of websites for allowing.

So far I haven't found how to do this in SwiftUI. Many thanks in advance!

2
  • What have you tried so far? Commented Oct 28, 2019 at 5:01
  • I am only learning SwiftUI and this is my first need. My first idea was to create it all from Scratch as I cannot find examples. By now I decided to go with Cocoa instead as I cannot wait too long right now. :-( Commented Oct 28, 2019 at 19:39

7 Answers 7

56
Picker(selection: $order.avocadoStyle, label: Text("Avocado:")) { Text("Sliced").tag(AvocadoStyle.sliced) Text("Mashed").tag(AvocadoStyle.mashed) }.pickerStyle(RadioGroupPickerStyle()) 

This is the code from the 2019 swiftUI essentials keynote (SwiftUI Essentials - WWDC 2019. Around 43 minutes in the video they show this example.

It will look like this:

radio button group

Sign up to request clarification or add additional context in comments.

5 Comments

How to Create .radioGroup?
It’s a predefined type but it could be renamed to RadioGroupPickerStyle. .pickerStyle(RadioGroupPickerStyle())
'RadioGroupPickerStyle' has been explicitly marked unavailable here (SwiftUI.RadioGroupPickerStyle) I am getting above error
I think radio buttons are not natively supported for iOS.
Please note that RadioGroupPickerStyle is supported on Mac only (so no iOS)
52

check this out...an easy to use SwiftUI RadiobuttonGroup for iOS

you can use it like this:

RadioButtonGroup(items: ["Rome", "London", "Paris", "Berlin", "New York"], selectedId: "London") { selected in print("Selected is: \(selected)") } 

and here is the code:

struct ColorInvert: ViewModifier { @Environment(\.colorScheme) var colorScheme func body(content: Content) -> some View { Group { if colorScheme == .dark { content.colorInvert() } else { content } } } } struct RadioButton: View { @Environment(\.colorScheme) var colorScheme let id: String let callback: (String)->() let selectedID : String let size: CGFloat let color: Color let textSize: CGFloat init( _ id: String, callback: @escaping (String)->(), selectedID: String, size: CGFloat = 20, color: Color = Color.primary, textSize: CGFloat = 14 ) { self.id = id self.size = size self.color = color self.textSize = textSize self.selectedID = selectedID self.callback = callback } var body: some View { Button(action:{ self.callback(self.id) }) { HStack(alignment: .center, spacing: 10) { Image(systemName: self.selectedID == self.id ? "largecircle.fill.circle" : "circle") .renderingMode(.original) .resizable() .aspectRatio(contentMode: .fit) .frame(width: self.size, height: self.size) .modifier(ColorInvert()) Text(id) .font(Font.system(size: textSize)) Spacer() }.foregroundColor(self.color) } .foregroundColor(self.color) } } struct RadioButtonGroup: View { let items : [String] @State var selectedId: String = "" let callback: (String) -> () var body: some View { VStack { ForEach(0..<items.count) { index in RadioButton(self.items[index], callback: self.radioGroupCallback, selectedID: self.selectedId) } } } func radioGroupCallback(id: String) { selectedId = id callback(id) } } struct ContentView: View { var body: some View { HStack { Text("Example") .font(Font.headline) .padding() RadioButtonGroup(items: ["Rome", "London", "Paris", "Berlin", "New York"], selectedId: "London") { selected in print("Selected is: \(selected)") } }.padding() } } struct ContentView_Previews: PreviewProvider { static var previews: some View { ContentView() } } struct ContentViewDark_Previews: PreviewProvider { static var previews: some View { ContentView() .environment(\.colorScheme, .dark) .darkModeFix() } } 

enter image description here

1 Comment

Great answer! For my implementation I found it useful to use \@Binding for the selectedId, then I could use a \@State var in my app to hold the selectedId and pass it to the RadioButton group to be kept in sync.
14

I just edited @LizJ answer , by adding Binding instead of didTapActive & didTapInactive , so like that it will looks like other SwiftUI elements

import SwiftUI struct RadioButton: View { @Binding var checked: Bool //the variable that determines if its checked var body: some View { Group{ if checked { ZStack{ Circle() .fill(Color.blue) .frame(width: 20, height: 20) Circle() .fill(Color.white) .frame(width: 8, height: 8) }.onTapGesture {self.checked = false} } else { Circle() .fill(Color.white) .frame(width: 20, height: 20) .overlay(Circle().stroke(Color.gray, lineWidth: 1)) .onTapGesture {self.checked = true} } } } } 

Comments

10

I'm using swift4, Catalina OS and Xcode 11.2 and was having the issue where RadioGroupPickerStyle was unavailable for iOS and .radiogroup just didn't work (it froze in build) so I made my own that's reusable for other occasions. (notice its only the button so you have to handle the logic yourself.) Hope it helps!

import SwiftUI struct RadioButton: View { let ifVariable: Bool //the variable that determines if its checked let onTapToActive: ()-> Void//action when taped to activate let onTapToInactive: ()-> Void //action when taped to inactivate var body: some View { Group{ if ifVariable { ZStack{ Circle() .fill(Color.blue) .frame(width: 20, height: 20) Circle() .fill(Color.white) .frame(width: 8, height: 8) }.onTapGesture {self.onTapToInactive()} } else { Circle() .fill(Color.white) .frame(width: 20, height: 20) .overlay(Circle().stroke(Color.gray, lineWidth: 1)) .onTapGesture {self.onTapToActive()} } } } } 

TO USE: Put this in any file and you can use it as you would any other view anywhere else in the project. (we keep a global folder that has a buttons file in it)

Comments

2

I just wrote a swift package to handle the missing radio button view. Thanks to @LizJ for the starting idea!

Should be easy to use, especially with enums!

RadioButton

Comments

1

I will use the previous answer of @LizJ and i will add a text after the radio button to resemble (RadioListTile in Flutter)

struct RadioButton: View { let ifVariable: Bool //the variable that determines if its checked let radioTitle: String var onTapToActive: ()-> Void//action when taped to activate let onTapToInactive: ()-> Void //action when taped to inactivate var body: some View { Group{ if ifVariable { HStack(alignment: .center, spacing: 16) { ZStack{ Circle() .fill(AppColors.primaryColor) .frame(width: 20, height: 20) Circle() .fill(Color.white) .frame(width: 8, height: 8) }.onTapGesture {self.onTapToInactive()} Text(radioTitle) .font(.headline) } } else { HStack(alignment: .center, spacing: 16){ Circle() .fill(Color.white) .frame(width: 20, height: 20) .overlay(Circle().stroke(Color.gray, lineWidth: 1)) .onTapGesture {self.onTapToActive()} Text(radioTitle) .font(.headline) } } } } 

I will also provide an example for the selection logic we will create a enum for radio cases

enum PaymentMethod: Int { case undefined = 0 case credit = 1 case cash = 2 } 

then we will create @State variable to carry the selection, i will not recreate another SwiftUI view but only explain the basic concept without any boilerplate code

struct YourView: View { @State private var paymentMethod: PaymentMethod var body: some View { RadioButton(ifVariable: paymentMethod == PaymentMethod.credit,radioTitle: "Pay in Credit", onTapToActive: { paymentMethod = .credit }, onTapToInactive: {}) RadioButton(ifVariable: paymentMethod == PaymentMethod.cash,radioTitle: "Pay in Cash", onTapToActive: { paymentMethod = .cash }, onTapToInactive: {}) } } 

with this previous code you can toggle between radio buttons in SwiftUI with a text after each selection to resemble (RadioListTile in Flutter)

Comments

0

Here is my solution that supports IOS and MacOS, Elegant and Native-like Usage:

RadioButtonGroup(value: $selection) { Text("radio A") .radioTag("1") Text("radio B") .radioTag("2") } 

Snapshot: enter image description here

Here is the code:

// // RadioButtonGroup.swift // // Created by Frank Lin on 2025/1/21. // import SwiftUI struct Radio: View { @Binding var isSelected: Bool var len: CGFloat = 30 private var onTapReceive: TapReceiveAction? var outColor: Color { isSelected == true ? Color.blue : Color.gray } var innerRadius: CGFloat { isSelected == true ? 9 : 0 } var body: some View { Circle() .stroke(outColor, lineWidth: 1.5) .padding(4) .overlay() { if isSelected { Circle() .fill(Color.blue) .padding(innerRadius) .animation(.easeInOut(duration: 2), value: innerRadius) } else { EmptyView() } } .frame(width: len, height: len) .onTapGesture { withAnimation { isSelected.toggle() onTapReceive?(isSelected) } } } } extension Radio { typealias TapReceiveAction = (Bool) -> Void init(isSelected: Binding<Bool>, len: CGFloat = 30) { _isSelected = isSelected self.len = len } init(isSelected: Binding<Bool>, onTapReceive: @escaping TapReceiveAction) { _isSelected = isSelected self.onTapReceive = onTapReceive } } struct RadioButtonGroup<V: Hashable, Content: View>: View { private var value: RadioValue<V> private var items: () -> Content @ViewBuilder var body: some View { VStack { items() }.environmentObject(value) } } fileprivate extension RadioButtonGroup where V: Hashable, Content: View { init(value: Binding<V?>, @ViewBuilder _ items: @escaping () -> Content) { self.value = RadioValue(selection: value) self.items = items } } fileprivate class RadioValue<T: Hashable>: ObservableObject { @Binding var selection: T? init(selection: Binding<T?>) { _selection = selection } } fileprivate struct RadioItemModifier<V: Hashable>: ViewModifier { @EnvironmentObject var value: RadioValue<V> private var tag: V init(tag: V) { self.tag = tag } func body(content: Content) -> some View { Button { value.selection = tag } label: { HStack { Text("\(tag):") content } } } } extension View { func radioTag<V: Hashable>(_ v: V) -> some View { self.modifier(RadioItemModifier(tag: v)) } } struct RadioButtonGroup_Preview: View { @State var selection: String? = "1" var body: some View { RadioButtonGroup(value: $selection) { Text("radio A") .radioTag("1") Text("radio B") .radioTag("2") } } } #Preview { RadioButtonGroup_Preview() } 

1 Comment

Doesn't work on iOS

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.