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

SwiftUI绑定两个ForEach循环实现父子视图收藏按钮状态同步

解决方法

核心问题有两个:

  1. 你的Data模型没有存储对应条目的收藏状态,所有视图的selectedFavoriteSong都是独立的局部状态,自然无法同步
  2. 你之前用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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.02 21:39:03