Search code examples
swiftxcodelistswiftuiswiftui-navigationlink

SwiftUI: Cannot use mutating member on immutable value: 'shop' is a 'let' constant


I would like to add a Product to a Shop's container but i can't and i don't understand why because my Shop is var and not let. My goal is to put a Product into a Shop like this: input Shop(name: "Apple Store", container: []) output: Shop(name: "Apple Store", container: [Product(name: "Cheese")])

Here is My code:

import SwiftUI


struct Shop: Identifiable {
    let name: String
    var container: [Product]
    var id = UUID()
}

struct Product: Identifiable {
    let name: String
    var id = UUID()
}

struct ContentView: View {
    
    @State var shops: [Shop] = [
        Shop(name: "Apple Store", container: []),
        Shop(name: "StopShop", container: [Product(name: "milk")])
    ]
    
    var body: some View {
        NavigationView {
            List {
                ForEach(shops) { shop in
                    NavigationLink(shop.name, destination: {
                        List {
                            ForEach(shop.container) { product in
                                Text(product.name)
                            }
                        }
                        .navigationBarTitle(shop.name)
                        .navigationBarItems(trailing: Button {
                            shop.container.append(Product(name: "Cheese"))
                        } label: {
                            Text("add").bold()
                        })
                    })
                }
            }
            .navigationBarTitle("Shops")
        }
    }
}


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

I have tried by append or insert it and i expected it to work but it didn't. :(


Solution

  • The error tells the truth: Index variables in a loop are constants in Swift (since the initial release).

    Due to value semantics you have to modify the Shop element in the shops array directly.

    Add a function inside the view

    func appendProduct(_ product: Product, to shop: Shop) {
        guard let index = shops.firstIndex(where: {$0.id == shop.id}) else { return }
        shops[index].container.append(product)
    }
    

    and call it

    .navigationBarItems(trailing: Button {
        appendProduct(Product(name: "Cheese"), to: shop)
    } label: {
        Text("add").bold()
    })