Home > Blockchain >  How to pass the selected object in a List to a second view when a button in a row is tapped
How to pass the selected object in a List to a second view when a button in a row is tapped

Time:09-16

In the following code, I have a list that has a button in each row. What I want is to be able to pass the selected Fruit from the ContentView to the SecondView when the button is tapped. Right now I can present the SecondView but it's blank, it has no text in it, in other words, I don't see the text, Second View: [fruit name]. Please note that I cannot use the NavigationLink(destination:) because I'm already using it to go to a details view when the rows are tapped.

How can I pass the selected fruit to the Second View when the button on each row is tapped?

Fruit Object

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

Content View:

struct ContentView: View {
    @State private var secondViewIsPresented = false
    
    var fruits = [Fruit(name: "Apple"), Fruit(name: "Orange")]
    
    var body: some View {
        List{
            ForEach(fruits){ fruit in
                HStack{
                    Text(fruit.name)
                    
                    Button("Tap Me"){
                        secondViewIsPresented.toggle()
                        SecondView(selectedFruit: fruit)
                    }
                    .frame(width:150, height: 35)
                    .background(Color.blue)
                    .buttonStyle(BlueButton())
                }
            }
        }
        .sheet(isPresented: $secondViewIsPresented){
            // Here I cannot access the selected fruit
        }
    }
}

// style to make the button in the row work properly
// I'm showing it just for reference.
struct BlueButton: ButtonStyle {
    func makeBody(configuration: Self.Configuration) -> some View {
        configuration.label
            .scaleEffect(configuration.isPressed ? 2 : 1)
    }
}

Second View

struct SecondView: View {
    var selectedFruit: Fruit
    
    var body: some View {
        Text("Second View: \(selectedFruit.name)")
    }
}

CodePudding user response:

First, declaring SecondView inside the button's action closure has no effect. All this does is create a SecondView, then throw it away.

Button("Tap Me"){
    secondViewIsPresented.toggle()
    SecondView(selectedFruit: fruit) /// nope!
}

You want to present the selected fruit inside a sheet, right? But as you said, the problem is that you can't access the selected fruit from within the sheet.

That's where sheet(item:onDismiss:content:) comes in. This alternate version of sheet gets triggered when the item is not nil. You're then able to access the selected item inside its content closure.

struct ContentView: View {
//    @State private var secondViewIsPresented = false // delete this line
    @State private var selectedFruit: Fruit? /// replace with this
    
    var fruits = [Fruit(name: "Apple"), Fruit(name: "Orange")]
    
    var body: some View {
        List{
            ForEach(fruits){ fruit in
                HStack{
                    Text(fruit.name)
                    
                    Button("Tap Me"){
                        selectedFruit = fruit /// set the fruit
                    }
                    .frame(width:150, height: 35)
                    .background(Color.blue)
                    .buttonStyle(BlueButton())
                }
            }
        }
        .sheet(item: $selectedFruit) { fruit in /// access selected fruit inside content closure
            SecondView(selectedFruit: fruit)
        }
    }
}

Result:

Sheet gets presented with correct item

CodePudding user response:

I would suggest you use EnvironmentObject for this.

Fruit struct remains same as before.

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

You create an ObservableObject which is passed to FirstView during initialization.

class DataState: ObservableObject {
    @Published var fruit: Fruit? = nil
}

Now the first view becomes

struct FirstView: View {
    @State private var secondViewIsPresented = false
    //This has been added
    @EnvironmentObject var dataState: DataState
    var fruits = [
        Fruit(name: "Apple"),
        Fruit(name: "Orange")
    ]
    
    var body: some View {
        List{
            ForEach(fruits){ fruit in
                HStack{
                    Text(fruit.name)
                    
                    Button("Tap Me"){
                        secondViewIsPresented.toggle()
                        //This has been changed
                        dataState.fruit = fruit
                        SecondView()
                    }
                    .frame(width:150, height: 35)
                    .background(Color.blue)
                    .buttonStyle(BlueButton())
                }
            }
        }
        .sheet(isPresented: $secondViewIsPresented){
            // Here I cannot access the selected fruit
        }
    }
}

// style to make the button in the row work properly
// I'm showing it just for reference.
struct BlueButton: ButtonStyle {
    func makeBody(configuration: Self.Configuration) -> some View {
        configuration.label
            .scaleEffect(configuration.isPressed ? 2 : 1)
    }
}

Also, you now access the passed object using EnvironmentObject

struct SecondView: View {
    //This has been added
    @EnvironmentObject var dataState: DataState

    var body: some View {
        Text("Second View: \(dataState.fruit?.name ?? "")")
    }
}

Finally, you need to make sure that you are passing in the DataState during the initialization of the FirstView

var appState = DataState()
FirstView()
    .environmentObject(self.appState))
  • Related