How to change value from an identifiable object in one view from another view swiftui

228 views Asked by At

I am making an achievement system in my app, i want people to be awarded for using my app. Im trying to change the isComplete value from content view, for the identifiable object in achievement view. My achievement view is being called to run in a sheet from content view.


struct ContentView: View {
    var body: some View {
        // change value from here
    }
}

struct Achievement: Identifiable {
    let id = UUID()
    let title: String
    let description: String
    let isComplete: Bool
}

struct AchievementView: View {
    @State var Achievements = [
        Achievement(title: "Done and Done I", description: "delete 1 task", isComplete: false),
        Achievement(title: "Done and Done II", description: "delete 10 tasks", isComplete: false),
        Achievement(title: "Done and Done III", description: "delete 50 tasks", isComplete: false),
        Achievement(title: "Done and Done IV", description: "delete 100 tasks", isComplete: false),
        Achievement(title: "Done and Done V", description: "delete 1000 tasks", isComplete: false),
        Achievement(title: "Busy bee I", description: "add 1 task", isComplete: false),
        Achievement(title: "Busy bee II", description: "add 10 tasks", isComplete: false),
        Achievement(title: "Busy bee III", description: "add 50 tasks", isComplete: false),
        Achievement(title: "Busy bee IV", description: "add 100 tasks", isComplete: false),
        Achievement(title: "Busy bee V", description: "add 1000 tasks", isComplete: false),
        Achievement(title: "Thank you", description: "rate our app", isComplete: false),
        Achievement(title: "Sharing is caring", description: "share your list", isComplete: false),
        Achievement(title: "No place like home", description: "customize taskfairy", isComplete: false)
    ]
    var body: some View {
        List {
            Section(header: Text("completed")) {
                ForEach(Achievements) { i in
                    if i.isComplete {
                        HStack {
                            Image(systemName: "trophy.circle")
                                .scaleEffect(1.5)
                                .padding()
                            VStack {
                                Text("\(i.title)")
                                Text("\(i.description)")
                                    .font(.custom("SanFrancisco", size: 12))
                                    .foregroundColor(.gray)
                            }
                        }
                    }
                }
            }
            Section(header: Text("incomplete")) {
                ForEach(Achievements) { i in
                    if !i.isComplete {
                        HStack {
                            Image(systemName: "trophy.circle")
                                .scaleEffect(1.5)
                                .padding()
                            VStack {
                                Text("\(i.title)")
                                Text("\(i.description)")
                                    .font(.custom("SanFrancisco", size: 12))
                                    .foregroundColor(.gray)
                            }
                        }
                    }
                }
            }
        }
    }
}


2

There are 2 answers

0
BurnDownTheWorld On BEST ANSWER

I figured it out. You can use userDefaults to save data from one View, then view it in the other.

struct Achievement: Codable, Identifiable {
    let id: String
    let title: String
    let description: String
    var isComplete: Bool = false
    init(title: String, description: String, isComplete: Bool) {
        self.title = title
        self.description = description
        self.isComplete = isComplete
        self.id = UUID().uuidString
    }
}

// to read (not full code)
.onAppear {
            LoadAchievements()
        }
    }
    
    func LoadAchievements() {
        let decoder = JSONDecoder()
        if let data = UserDefaults.standard.data(forKey: "achievements") {
            achievements = try! decoder.decode([Achievement].self, from: data) as? [Achievement] ?? [
                Achievement(title: "Busy Bee I", description: "Make 10 tasks", isComplete: false),
                Achievement(title: "Busy Bee II", description: "Make 25 tasks", isComplete: false),
                Achievement(title: "Busy Bee III", description: "Make 50 tasks", isComplete: false),
                Achievement(title: "Busy Bee IV", description: "Make 100 tasks", isComplete: false),
                Achievement(title: "Done and Done I", description: "Make 10 tasks", isComplete: false),
                Achievement(title: "Done and Done II", description: "Delete 25 tasks", isComplete: false),
                Achievement(title: "Done and Done III", description: "Delete 50 tasks", isComplete: false),
                Achievement(title: "Done and Done IV", description: "Delete 100 tasks", isComplete: false),
                Achievement(title: "Home Sweet Home", description: "Customize Something In Settings", isComplete: false),
                Achievement(title: "Sharing Is Caring", description: "Share your taskfairy list", isComplete: false)
            ]
        }
    }

// to set
tasksMadeCount += 1
                        userDefaults.set(tasksMadeCount, forKey: "tasksMadeCount")
                        if(userDefaults.integer(forKey: "tasksMadeCount") == 10) {
                            achievements[0].isComplete = true
                        }
0
Fogmeister On

So, this is not a trivial problem to solve.

There are frameworks that originated from this sort of question. One in particular is TCA https://github.com/pointfreeco/swift-composable-architecture

The issue is that you can’t pass in the object to your Content View and have changes to it automatically pass back up into the Array in the parent.

What you need to do is be able write back into the array when the object is changed and there are a few ways of doing this.

You might pass a closure into the view that is called when changes are made. In that closure you can write the updated object back into the array.

You might store the index and the selected item in the parent as a binding and write back in the didSet from there.

I’d definitely recommend looking into something like TCA as a way to wrap this sort of logic up for you. Even if it’s only to understand the problem and different possible solutions for something like this.