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

Problem with passing data with NavigationLink (challenge Day 47)

Forums > 100 Days of SwiftUI

Hi everyone! I’m struggling with a point of the challenge of Day 47 of SwiftUI. I implemented the view to show the title, the description and the count of the tracked activity, but I have a problem when I implement the button to increase the repetition count.

If i go like that:

Button("Increase repetitions") {
          self.itemDetail.count += 1
}

I receive the following error:

Left side of mutating operator isn't mutable: 'self' is immutable

I implemented a struct for the tracked item and created a class with an array of such structs, and I guess that the error stem from the fact that a struct is an immutable copy of the values, so how can I implement this increase button as that it can update the value of the struct in the array? I passed the struct to the View via a NavigationLink nested in a ForEach that iterates over every element of the items array, should I pass the class?

2      

Hi Gabriele, I can't fully understand the part below your error message without seeing that actual code.

But your code sample and your error message point that itemDetail is a property of the View you're working in and it has not been marked as @State var (which would let "Left side of mutating operator" to be mutable.

2      

Hi Daniel, thank you for your answer!

Here is the code of the Detail view:

struct DetailView: View {
    @State var itemDetail: TrackedItem

    var body: some View {
        ScrollView(.vertical) {
            VStack {
                Text(itemDetail.name)
                    .font(.title)
                    .fontWeight(.bold)
                Text(itemDetail.description)
                Text("Numero di ripetizioni: \(itemDetail.count)")
                Spacer(minLength: 40)                
                Button("Incrementa ripetizioni") {
                    self.itemDetail.count += 1
                }
            }
        }
    }
}

And here is the ContentView which links to the Detail View:

struct ContentView: View {
    @ObservedObject var trackedObjects = Tracked()
    @State private var showingAddItemView = false

    var body: some View {
        NavigationView {
            List {
                ForEach(trackedObjects.items) { object in
                    NavigationLink(destination: DetailView(itemDetail: object)) {
                        HStack {
                            VStack(alignment: .leading) {
                                Text(object.name)
                                    .font(.headline)
                                Text(object.description)
                            }
                            .frame(maxWidth: 200, maxHeight: 100)
                            Spacer()
                            Text("Completed \(object.count) times")
                        }
                    }
//                    Tile()
                }
            }
        .navigationBarTitle("Test")
        .navigationBarItems(trailing:
            HStack(spacing: 15) {
                EditButton()
                Button(action: {
                    self.showingAddItemView = true
                }) {
                    Image(systemName: "plus")
                }
            })
        .sheet(isPresented: $showingAddItemView) {
            AddItemView(trackedObjects: self.trackedObjects)
        }

        }
    }
}

And these are my struct and Class:

struct TrackedItem: Identifiable, Codable {
    let id = UUID()
    var name: String
    var description: String
    var count: Int
}

class Tracked: ObservableObject {
    @Published var items = [TrackedItem]() {

Right now, I pass the struct to the detail view, but even if I update it (thank you for the @State tip! It worked!) then it's not update in the actual item in the class. How can I pass the struct back and forth between the views?

Thank you very much!

3      

Hi Gabriele, For your DetailView to be able to send back changes, that property would have to be a Binding (to a property in ContentView). Unfortunately, it cannot be bound to a data element of a ForEach loop that's iterating over objects.

The least amount of changes to your code to get what you want: Instead of passing in an item to DetailView, the loop would pass the index of the current item + provide the DetailView with trackedObjects through the environment. Then any changes that DetailView does to the environment object will be reflected in the original.

P.S.

using "count" as a name for your own properties is not prohibited, but might get you into unexpected situations that take time to figure out. For example:

  • self.trackedObjects.items[index].count <- that's your property
  • self.trackedObjects.items.count <- that's Swift's computed property

If you named yours "counter" or anything else, you'd be sure not to get tangled with "count"

struct ContentView: View {
    @ObservedObject var trackedObjects = Tracked()
    @State private var showingAddItemView = false

    var body: some View {
        NavigationView {
            List {
                ForEach(trackedObjects.items.indices, id: \.self) { index in
                    NavigationLink(destination: DetailView(index: index).environmentObject(self.trackedObjects)) {
                        HStack {
                            VStack(alignment: .leading) {
                                Text(self.trackedObjects.items[index].name)
                                    .font(.headline)
                                Text(index.description)
                            }
                            .frame(maxWidth: 200, maxHeight: 100)
                            Spacer()
                            Text("Completed \(self.trackedObjects.items[index].count) times")
                        }
                    }
                    //                    Tile()
                }
            }
            .navigationBarTitle("Test")
            .navigationBarItems(trailing:
                HStack(spacing: 15) {
                    EditButton()
                    Button(action: {
                        self.showingAddItemView = true
                    }) {
                        Image(systemName: "plus")
                    }
            })
                .sheet(isPresented: $showingAddItemView) {
                    AddItemView(trackedObjects: self.trackedObjects)
            }

        }
    }
}

struct DetailView: View {
    @EnvironmentObject var trackedObjects: Tracked
    var index: Int

    var body: some View {
        ScrollView(.vertical) {
            VStack {
                Text(trackedObjects.items[index].name)
                    .font(.title)
                    .fontWeight(.bold)
                Text(trackedObjects.items[index].description)
                Text("Numero di ripetizioni: \(trackedObjects.items[index].count)")
                Spacer(minLength: 40)
                Button("Incrementa ripetizioni") {
                    self.trackedObjects.items[self.index].count += 1
                }
            }
        }
    }
}

6      

Hi,

Sorry for the late answer. Thank you very much for your detailed answer, it's very helpful. Maybe this topic si a bit more advanced that what I've been working on, but that's a great occasion to learn. I'll try this as soon as I get to the computer, and report back. Thank you very much!

2      

Hi Daniel,

It worked! Thank you so much!

Out of curiosity, how would you have implemented on your own if you had to start from scratch? Would you have done something similar?

3      

Hacking with Swift is sponsored by Essential Developer

SPONSORED Join a FREE crash course for mid/senior iOS devs who want to achieve an expert level of technical and practical skills – it’s the fast track to being a complete senior developer! Hurry up because it'll be available only until April 28th.

Click to save your free spot now

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.