Custom Initializer Binding Problems

I'm trying to add sodas from ListView to ContentView and have that data passed on and update the view in AisleView. I'm having a lot of trouble getting my columnOne and columnTwo properties to update the AisleView correctly. I know the data is being passed to AisleView, but it isn't updating the actual view. I believe the issue is when I'm trying to initialize my columns. Any help would be appreciated. Thank you!

class Inventory {
    var inventory: [Product] = [
        Product(name: "Coke", price: 2.99, aisle: 1, location: 10),
        Product(name: "Pepsi", price: 3.99, aisle: 1, location: 6),
        Product(name: "Dr. Pepper", price: 1.99, aisle: 2, location: 8),
        Product(name: "Pibb", price: 1.50, aisle: 2, location: 1)
    ]
}

struct ListView: View {
    @State var base = Inventory()
    @State var sodas: [Product] = []
    
    var body: some View {
        VStack {
            ContentView(sodas: $sodas)
            List {
                ForEach(base.inventory) { product in
                        HStack {
                            Text(product.name)
                            Spacer()
                            Text("\(product.price, specifier: "%.2f")")
                            Button {
                                sodas.append(product)
                            } label: {
                                Image(systemName: "plus")
                            }
                        }
                        
                    }
                

            }
        }
     
    }
    }

struct ListView_Previews: PreviewProvider {
    static var previews: some View {
        ListView()
    }
}

struct ContentView: View {
    @Binding var sodas: [Product]
    @State private var columnOne: [Product] = []
    @State private var columnTwo: [Product] = []
    
    init(sodas: Binding<[Product]>) {
        self._sodas = sodas
        self.columnOne = aisleSort(sodas: self.sodas, aisle: 1)
        self.columnTwo = aisleSort(sodas: self.sodas, aisle: 2)
    }
    

    
    var body: some View {
        VStack {
            HStack(spacing: 10) {
            AisleView(products: $columnOne)
            AisleView(products: $columnTwo)
            }
        }
    }
    func aisleSort(sodas: [Product], aisle: Int) -> [Product] {
        var sort: [Product] = []

        for soda in sodas {
            if soda.aisle == aisle {
                sort.append(soda)
            }
        }
        return sort
    }
}

struct AisleView: View {
    @Binding var products: [Product]
    @State private var buttonNumber = 0
    
    var location: [Int] {
        var answer: [Int] = []
        for number in products {
            answer.append(number.location)
        }
        return answer
    }
   
    func idicator(number: Int) -> Color {
        if location.contains(number) {
            return Color.red
        } else {
            return Color.primary
        }
    }
    
    var body: some View {
        ZStack {
            VStack(alignment: .center, spacing: 0) {
                ForEach(1..<21, id: \.self) {number in
                    ZStack {
                        if location.contains(number) {
                            Button {
                                buttonNumber = number
                            } label: {
                                HStack {
                                    Rectangle()
                                        .foregroundColor(.red)
                                    .frame(width: 20, height: 20)
                                }
                                   
                            }
                            .overlay(buttonNumber == number ? InfoView(sodas: products, number: number).offset(x: -70) : nil)
                                
                        } else {
                            HStack {
                                Rectangle()
                                    .frame(width: 20, height: 20)
                                .foregroundColor(idicator(number: number))
                                Text("\(number)")

                            }
                        }
                      
                    }
                }
            }
        }
    
    }
}

struct InfoView: View {
    @State var sodas: [Product]
    @State var number: Int
    
    var body: some View {
        VStack {
            ForEach(sodas) { soda in
                if soda.location == number {
                    ZStack {
                        RoundedRectangle(cornerRadius: 10)
                            .foregroundColor(.clear)
                            .background(.regularMaterial)
                            .clipShape(RoundedRectangle(cornerRadius: 10))
                        VStack {
                            Text(soda.name)
                            Text("$\(soda.price, specifier: "%.2f")")
                        }
//                        .font(.title)
                    }
                    .frame(width: 100, height:60)
                }
            }
        }
       
    }
}

struct AisleView_Previews: PreviewProvider {
    static var previews: some View {
        AisleView(products: ListView().$sodas)
    }
}

