SwiftUI泛型函数中onTapGesture修改State变量的问题求解
Why Your Current Approach Fails
The error occurs because:
- Passing
selected: [T]as a regular parameter gives you a copy of the array, so modifying it doesn't affect the original@Statevariable. - Using
inoutwon't work becauseonTapGestureuses an escaping closure—the closure can run after the function finishes, and Swift prohibits inout parameters in escaping closures to avoid invalid memory references.
Fix: Use Binding to Modify State
SwiftUI's Binding type lets you pass a reference to your @State variables, allowing you to modify them from within reusable views/functions. Here's how to implement this:
Step 1: Create a Generic Reusable Survey Section
Instead of duplicate workoutTypeSection and bodyPartSection, make a single generic view that handles all survey steps:
struct SurveySection<T: Identifiable & Hashable & CaseIterable & RawRepresentable>: View where T.RawValue == String { let title: String @Binding var selectedItems: [T] var body: some View { VStack(spacing: 20) { header(title) ForEach(T.allCases) { option in Text(option.rawValue) .font(.headline) .foregroundColor(.purple) .frame(height: 55) .frame(maxWidth: .infinity) .background(selectedItems.contains(option) ? Color.blue : Color.white) .cornerRadius(10) .onTapGesture { toggleSelection(for: option) } } } .padding(30) } // Reusable toggle logic private func toggleSelection(for option: T) { if selectedItems.contains(option) { selectedItems.removeAll { $0 == option } } else { selectedItems.append(option) } } // Your existing header function (adjust styling as needed) private func header(_ text: String) -> some View { Text(text) .font(.title) .fontWeight(.bold) } }
Step 2: Replace Duplicate Sections with the Generic View
Update your parent view to use the reusable SurveySection instead of the original duplicate code:
// Keep your existing @State variables @State var selectedWorkoutTypes: [WorkoutType] = [] @State var selectedBodyParts: [BodyPart] = [] // Replace the old sections with these private var workoutTypeSection: some View { SurveySection(title: "What Is Your Preferred Workout?", selectedItems: $selectedWorkoutTypes) } private var bodyPartSection: some View { SurveySection(title: "Select a Body Part to Strengthen:", selectedItems: $selectedBodyParts) }
Step 3: Remove Duplicate Toggle Functions
You can now delete the workoutButtonPressed and bodyPartButtonPressed functions—they're replaced by the generic toggleSelection method inside SurveySection.
Optional: Simplify with Sets (Even Cleaner)
If order of selection doesn't matter, switch your @State variables to Set<T> instead of arrays. This simplifies the toggle logic to a single line:
// Update State variables to Sets @State var selectedWorkoutTypes: Set<WorkoutType> = [] @State var selectedBodyParts: Set<BodyPart> = [] // Modify the toggle function in SurveySection private func toggleSelection(for option: T) { selectedItems.toggle(option) } // Update the background check in the option view .background(selectedItems.contains(option) ? Color.blue : Color.white)
This works because Set has a built-in toggle(_:) method that adds/removes elements automatically.
内容的提问来源于stack exchange,提问作者Elena Rubilova

