SwiftUI互斥Toggle实现问题求助:点击Toggle致程序崩溃
Hey there! Let's break down why your current implementation is crashing and get those mutually exclusive Toggles working exactly how you want them—with always at least one selected, and no crashes when clicking an already active Toggle.
The Root Cause of the Crash
Your current didSet logic is causing an infinite loop. For example, when BY2_allAges is set to true, you immediately set BY2_allAges = true again inside its own didSet. This triggers the didSet over and over until the app crashes. Plus, managing three separate booleans is error-prone and hard to maintain.
Solution 1: Use an Enum (The Cleanest Approach)
Enums are perfect for mutually exclusive states—they naturally enforce that only one option can be selected at a time. Here's how to refactor your code:
Step 1: Define an Enum for Age Groups
enum BY2AgeGroup: String, CaseIterable { case allAges = "Alle aldre" case fiveYear = "5-års aldersgrupper" case tenYear = "10-års aldersgrupper" }
Step 2: Update Your Data Model
Replace the three boolean properties with a single published enum property:
final class UserData: ObservableObject { @Published var DSTSelections: DSTSelection = DSTSelection() } final class DSTSelection: ObservableObject { // Default to the 10-year group as you had before @Published var selectedBY2AgeGroup: BY2AgeGroup = .tenYear }
Step 3: Update the SwiftUI View
Use a ForEach to render the toggles, and a custom binding to link each toggle to the selected enum value:
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)") // Loop through all age group options ForEach(BY2AgeGroup.allCases, id: \.self) { group in Toggle( group.rawValue, isOn: Binding( get: { userData.DSTSelections.selectedBY2AgeGroup == group }, set: { isSelected in // Only update if the toggle is being turned on if isSelected { userData.DSTSelections.selectedBY2AgeGroup = group } } ) ) } } Spacer() } .padding(.leading, 8) } } .frame(maxWidth: 580, minHeight: 300) .border(Color.black) } }
This approach guarantees only one toggle is selected at all times, eliminates crash risks, and makes adding new age groups later trivial—just add a case to the enum!
Solution 2: Fix the Boolean Logic (If You Prefer to Keep Booleans)
If you want to stick with your original boolean setup, you need to fix the didSet logic to avoid infinite loops and enforce the "at least one selected" rule:
final class DSTSelection: ObservableObject { @Published var BY2_allAges: Bool = false { didSet { // Only deselect others if this toggle was turned on if BY2_allAges { BY2_5Year = false BY2_10Year = false } else { // Prevent deselecting if it would leave no options selected if !BY2_5Year && !BY2_10Year { BY2_allAges = true } } } } @Published var BY2_5Year: Bool = false { didSet { if BY2_5Year { BY2_allAges = false BY2_10Year = false } else { if !BY2_allAges && !BY2_10Year { BY2_5Year = true } } } } @Published var BY2_10Year: Bool = true { didSet { if BY2_10Year { BY2_allAges = false BY2_5Year = false } else { if !BY2_allAges && !BY2_5Year { BY2_10Year = true } } } } }
Key fixes here:
- Removed redundant lines like
BY2_allAges = truethat caused infinite loops - Added checks to prevent deselecting the last active toggle, ensuring at least one is always selected
- Only modify other toggles when the current one is being turned on
Final Notes
The enum approach is strongly recommended because it's more maintainable, less error-prone, and aligns perfectly with your mutually exclusive requirement.
内容的提问来源于stack exchange,提问作者Philip Holler

