Search code examples
swiftbindingcombine

How to implement a two-way binding in a ForEach statement with nested views with Swift?


I've implemented a two-way binding in a Foreach statement that seems to work properly:

struct ContentView: View {
    @ObservedObject var dataManager: ContentViewModel
    var body: some View {
        ScrollView(.horizontal, showsIndicators: false){
            HStack(spacing: 20){
                ForEach($dataManager.data) { $item in 
                    if item.status == true {
                        Button(item.tag){
                            item.status.toggle()
                        }
                        .foregroundColor(.white)
                        .background(Color.red)
                    } else {
                        Button(item.tag){
                            item.status.toggle()
                        }
                        .foregroundColor(.white)
                        .background(Color.blue)
                    }
                }
            }
        }
    }
}

The problem is when I've to use some nested views like:

struct ContentView: View {
    @ObservedObject var dataManager: ContentViewModel
    var body: some View {
        
        ScrollView(.horizontal, showsIndicators: false){
            HStack(spacing: 20){
                ForEach($dataManager.data) { $item in
                    if item.status == true {
                        DetailView(dataManager: dataManager, item: item)
                            .foregroundColor(.white)
                            .background(Color.red)
                    } else {
                        Button(item.tag){
                            item.status.toggle()
                        }
                        .foregroundColor(.white)
                        .background(Color.blue)
                    }
                }
            }
        }
    }
}

and:

struct DetailView: View {
    
    @ObservedObject var dataManager: ContentViewModel
    var item: SMTTagBoardItem
    
    var body: some View {
        Button(item.tag){
            item.status.toggle()
        }
    }
}

struct DetailView_Previews: PreviewProvider {
    static var previews: some View {
        DetailView(dataManager: ContentViewModel(), item: SMTTagBoardItem(id: UUID(), tag: "tag1", rank: 0, status: true))
    }
}

I receive this error:

Cannot use mutating member on immutable value: 'self' is immutable

at line :

item.status.toggle() 

How can I implement a two-way binding in a ForEach statement that works by passing values to nested views?


Solution

  • First of all, instead of using @ObservedObject, use EnvironmentObject and StateObject.

    Then instead of var item use @Binding var item. (I think dataManager.data is @Published variable)

    ContentView

    struct ContentView: View {
        @StateObject var dataManager: ContentViewModel
        var body: some View {
       
            ScrollView(.horizontal, showsIndicators: false){
                HStack(spacing: 20){
                    ForEach($dataManager.data) { $item in
                        if item.status == true {
                            DetailView(item: $item)
                                .environmentObject(dataManager)
                                .foregroundColor(.white)
                                .background(Color.red)
                        } else {
                            //more code
                        }
                    }
                }
            }
        }
    }
    

    DetailView

    struct DetailView: View {
        
        @EnvironmentObject var dataManager: ContentViewModel
        @Binding var item: SMTTagBoardItem
        
        var body: some View {
            Button(item.tag){
                item.status.toggle()
            }
        }
    }