swiftswiftui

Toggle one switch on and turn 2 others off


I'm having an issue with my application. I am trying to toggle one switch, and when this switch is toggled, all the other switched get toggled to off. I have tried using the onChange method. This method works for 2 of the switches on and off, but not for 3 or more?

Here is my attempted code:

import SwiftUI
import ToastUI

struct ContentView: View {
@State var generatedNumber : Int = 0

@State var allNumbers : Bool = true
@State var evensOnly : Bool = false
@State var oddsOnly : Bool = false

var body: some View {
    VStack {
        Text("Your genenerated number:")
            .font(.bold(.title)())
        
        Text("\(generatedNumber)")
            .font(.bold(.custom("Generated Number Size", size: 60))())
            .foregroundColor(.cyan)
        
        VStack{
            Toggle("All numbers", isOn: $allNumbers)
                .tint(.cyan)
                .onChange(of: allNumbers) { newValue in
                    //When toggled, turn other switches off, but leave this one on
                    evensOnly = !newValue
                    oddsOnly = !newValue
                }
            Toggle("Even numbers only", isOn: $evensOnly)
                .tint(.cyan)
                .onChange(of: evensOnly) { newValue in
                    allNumbers = !newValue
                    oddsOnly = !newValue
                }
            Toggle("Odd numbers only", isOn: $oddsOnly)
                .tint(.cyan)
                .onChange(of: oddsOnly) { newValue in

                    allNumbers = !newValue
                    evensOnly = !newValue
                }
        }.padding(30)
            .toggleStyle(.switch)
    }
}
}

This is what I am getting:

enter image description here

Only one switch should be on at a time, and if a different one is toggled, to turn the other 2 off. The onChange method when doing this states: "action tried to update multiple times per frame." Does this mean that two @State reloads are trying to occur at execution times too close to each other? Please help me


Solution

  • As the first comment already said you got a retain cycle. To achieve your goal, there are many solutions to it. However, I would do it like that (see code snippet) using the MVVM pattern to achieve it.

    struct ContentView: View {
        @State var generatedNumber : Int = 0
        @StateObject var viewModel: ContentViewModel = .init()
        
        var body: some View {
            VStack {
                Text("Your genenerated number:")
                    .font(.bold(.title)())
                
                Text("\(generatedNumber)")
                    .font(.bold(.custom("Generated Number Size", size: 60))())
                    .foregroundColor(.cyan)
                
                VStack {
                    Toggle("All numbers", isOn: $viewModel.allNumbers)
                        .tint(.cyan)
                    Toggle("Even numbers only", isOn: $viewModel.evensOnly)
                        .tint(.cyan)
                    Toggle("Odd numbers only", isOn: $viewModel.oddsOnly)
                        .tint(.cyan)
                }.padding(30)
                    .toggleStyle(.switch)
            }
        }
    }
    
    class ContentViewModel: ObservableObject {
        @Published var allNumbers : Bool = true
        @Published var evensOnly : Bool = false
        @Published var oddsOnly : Bool = false
        
        private var store: [AnyCancellable] = []
        
        init() {
            $allNumbers
                .sink(receiveValue: {
                    guard $0 else { return }
                    self.evensOnly = false
                    self.oddsOnly = false
                })
                .store(in: &store)
    
            $evensOnly
                .sink(receiveValue: {
                    guard $0 else { return }
                    self.allNumbers = false
                    self.oddsOnly = false
                })
                .store(in: &store)
    
            $oddsOnly
                .sink(receiveValue: {
                    guard $0 else { return }
                    self.allNumbers = false
                    self.evensOnly = false
                })
                .store(in: &store)
        }
    }