`
Answered by Claude31 in 763817022

In fact, you don't need aisleSort nor sodas anymore with this code. And you can simplify by removing ContentView and have directly the 2 subviens in ListView:

struct ListView: View {  
    @State var base = Inventory2()
    // @State var sodas: [Product] = []  //
    @State var columnOne: [Product] = []
    @State var columnTwo: [Product] = []

    var body: some View {
        VStack {
            // ContentView(sodas: $sodas, columnOne: $columnOne, columnTwo: $columnTwo)
            HStack(spacing: 10) {
                AisleView(aisleProducts: $columnOne, aisleNumber: 1)
                AisleView(aisleProducts: $columnTwo, aisleNumber: 2)
            }
            List {
                ForEach(base.inventory) { product in
                    HStack {
                        Text(product.name)
                        Spacer()
                        Text("\(product.price, specifier: "%.2f")")
                        Button {
                            // sodas.append(product)
                            if product.aisle == 1 {
                                columnOne.append(product)
                            } else {
                                columnTwo.append(product)
                            }
                        } label: {
                            Image(systemName: "plus")
                        }
                    }
                    
                }
            }
        }
        
    }
}

Could you explain what you expect ? How you add sodas ?

Note: the + on price does not work here.

Firs issue in init of ContentView.

To initialize a State var, you have to do as follows

    init(sodas: Binding<[Product]>) {
        self._sodas = sodas
//        self.columnOne = aisleSort(sodas: self.sodas, aisle: 1)
//        self.columnTwo = aisleSort(sodas: self.sodas, aisle: 2)
        self._columnOne = State(wrappedValue: aisleSort(sodas: self.sodas, aisle: 1))
        self._columnTwo = State(wrappedValue: aisleSort(sodas: self.sodas, aisle: 2))
    }

Second problem: a change to location does not cause a redraw, because it is not a State var.

You could change as follows:

struct AisleView: View {
    @Binding var products: [Product]
    @State private var buttonNumber = 0
    @State var theLoc: [Int] = []
    
    var location: [Int] {
        var answer: [Int] = []

        for product in products {
            answer.append(product.location)
        }
        return answer
    }
   
    init(products: Binding<[Product]>) {
        self._products = products
        self._theLoc = State(wrappedValue: location)
    }
    
    func idicator(number: Int) -> Color {
        if location.contains(number) {
            return Color.red
        } else {
            return Color.green // primary
        }
    }
    
    var body: some View {
        ZStack {
            VStack(alignment: .center, spacing: 0) {
                ForEach(1..<21, id: \.self) {number in
                    ZStack {
                        if theLoc.contains(number) {
                            Button {
                                buttonNumber = number
                                theLoc = location
                                print("theLoc", theLoc)
                            } label: {
                                HStack {
                                    Rectangle()
                                        .foregroundColor(.red)
                                    .frame(width: 20, height: 20)
                                    .overlay(InfoView(sodas: products, number: number).offset(x: -70))
                                }
                                   
                            }
//                            .overlay(buttonNumber == number ? InfoView(sodas: products, number: number).offset(x: -70) : nil)

I don't understand what you expect when tapping button.

The code is really hard to grasp, it should be simplified.

But here is something that works. columnOne and Two should be state var in ListView

struct ListView: View {  
    @State var base = Inventory()
    @State var sodas: [Product] = []
    @State var columnOne: [Product] = []
    @State var columnTwo: [Product] = []

    var body: some View {
        VStack {
            ContentView(sodas: $sodas, columnOne: $columnOne, columnTwo: $columnTwo)
            List {
                ForEach(base.inventory) { product in
                    HStack {
                        Text(product.name)
                        Spacer()
                        Text("\(product.price, specifier: "%.2f")")
                        Button {
                            sodas.append(product)
                            if product.aisle == 1 {
                                columnOne.append(product)
                            } else {
                                columnTwo.append(product)
                            }
                        } label: {
                            Image(systemName: "plus")
                        }
                    }
                    
                }
            }
        }
        
    }
}

struct ContentView: View {
    @Binding var sodas: [Product]
    @Binding private var columnOne: [Product] // = []
    @Binding private var columnTwo: [Product] // = []
    
    init(sodas: Binding<[Product]>, columnOne: Binding<[Product]>, columnTwo: Binding<[Product]>) {
        self._sodas = sodas
        self._columnOne = columnOne
        self._columnTwo = columnTwo
    }
    
    func aisleSort(sodas: [Product], aisle: Int) -> [Product] {
        var sort: [Product] = []

        for soda in sodas {
            if soda.aisle == aisle {
                sort.append(soda)
            }
        }
        return sort
    }

    var body: some View {
        VStack {
            HStack(spacing: 10) {
                AisleView(aisleProducts: $columnOne, aisleNumber: 1)
                AisleView(aisleProducts: $columnTwo, aisleNumber: 2)
            }
        }
    }
    
}

struct AisleView: View {
    @Binding var aisleProducts: [Product]
    @State private var buttonNumber = 0
    var aisleNumber = 0
    
    var location: [Int] {
        var answer: [Int] = []
        
        for product in aisleProducts {
            answer.append(product.location)
        }
        return answer
    }
   
    init(aisleProducts: Binding<[Product]>, aisleNumber: Int) {
        self._aisleProducts = aisleProducts
        self.aisleNumber = aisleNumber
    }
    
    func idicator(number: Int) -> Color {
        if location.contains(number) {
            return Color.red
        } else {
            return Color.green // primary
        }
    }
    
    var body: some View {
        ZStack {
            VStack(alignment: .center, spacing: 0) {
                ForEach(1..<21, id: \.self) {number in
                    ZStack {
                        if location.contains(number) {
                            Button {
                                buttonNumber = number
                            } label: {
                                HStack {
                                    Rectangle()
                                        .foregroundColor(.red)
                                    .frame(width: 20, height: 20)
                                    .overlay(InfoView(sodas: aisleProducts, number: number).offset(x: aisleNumber == 1 ? -70 : 70)) // To position correctly for aisle2
                                }
                                   
                            }
//                            .overlay(buttonNumber == number ? InfoView(sodas: products, number: number).offset(x: -70) : nil)
                                
                        } else {
                            HStack {
                                Rectangle()
                                    .frame(width: 20, height: 20)
                                .foregroundColor(idicator(number: number))
                                Text("\(number)")

                            }
                        }
                      
                    }
                }
            }
        }
    
    }
}

struct InfoView: View {
    @State var sodas: [Product]
    @State var number: Int
    
    var body: some View {
        VStack {
            ForEach(sodas) { soda in
                if soda.location == number {
                    ZStack {
                        RoundedRectangle(cornerRadius: 10)
                            .foregroundColor(.clear)
                            .background(.regularMaterial)
                            .clipShape(RoundedRectangle(cornerRadius: 10))
                        VStack {
                            Text(soda.name)
                            Text("$\(soda.price, specifier: "%.2f")")
                        }
//                        .font(.title)
                    }
                    .frame(width: 100, height:60)
                }
            }
        }
       
    }
}

Accepted Answer

In fact, you don't need aisleSort nor sodas anymore with this code. And you can simplify by removing ContentView and have directly the 2 subviens in ListView:

struct ListView: View {  
    @State var base = Inventory2()
    // @State var sodas: [Product] = []  //
    @State var columnOne: [Product] = []
    @State var columnTwo: [Product] = []

    var body: some View {
        VStack {
            // ContentView(sodas: $sodas, columnOne: $columnOne, columnTwo: $columnTwo)
            HStack(spacing: 10) {
                AisleView(aisleProducts: $columnOne, aisleNumber: 1)
                AisleView(aisleProducts: $columnTwo, aisleNumber: 2)
            }
            List {
                ForEach(base.inventory) { product in
                    HStack {
                        Text(product.name)
                        Spacer()
                        Text("\(product.price, specifier: "%.2f")")
                        Button {
                            // sodas.append(product)
                            if product.aisle == 1 {
                                columnOne.append(product)
                            } else {
                                columnTwo.append(product)
                            }
                        } label: {
                            Image(systemName: "plus")
                        }
                    }
                    
                }
            }
        }
        
    }
}

@Claude31 thank you for taking time to help me out, I really appreciate it. You're absolutely right, my code was much more complicated than it needed to be; having the columns in the original view makes much more sense. I'm new to coding, so sorry for the confusing and messy code haha. Thanks again for the help!

Custom Initializer Binding Problems
 
 
Q