Hi everyone. I recently finished the project on Day 47, but I'm still failing to understand how SwiftUI treats data between views. Some explanation would be appreciated.
In my ActivityDetail view, I was trying to update the timesCompleted property of the Activity struct directly, by incrementing the count with a button and then setting the new value directly with this function. The code checked out, but it wouldn't let me change the timesCompleted property because it stated that "activity" was a constant (even though I declared it with var).
Essentially, I tried to modify one property in the struct that the class contained and it didn't work.
func updateTimes(count: Int, name: String) {
for activity in activities {
if activity.activityName == name {
activity.timesCompleted = count
}
}
}
Eventually I found that I needed to swap the Activity item in the Activities array completely, using this function:
func updateActivity() {
if let position = activities.activities.firstIndex(where: {$0.id == activity.id}) {
activities.activities[position] = activity
}
}
My question is: why isn't it possible to modify only the timesCompleted property? Why does Swift require a new instance of Activity to replace the old one?
Full code here of code that didn't work (error message cannot assign to property: 'activity' is a 'let' constant:
struct Activity: Identifiable, Codable {
let id = UUID()
let activityName: String
let activityDetails: String
let measureUnit: String
var timesCompleted: Int
mutating func incrementTimes() {
self.timesCompleted += 1
}
}
class Activities: ObservableObject {
@Published var activities = [Activity]() {
didSet {
let encoder = JSONEncoder()
if let encoded = try? encoder.encode(activities) {
UserDefaults.standard.set(encoded, forKey: "Activities")
}
}
}
init() {
if let items = UserDefaults.standard.data(forKey: "Activities") {
let decoder = JSONDecoder()
if let decoded = try? decoder.decode([Activity].self, from: items) {
self.activities = decoded
return
}
}
}
func updateTimes(count: Int, name: String) {
for activity in activities {
if activity.activityName == name {
activity.timesCompleted = count
}
}
}
}
struct ContentView: View {
@ObservedObject var activities = Activities()
@State private var showingAddActivity = false
var body: some View {
NavigationView {
List(activities.activities) { activity in
NavigationLink(destination: ActivityDetails(activity: activity, activities: self.activities)) {
VStack(alignment: .leading) {
Text(activity.activityName)
Text("Times Completed: \(String(activity.timesCompleted))")
}
}
}
//.onDelete(perform: self.removeItems)
.navigationBarTitle("Be Better")
.navigationBarItems(leading: EditButton(), trailing: Button(action: {
self.showingAddActivity = true
}) {
Image(systemName: "plus")
})
.sheet(isPresented: $showingAddActivity) {
AddActivity(activities: self.activities)
}
}
}
func removeItems(at offsets: IndexSet) {
activities.activities.remove(atOffsets: offsets)
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
struct ActivityDetails: View {
@Environment(\.presentationMode) var presentationMode
@State var activity: Activity
@ObservedObject var activities: Activities
var body: some View {
NavigationView {
VStack {
Text(activity.activityName)
.font(.title)
.padding()
HStack {
Button(action: {
self.activity.incrementTimes()
}) {
Image(systemName: "arrow.left")
}
Text(String(activity.timesCompleted))
Button(action: {
self.activity.incrementTimes()
self.activities.updateTimes(count: self.activity.timesCompleted, name: self.activity.activityName)
}) {
Image(systemName: "arrow.right")
}
}
.padding()
Text(activity.activityDetails)
.padding()
Spacer()
}
navigationBarTitle("Activity information")
.navigationBarTitle("Add a new activity")
.navigationBarItems(leading: Button("Back") {
self.presentationMode.wrappedValue.dismiss()
})
}
}