SwiftUI绑定两个ForEach循环实现父子视图收藏按钮状态同步
解决方法
核心问题有两个:
- 你的
Data模型没有存储对应条目的收藏状态,所有视图的selectedFavoriteSong都是独立的局部状态,自然无法同步 - 你之前用EnvironmentObject时只存了单个布尔值,所有条目共用同一个状态,才会出现点一个全选中的问题
步骤1:调整模型与全局数据源
首先给Data结构加收藏状态字段,同时创建全局可观测的数据源管理类,代替原来的静态DataList:
import SwiftUI struct Data: Identifiable { let id = UUID() let number: Int var name1: String let name2: String // 新增:每个条目自带收藏状态 var isFavorited: Bool = false } // 全局数据源管理类 class DataViewModel: ObservableObject { @Published var datas: [Data] = [ Data(number: 1, name1: "Pasian Phatna", name2: "Praise God, from whom All Blessings Flow"), Data(number: 2, name1: "Itna Kumpi, Ka Tuu-Cing Pa", name2: "The King of Love My Shephaerd Is (Dominus Regit Me)"), Data(number: 3, name1: "Kumpipa Bia Un", name2: "O Worship the King"), Data(number: 4, name1: "Pa Tung Min Than'na Om Hen", name2: "Gloria Patri (1st Tune)"), Data(number: 5, name1: "Pa Tung Min Than'na Om Hen", name2: "Gloria Patri (2nd Tune)") ] }
步骤2:在App入口注入全局数据源
在你的App主结构里把DataViewModel注入环境,这样所有子视图都能访问同一份数据:
@main struct YourAppName: App { // 初始化全局数据源 @StateObject private var dataVM = DataViewModel() var body: some Scene { WindowGroup { // 这里放你的根视图,比如ParentView或者TabView ParentView() .environmentObject(dataVM) } } }
步骤3:修改父视图与MainData组件
父视图不要自己存独立的datas,直接用环境里的全局数据源,遍历的时候传递绑定:
struct ParentView: View { // 取全局数据源,不要自己单独存@State的datas @EnvironmentObject var dataVM: DataViewModel var body: some View { NavigationView { ScrollView (.vertical, showsIndicators: false) { LazyVStack(spacing: 5) { // 这里用$符号获取绑定的数组元素 ForEach ($dataVM.datas) { $data in MainData(data: $data) Divider() .padding(.all) } } } .navigationBarHidden(true) } .navigationViewStyle(StackNavigationViewStyle()) } } struct MainData: View { // 接收绑定的data,不要用@State存本地副本 @Binding var data: Data var body: some View { HStack { Button(action: { // 直接修改绑定的data的收藏状态 data.isFavorited.toggle() }, label: { Image(systemName: data.isFavorited ? "suit.heart.fill" : "suit.heart") .foregroundColor(data.isFavorited ? .red : .primary) .padding(.horizontal) }) Spacer() Text("\(data.number)") Spacer() } .padding(.top) VStack { Text(data.name1) .font(.title2.smallCaps()) .fontWeight(.bold) .foregroundColor(.primary) Text(data.name2) .font(.title3) .fontWeight(.medium) .foregroundColor(.secondary) .italic() } .padding(.horizontal) .multilineTextAlignment(.center) } }
步骤4:修改搜索页与详情页
搜索页同样使用全局数据源,跳转详情页时传递绑定的data:
struct Search: View { // 同样取全局数据源,不要自己存本地副本 @EnvironmentObject var dataVM: DataViewModel var body: some View { NavigationView { ScrollView (.vertical, showsIndicators: false) { LazyVStack(alignment: .leading, spacing: 10) { ForEach ($dataVM.datas) { $data in NavigationLink( // 传递绑定的data给详情页 destination: DetailView(data: $data), label: { Text(data.name1) }) } }.padding(.horizontal) } } } } struct DetailView: View { // 接收绑定的data,不用单独存本地收藏状态 @Binding var data: Data var body: some View { HStack { Button(action: { data.isFavorited.toggle() }, label: { Image(systemName: data.isFavorited ? "suit.heart.fill" : "suit.heart") .foregroundColor(data.isFavorited ? .red : .primary) .padding(.horizontal) }) Spacer() Text("\(data.name1)") Spacer() } .padding(.top) VStack { Text(data.name2) .font(.title2.smallCaps()) .fontWeight(.bold) .foregroundColor(.primary) } .padding(.horizontal) .multilineTextAlignment(.center) Spacer() } }
注意点
如果你的Search页面是模态弹出的,弹出时也要确保环境里已经注入了DataViewModel,模态视图默认会继承父视图的环境变量,只要根视图注入了就不会有问题。所有视图都共用同一份数据源,修改任意位置的收藏状态,都会自动同步到所有显示该条目的视图里。
内容的提问来源于stack exchange,提问作者Lian
相关产品推荐
相关产品推荐

