首页 > 解决方案 > 在 ForEach 迭代中将数据传递到自定义视图 (SwiftUI)

问题描述

问题的背景:我正在创建一个简单的应用程序,它将活动数据存储在一个结构中。我创建了一个名为“CardView”的自定义视图(保存在项目中的单独文件中),它将出现在基本的 ContentView 上。CardView 填充了文本标签,这些标签将绑定到上述活动数据结构中的相应数据。ForEach 根据存在多少活动“项目”来处理这些卡片的动态创建。见以下代码...

struct ActivityItem: Identifiable, Codable {
let id = UUID()
let name: String
let description: String
let type: String
var amount: Int
}

class AppData: ObservableObject {
@Published var activites: [ActivityItem] {
    didSet {
        let encoder = JSONEncoder()
        if let encoded = try? encoder.encode(activites) {
            UserDefaults.standard.set(encoded, forKey: "Activites")
        }
    }
}

init() {
    if let foundActivities = UserDefaults.standard.data(forKey: "Activites") {
        let decoder = JSONDecoder()
        if let decoded = try? decoder.decode([ActivityItem].self, from: foundActivities) {
            self.activites = decoded
            return
        }
    }

    self.activites = []
}
}

struct ContentView: View {
@ObservedObject var appData = AppData()
@State private var showingAddActivity = false
@State private var showingMoreInfo = false

var body: some View {
    NavigationView {
        ZStack {
            Rectangle()
            .fill(Color(.gray))
            .frame(maxWidth: .infinity, maxHeight: .infinity)
                .edgesIgnoringSafeArea(.all)
            
            ScrollView {
                VStack(spacing: 50) {
                    ForEach(appData.activites) { activity in
                        CardView(appData: self.appData)
                    }
                }
            }

// Below held in separate file
struct CardView: View {
@ObservedObject var appData: AppData
@State private var showingMoreInfo = false

var body: some View {
    ZStack {
        Rectangle()
            .fill(Color(.gray))
            .frame(maxWidth: .infinity, maxHeight: .infinity)
            .edgesIgnoringSafeArea(.all)
        RoundedRectangle(cornerRadius: 20)
            .fill(Color(.black))
            .frame(width: 325, height: 180)
        VStack {
            Text("Name")
                .font(.title)
            
            Text("Description")

            Text("Type")
            
            Text("Amount")                
        }
        .sheet(isPresented: $showingMoreInfo) {
            Text("View")
        }
    }
}
}

问题:尽管通过使用@Binding 和利用@Environmental 进行了多次尝试和无数小时和错误,但我无法将数据从 ForEach 中的当前 ActivityItem 传递到 CardView 以用于替换文本标签中的字符串占位符(即名称、类型、金额)。当尝试创建和使用绑定时,例如 $activity.name 之类的东西,它声明“活动”不能成为绑定。我究竟做错了什么?如何以最简单的方式让每个活动将其数据传递给 CardView 结构,填充这些文本标签,然后在 ContentView“主屏幕”上创建自己 然后对 ForEach 迭代中的所有剩余对象重复?是否有任何重要的注意事项需要牢记以避免将来出现此类问题?必须补充一点,如果我将 CardView 代码直接放到 ContentView 中,而不是在 ForEach 中调用 CardView(),我可以很容易地引用数据;只有当我使用 CardView 结构时它才会成为一个问题(可重用性和减少代码混乱的长期目标)。谢谢您的帮助!

标签: swiftswiftui

解决方案


我假设您的意思类似于以下内容(使用 Xcode 12 / iOS 14 测试)

struct ActivityItem: Identifiable, Codable {
var id = UUID()
var name: String            // << made `var` here and other to allow write
var description: String
var type: String
var amount: Int
}

class AppData: ObservableObject {
@Published var activites: [ActivityItem] {
    didSet {
        let encoder = JSONEncoder()
        if let encoded = try? encoder.encode(activites) {
            UserDefaults.standard.set(encoded, forKey: "Activites")
        }
    }
}

init() {
    if let foundActivities = UserDefaults.standard.data(forKey: "Activites") {
        let decoder = JSONDecoder()
        if let decoded = try? decoder.decode([ActivityItem].self, from: foundActivities) {
            self.activites = decoded
            return
        }
    }

    self.activites = []
}
}

struct ContentView: View {
@ObservedObject var appData = AppData()
@State private var showingAddActivity = false
@State private var showingMoreInfo = false

var body: some View {
    NavigationView {
        ZStack {
            Rectangle()
            .fill(Color(.gray))
            .frame(maxWidth: .infinity, maxHeight: .infinity)
                .edgesIgnoringSafeArea(.all)

            ScrollView {
                VStack(spacing: 50) {
                    ForEach(appData.activites.indices, id: \.self) { i in
                        CardView(item: $appData.activites[i])  // binding here  
                    }
                }
            }
        }
    }
}
}

// Below held in separate file
struct CardView: View {
@Binding var item: ActivityItem             // binding here
@State private var showingMoreInfo = false

var body: some View {
    ZStack {
        Rectangle()
            .fill(Color(.gray))
            .frame(maxWidth: .infinity, maxHeight: .infinity)
            .edgesIgnoringSafeArea(.all)
        RoundedRectangle(cornerRadius: 20)
            .fill(Color(.black))
            .frame(width: 325, height: 180)
        VStack {
            TextField("Name", text: $item.name)    // edit here
                .font(.title)

            Text("Description")

            Text("Type")

            Text("Amount")
        }
        .sheet(isPresented: $showingMoreInfo) {
            Text("View")
        }
    }
}
}

推荐阅读