swiftswiftui

Mutually exclusive toggles


I have issue with SwiftUI. I am attempting to create mutually exclusive toggles for filtering. At least one of the toggles should at all times be selected. So if you click an already selected toggle, it should stay selected.

I have tried using published UserData and then setting the other toggles in a didSet section for all variables. This approach, however, simply crashes the program, when i click any of the toggles.

The UI looks like this

The code for the SwiftUI file can be seen her:

struct DSTScrollView: View {
    @EnvironmentObject private var userData: UserData
    
    var body: some View {
        ScrollView(.vertical, showsIndicators: true){
            VStack{
                Text("Befolkning og Valg Tabeller")
                    .font(.subheadline)
                    .multilineTextAlignment(.center)
                HStack {
                    VStack (alignment: .leading){
                        Text("Folketal (BY2)")
                        
                        HStack{
                            Toggle(isOn: $userData.DSTSelections.BY2_allAges){
                                Text("Alle aldre")
                            }
                            
                            Toggle(isOn: $userData.DSTSelections.BY2_5Year){
                                Text("5-års aldersgrupper")
                            }
                            
                            Toggle(isOn: $userData.DSTSelections.BY2_10Year){
                                Text("10-års aldersgrupper")
                            }
                        }
                        
                    }
                    Spacer()
                }
                .padding(.leading, 8)
            }
        }
        .frame(maxWidth: 580, minHeight: 300)
        .border(Color.black)
    }
}

The code for the UserData can be seen here:

final class UserData: ObservableObject {
    @Published var DSTSelections: DSTSelection = DSTSelection()
}

final class DSTSelection: ObservableObject{
    var BY2_allAges: Bool = false{
        didSet{
            BY2_10Year = false
            BY2_5Year = false
            BY2_allAges = true
        }
    }
    var BY2_5Year: Bool = false{
        didSet{
            BY2_allAges = false
            BY2_10Year = false
            BY2_5Year = true
        }
    }
    var BY2_10Year: Bool = true {
        didSet{
            BY2_allAges = false
            BY2_5Year = false
            BY2_10Year = true
        }
    }
}

Solution

  • The trouble is with you model class DTSelection, in your didSet code you are setting the other properties to new values which will trigger their didSet and so on leading to an infinite loop.

    This is my refactoring of your model class where I have introduced an enum and a new private property to keep track of the currently selected value. I have also changed the public properties to be computed properties

    enum DSTSelectionEnum {
        case BY2_allAges
        case BY2_5Year
        case BY2_10Year
    }
    
    final class DSTSelection: ObservableObject{
        private var trueOption: DSTSelectionEnum = .BY2_allAges
        var BY2_allAges: Bool {
            get {
                trueOption == .BY2_allAges
            }
            set {
                trueOption = .BY2_allAges
            }
        }
        var BY2_5Year: Bool {
            get {
                trueOption == .BY2_5Year
            }
            set {
                trueOption = .BY2_5Year
            }
        }
        var BY2_10Year: Bool {
            get {
                trueOption == .BY2_10Year
            }
            set {
                trueOption = .BY2_10Year
            }
        }
    }
    

    This should work without any changes to your view code