1
votes

A background to the issue: I am creating a simple app which stores activity data in a struct. I have created a custom view called "CardView" (held in a separate file within the project) which is to appear onto a basic ContentView. The CardView is populated with Text labels which are to be bound to corresponding data within the aforementioned activity data struct. A ForEach handles the dynamic creation of these cards according to how many activity "items" exist. See following code...

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")
        }
    }
}
}

The issue: Despite multiple attempts and countless hours and errors later through using @Binding and leveraging @Environmental, I cannot pass data from the current ActivityItem in the ForEach into the CardView to be used to replace the String placeholder in the Text labels (i.e. Name, Type, Amount). When trying to create and use bindings, such as something like $activity.name, it declares that "activity" cannot become a binding. What am I doing wrong? How, in the simplest way, can I get each activity to pass its data to the CardView struct, populate those Text labels and then create itself on the ContentView "main screen" and then repeat for all remaining objects in the ForEach iteration? Is there any important notes to keep in mind to avoid issues like this in the future? I must add that if I put the CardView code directly into ContentView and not call CardView() in the ForEach, I can easily refer to the data; it only becomes an issue when I use the CardView struct (a long term goal for reusability and less code clutter). Thank you for your help!

1
Based on your CardView, since it's only displaying - not modifying - the values, it doesn't need a binding. Just create a property let activity: ActivityItem, and pass it in init: CardView(appData: self.appData, activity: activity). Do you even need appData inside CardView? If not, remove that, and it becomes CardView(activity: activity) - New Dev

1 Answers

0
votes

I assume you meant something like the following (tested with 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")
        }
    }
}
}