mallow
mallow

Reputation: 2836

How to access value from an item in ForEach list

How to access values from particular item on the list made with ForEach? As you can see I was trying something like this (and many other options):

Text(item[2].onOff ? "On" : "Off")

I wanted to check the value of toggle of 2nd list item (for example) and update text on the screen saying if it's on or off.

And I know that it's something to do with @Binding and I was searching examples of this and trying few things, but I cannot make it to work. Maybe it is a beginner question. I would appreciate if someone could help me.

My ContentView:

struct ContentView: View {

    //  @Binding var onOff : Bool
    @State private var onOff = false
    @State private var test = false

    var body: some View {
        NavigationView {
            List {

                HStack {
                    Text("Is 2nd item on or off? ")
                    Text(onOff ? "On" : "Off")
//                  Text(item[2].onOff ? "On" : "Off")
                }

                ForEach((1...15), id: \.self) {item in
                    ListItemView()
                }

            }
            .navigationBarTitle(Text("List"))
        }
    }
}

And ListItemView:

import SwiftUI

struct ListItemView: View {

    @State private var onOff : Bool = false
    //  @Binding var onOff : Bool

    var body: some View {
        HStack {

            Text("99")
                .font(.title)

            Text("List item")

            Spacer()

            Toggle(isOn: self.$onOff) {
                Text("Label")
            }
            .labelsHidden()
        }
    }
}

Upvotes: 0

Views: 538

Answers (3)

Paul B
Paul B

Reputation: 5117

import SwiftUI

struct ContentView: View {

        @State private var onOffList = Array(repeating: true, count: 15)

        var body: some View {
            NavigationView {
                List {

                    HStack {
                        Text("Is 2nd item on or off? ")
                        Text(onOffList[1] ? "On" : "Off")
                    }
                    ForEach((onOffList.indices), id: \.self) {idx in
                        ListItemView(onOff: self.$onOffList[idx])
                    }
                }
                .navigationBarTitle(Text("List"))
            }
        }
    }

    struct ListItemView: View {

        @Binding var onOff : Bool

        var body: some View {
            HStack {
                Text("99")
                    .font(.title)
                Text("List item")
                Spacer()
                Toggle(isOn: $onOff) {
                    Text("Label")
                }
                .labelsHidden()
            }
        }
    }

Upvotes: 1

mallow
mallow

Reputation: 2836

I understand that you are directing me to use ObservableObject. And probably it's the best way to go with final product. But I am still thinking about @Binding as I just need to pass values better between 2 views only. Maybe I still don't understand binding, but I came to this solution.

struct ContentView: View {

    //  @Binding var onOff : Bool
    @State private var onOff = false
//  @State private var test = false

    var body: some View {
        NavigationView {
            List {

                HStack {
                    Text("Is 2nd item on or off? ")
                    Text(onOff ? "On" : "Off")
//                  Text(self.item[2].$onOff ? "On" : "Off")
//                  Text(item[2].onOff ? "On" : "Off")
                }

                ForEach((1...15), id: \.self) {item in
                    ListItemView(onOff: self.$onOff)
                }

            }
            .navigationBarTitle(Text("List"))
        }
    }
}

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

and ListItemView:

import SwiftUI

struct ListItemView: View {

//  @State private var onOff : Bool = false
    @Binding var onOff : Bool

    var body: some View {
        HStack {

            Text("99")
                .font(.title)

            Text("List item")

            Spacer()

            Toggle(isOn: self.$onOff) {
                Text("Label")
            }
            .labelsHidden()
        }
    }
}

What is happening now is text is being updated after I tap toggle. But I have 2 problems:

  • tapping 1 toggle changes all of them. I think it's because of this line:

    ListItemView(onOff: self.$onOff)

  • I still cannot access value of just one row. In my understanding ForEach((1...15), id: .self) make each row have their own id, but I don't know how to access it later on.

Upvotes: 0

krjw
krjw

Reputation: 4450

I don't know what exactly you would like to achieve, but I made you a working example:

struct ListItemView: View {

    @ObservedObject var model: ListItemModel

    var body: some View {
        HStack {

            Text("99")
                .font(.title)

            Text("List item")

            Spacer()

            Toggle(isOn: self.$model.switchedOnOff) {
                Text("Label")
            }
            .labelsHidden()
        }
    }
}

class ListItemModel: ObservableObject {
    @Published var switchedOnOff: Bool = false
}

struct ContentView: View {

    @State private var onOff = false
    @State private var test = false

    @State private var list = [
        (id: 0, model: ListItemModel()),
        (id: 1, model: ListItemModel()),
        (id: 2, model: ListItemModel()),
        (id: 3, model: ListItemModel()),
        (id: 4, model: ListItemModel())
    ]

    var body: some View {
                NavigationView {
                    List {

                        HStack {
                            Text("Is 2nd item on or off? ")
                            Text(onOff ? "On" : "Off")
        //                  Text(item[2].onOff ? "On" : "Off")
                        }

                        ForEach(self.list, id: \.id) {item in
                            ListItemView(model: item.model)
                        }

                    }
                    .navigationBarTitle(Text("List"))
                }.onReceive(self.list[1].model.$switchedOnOff, perform: { switchedOnOff_second_item in
                    self.onOff = switchedOnOff_second_item
                })
    }
}

The @Published basically creates a Publisher, which the UI can listen to per onReceive().

Play around with this and you will figure out what these things do!

Good luck :)

Upvotes: 1

Related Questions