在通用函数中修改 onTapGesture 的状态变量

问题描述 投票:0回答:1

我有重复的调查步骤代码,我试图使其可重用:

private var workoutTypeSection: some View {
    VStack(spacing: 20) {
        header("What Is Your Preferred Workout?")
        
        ForEach(WorkoutType.allCases) { workoutType in
            Text(workoutType.rawValue)
                .font(.headline)
                .foregroundColor(.purple)
                .frame(height: 55)
                .frame(maxWidth: .infinity)
                .background(selectedWorkoutTypes.contains(workoutType) ?
                            Color.blue : Color.white)
                .cornerRadius(10)
                .onTapGesture {
                    workoutButtonPressed(workout: workoutType)
                }
        }
    }
    .padding(30)
}

private var bodyPartSection: some View {
    VStack(spacing: 20) {
        header("Select a Body Part to Strengthen:")
        
        ForEach(BodyPart.allCases) { bodyPart in
            Text(bodyPart.rawValue)
                .font(.headline)
                .foregroundColor(.purple)
                .frame(height: 55)
                .frame(maxWidth: .infinity)
                .background(selectedBodyParts.contains(bodyPart) ?
                            Color.blue : Color.white)
                .cornerRadius(10)
                .onTapGesture {
                    bodyPartButtonPressed(part: bodyPart)
                }
        }
    }
    .padding(30)
}

OnTapGesture 选择或取消选择调查选项:

func workoutButtonPressed(workout: WorkoutType) {
    if selectedWorkoutTypes.contains(workout) {
        if let index = selectedWorkoutTypes.firstIndex(of: workout) {
            selectedWorkoutTypes.remove(at: index)
        }
    } else {
        selectedWorkoutTypes.append(workout)
    }
}

func bodyPartButtonPressed(part: BodyPart) {
    if selectedBodyParts.contains(part) {
        if let index = selectedBodyParts.firstIndex(of: part) {
            selectedBodyParts.remove(at: index)
        }
    } else {
        selectedBodyParts.append(part)
    }
}

哪里

@State var selectedWorkoutTypes: [WorkoutType] = []
@State var selectedBodyParts: [BodyPart] = []

枚举类型为:

enum WorkoutType: String, CaseIterable, Codable, Identifiable {
case yoga = "Yoga"
case mobility = "Mobility"
case strength = "Strength"
case cardio = "Cardio" 

var id: WorkoutType { self }}

enum BodyPart: String, CaseIterable, Codable, Identifiable {
case legs = "Legs"
case core = "Core/Abs"
case back = "Back"
case chest = "Chest/Arms"
case neck = "Neck/Shoulders"
case body = "Whole Body"

var id: BodyPart { self }}

我创建了一个通用函数,使代码可重用:

func surveyStepOptions<T : Identifiable & Hashable & CaseIterable & RawRepresentable >(_ enumType: T.Type, selected: [T])
-> some View
where T.RawValue == String
{
    ForEach(Array(enumType.allCases)) {option in
        
        Text(option.rawValue).font(.headline)
            .foregroundColor(.purple)
            .frame(height: 55)
            .frame(maxWidth: .infinity)
            .background(selected.contains(option) ?
                        Color.blue : Color.white)
            .cornerRadius(10)
            .onTapGesture {
                if selected.contains(option) {
                    if let index = selected.firstIndex(of: option) {
                        selected.remove(at: index)
                    }
                } else {
                    selected.append(option)
                }
            }
    }
}

我很难改变 onTapGesture 中的状态变量,因为它说“选定的”参数是不可变的,但是当我使它可变时,它说我不能在转义闭包中使用 inout 。怎么解决?

swift generics swiftui code-reuse
1个回答
0
投票

您已经快完成了,只需将参数更改为

Binding
,这样您就可以在
ForEach
函数中更改选择

@ViewBuilder func surveyStepOptions<EnumType>(selected: Binding<[EnumType]>) -> some View where EnumType: CaseIterable & RawRepresentable<String> & Equatable{
    ForEach(Array(EnumType.allCases), id:\.rawValue) {option in
        
        Text(option.rawValue).font(.headline)
            .foregroundColor(.purple)
            .frame(height: 55)
            .frame(maxWidth: .infinity)
            .background(selected.wrappedValue.contains(option) ?
                        Color.blue : Color.white)
            .cornerRadius(10)
            .onTapGesture {
                if selected.wrappedValue.contains(option) {
                    if let index = selected.wrappedValue.firstIndex(of: option) {
                        selected.wrappedValue.remove(at: index)
                    }
                } else {
                    selected.wrappedValue.append(option)
                }
            }
    }
}

注意,我还删除了显式类型参数,Swift 中不需要它。

这是完整的代码。

import SwiftUI

struct ReusableEnumParentView: View {
    @State var selectedWorkoutTypes: [WorkoutType] = []
    @State var selectedBodyParts: [BodyPart] = []
    
    var body: some View {
        ScrollView{
            LazyVStack {
                workoutTypeSection
                bodyPartSection
            }
        }
    }
    
    @ViewBuilder var workoutTypeSection: some View {
        VStack(spacing: 20) {
            header("What Is Your Preferred Workout?")
            
            surveyStepOptions(selected: $selectedWorkoutTypes)
        }
        .padding(30)
    }
    
    @ViewBuilder var bodyPartSection: some View {
        VStack(spacing: 20) {
            header("What Is Your Preferred Workout?")
            
            surveyStepOptions(selected: $selectedBodyParts)
        }
        .padding(30)
    }
    
    @ViewBuilder func header(_ title: String) -> some View {
        Text(title)
    }
    @ViewBuilder func surveyStepOptions<EnumType>(selected: Binding<[EnumType]>) -> some View where EnumType: CaseIterable & RawRepresentable<String> & Equatable{
        ForEach(Array(EnumType.allCases), id:\.rawValue) {option in
            
            Text(option.rawValue).font(.headline)
                .foregroundColor(.purple)
                .frame(height: 55)
                .frame(maxWidth: .infinity)
                .background(selected.wrappedValue.contains(option) ?
                            Color.blue : Color.white)
                .cornerRadius(10)
                .onTapGesture {
                    if selected.wrappedValue.contains(option) {
                        if let index = selected.wrappedValue.firstIndex(of: option) {
                            selected.wrappedValue.remove(at: index)
                        }
                    } else {
                        selected.wrappedValue.append(option)
                    }
                }
        }
    }
}

#Preview {
    ReusableEnumParentView()
}

enum WorkoutType: String, CaseIterable, Codable, Identifiable {
    case yoga = "Yoga"
    case mobility = "Mobility"
    case strength = "Strength"
    case cardio = "Cardio"
    
    var id: WorkoutType { self }}

enum BodyPart: String, CaseIterable, Codable, Identifiable {
    case legs = "Legs"
    case core = "Core/Abs"
    case back = "Back"
    case chest = "Chest/Arms"
    case neck = "Neck/Shoulders"
    case body = "Whole Body"
    
    var id: BodyPart { self }
}
© www.soinside.com 2019 - 2024. All rights reserved.