UPGRADE YOUR SKILLS: Learn advanced Swift and SwiftUI on Hacking with Swift+! >>

day 47 challenge - completed activity

Forums > 100 Days of SwiftUI

@Moghi  

I wanted to add the counter on how many times the activity was completed. But the button I created in ActivityView does not work because of the error "cannot use mutating member on immutable value: self is immutable"

Why is my Activity immutable? How should I increment the counter?

Activity

import Foundation

struct Activity: Codable, Identifiable {
    var id = UUID()  // swift will create ids automatically
    let title: String
    let description: String
    var completedCount = 0

    mutating func completed() {
        self.completedCount += 1
    }
}

Activities

import Foundation

class Activities: ObservableObject {
    @Published var items = [Activity]() {
        // Whenever you set this property it will save the data to disk in UserDefaults
        didSet {
            let encoder = JSONEncoder()
            if let encoded = try? encoder.encode(items) {
                UserDefaults.standard.set(encoded, forKey: "activities")
            }
        }
    }

    init() {
        // Try to read saved data at init-time, if there is no data then create an empty array
        if let savedActivities = UserDefaults.standard.data(forKey: "activities") {
            if let decoded = try? JSONDecoder().decode([Activity].self, from: savedActivities) {
                items = decoded
                return
            }
        }
        items = [Activity]()
    }

    static var previewData: Activities {
        let sample = Activities()
        sample.items.append(Activity(title: "Outdoor Cycling", description: "Cycling outside in the nature."))
        return sample
    }
}

ActivityView

import SwiftUI

struct ActivityView: View {
    var activity: Activity

    var body: some View {
        GeometryReader { geometry in
            ScrollView {
                VStack(alignment: .leading) {
                    Text("This activity has been completed: \(activity.completedCount) times")
                        .padding(.top, 15)
                    Button {
                        activity.completed()    // HERE is the error
                    } label: {
                        Image(systemName: "plus")
                    }
                    Text(activity.description)
                        .padding(.top, 10)
                }
                .padding([.horizontal])
            }
        }
        .navigationTitle(activity.title)
    }
}

struct ActivityView_Previews: PreviewProvider {
    static var previews: some View {
        ActivityView(activity: Activities.previewData.items.popLast()!)
    }
}

ContentView

import SwiftUI

struct ContentView: View {
    @StateObject var activities = Activities()
    @State private var showingAddActivity = false

    @State private var dummies = ["Outdoor Cycle", "Walk", "Run"]

    var body: some View {
        NavigationView {
            List {
                Section {
                    ForEach(activities.items, id: \.id) { activity in
                        NavigationLink {
                            ActivityView(activity: activity)
                        } label: {
                            Text(activity.title)
                        }
                    }
                    .onDelete(perform: removeActivityItem(at:))
                } header: {
                    Text("My Activities")
                }
            }
            .navigationTitle("Time Tracker")
            .toolbar {
                Button {
                    showingAddActivity = true
                } label: {
                    Image(systemName: "plus")
                }
            }
            .sheet(isPresented: $showingAddActivity) {
                AddActivityView(activities: activities)
            }
        }
    }

    func removeActivityItem(at offsets: IndexSet) {
        activities.items.remove(atOffsets: offsets)
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

2      

You are trying to mutate the View itself, however Views are immutable. If you want to change anything in a View it will have to be marked as a @State.

Here is a brief note from Paul.

Normally with an @State the declaration would be

@State private var activity: Activity

However, since you are calling the ActivityView from ContentView and passing a parameter the compiler will complain that the initializer is inaccessible due to the private protection, so you should try this instead.

@State var activity: Activity

2      

@Moghi  

Hi, thanks for the pointer.

The Button woks now, of course the state is not preserved after I leave the detail view. Now I feel like I have to move away from passing a struct to the detail view, since I want to change it and preserve the change.

So instead of

...
ForEach(activities.items, id: \.id) { activity in
                        NavigationLink {
                            ActivityView(activity: activity)
...

I shall find out the index of the item the user touches (to go to the ActivityView()) and handing it over to ActivityView alongside the class instance activities. Then I could modify the count and have my class instance remember it. Hmmm...How would I get that index from ForEach?

Thank you for participating in my thinking process

2      

BUILD THE ULTIMATE PORTFOLIO APP Most Swift tutorials help you solve one specific problem, but in my Ultimate Portfolio App series I show you how to get all the best practices into a single app: architecture, testing, performance, accessibility, localization, project organization, and so much more, all while building a SwiftUI app that works on iOS, macOS and watchOS.

Get it on Hacking with Swift+

Sponsor Hacking with Swift and reach the world's largest Swift community!

Archived topic

This topic has been closed due to inactivity, so you can't reply. Please create a new topic if you need to.

All interactions here are governed by our code of conduct.

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.