You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

SwiftUI泛型函数中onTapGesture修改State变量的问题求解

Solution to Refactor Reusable Survey Sections in SwiftUI

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 @State variable.
  • Using inout won't work because onTapGesture uses 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.17 09:37:10