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

SwiftUI实现可多选自定义网格项:选中状态切换与数据收集问题

SwiftUI可选择网格项问题解决

问题描述

实现可选择网格项时遇到两个问题:

  • 点击项无法切换背景色与文字颜色
  • 选中的项无法添加到selectedSports状态集合中

使用WrappingStack库布局,原代码如下(略)

解决方案

1. 改造SportItem组件

让组件支持绑定选中状态,根据状态切换样式,并添加点击交互:

import SwiftUI

struct SportItem: View {
    var sport: Sport
    // 绑定父视图的选中状态
    @Binding var isSelected: Bool
    
    // 定义两种状态的样式参数
    private let normalBg = Color(red: 0.404, green: 0.314, blue: 0.643)
    private let selectedBg = Color.white
    private let normalText = Color.white
    private let selectedText = Color(red: 0.404, green: 0.314, blue: 0.643)
    
    var body: some View {
        HStack{
            Text(sport.emoji)
            Text("  "+sport.name)
                .bold()
        }
        .font(.system(size: 14))
        .padding(8)
        // 根据选中状态切换背景色
        .background(isSelected ? selectedBg : normalBg)
        // 根据选中状态切换文字颜色
        .foregroundColor(isSelected ? selectedText : normalText)
        .cornerRadius(15)
        .shadow(color: .black, radius: 3)
        // 点击触发选中状态切换
        .onTapGesture {
            isSelected.toggle()
        }
    }
}

struct SportItem_Previews: PreviewProvider {
    static var previews: some View {
        SportItem(sport: Sport(id: 1, emoji: "⚽️", name: "足球"), isSelected: .constant(false))
    }
}

2. 更新父视图SelectSport

遍历体育项时判断选中状态,绑定子组件并更新selectedSports集合:

import SwiftUI
import WrappingStack

struct SelectSport: View {
    var event: Event
    
    @State private var eventForm = Event(id: Int(), name: "", place: "", description: "",date: Date(), beginTime: 1, endTime: 2, img: "", sports: Set<Sport>())
    
    @State var selectedSports: Set<Sport> =  Set<Sport>()
    
    @EnvironmentObject var modelData: ModelData
    var sportsList: [Sport] { modelData.sports }
    
    var body: some View {
        VStack{
            Text("Which sports do you want to include?")
            Text(event.name)
            WrappingHStack {
                ForEach(sportsList){ sport in
                    // 判断当前项是否已被选中
                    let isSelected = selectedSports.contains(sport)
                    SportItem(sport: sport, isSelected: .constant(isSelected))
                        .padding(4)
                        .onTapGesture {
                            if isSelected {
                                selectedSports.remove(sport)
                            } else {
                                selectedSports.insert(sport)
                            }
                        }
                }
            }
            // 可选:调试用,显示选中项数量
            Text("已选中:\(selectedSports.count)项")
                .padding()
        }
        .navigationTitle("Select Sports")
        .navigationBarItems(trailing:
                                NavigationLink(destination: DatePickerEvent(event: eventForm)){
                Text("Next")
            }
        )
    }
}

struct SelectSport_Previews: PreviewProvider {
    static var previews: some View {
        SelectSport(event: Event(id: 1, name: "", place: "", description: "",date: Date(), beginTime: 1, endTime: 2, img: "", sports: Set<Sport>()))
            .environmentObject(ModelData())
    }
}

关键注意点

  • Sport必须遵循Hashable:因为要存入Set集合,需确保Sport实现Hashable协议(基于id实现即可):
    struct Sport: Identifiable, Hashable {
        var id: Int
        var emoji: String
        var name: String
        
        func hash(into hasher: inout Hasher) {
            hasher.combine(id)
        }
        
        static func == (lhs: Sport, rhs: Sport) -> Bool {
            lhs.id == rhs.id
        }
    }
    
  • 原代码中NavigationLink的目标视图创建了新Event实例,建议改为传递eventForm,方便后续将选中的运动项同步到表单中。

内容的提问来源于stack exchange,提问作者Diego Enrique Molina Sanchez

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.25 11:17:08