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:
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))