Sam
Sam

Reputation: 63

SwiftUI Sheet not updating variable

I'm trying to pass an item to a sheet, if I change the values by selecting another item. The sheet keeps displaying the first item, even when I never called the sheet with the first item.

Is this a bug or am I doing something wrong?

WorkoutList.swift

import SwiftUI

struct WorkoutList: View {
    private var workouts = workoutData
    @State var showWorkoutDetails = false

    var body: some View {
        ScrollView(.horizontal, showsIndicators: false) {
            HStack(spacing: 20) {
                ForEach(workouts) { workoutCard in
                    GeometryReader { geometry in
                        Button(action: {self.showWorkoutDetails.toggle()}) {
                            WorkoutCardView(workout: workoutCard)
                                .rotation3DEffect(Angle(degrees: Double((geometry.frame(in: .global).minX - 30) / -30)), axis: (x: 0, y: 10, z: 0))
                                .sheet(isPresented: self.$showWorkoutDetails) {
                                    WorkoutDetails(workout: workoutCard)
                                }
                        }
                    }
                    .frame(width:246, height: 360)

                }
            }
        .padding(30)
        }
    }
}

private let workoutData = [
    Workout(name: "Workout #1", imageName: "workoutImage", color: Color.orange),
    Workout(name: "Workout #2", imageName: "workoutImage", color: Color.green),
    Workout(name: "Workout #3", imageName: "workoutImage", color: Color.red),
    Workout(name: "Workout #4", imageName: "workoutImage", color: Color.blue)
]

WorkoutDetails.swift

import SwiftUI

struct WorkoutDetails: View {
    var workout = Workout(name: "", imageName: "", color: Color.black)

    var body: some View {
        GeometryReader { geometry in
            ZStack {
                Rectangle()
                    .fill(self.workout.color)

                VStack(alignment: .leading) {
                    VStack(alignment: .leading) {
                        Text(self.workout.name)
                            .foregroundColor(.white)
                            .font(.title)
                            .fontWeight(.heavy)
                            .lineLimit(nil)
                            .frame(minWidth: 0, maxWidth: .infinity, alignment: .leading)
                            .frame(height: 70)
                    }
                    .frame(width: 180)

                    Image(self.workout.imageName)
                        .resizable()
                        .aspectRatio(contentMode: .fit)
                        .frame(width: geometry.size.width-60, height: 200)
                        .padding()

                    Spacer()
                }
                .padding(.top, 40)
                .padding(.leading, 10)
            }
        }
        .edgesIgnoringSafeArea(.bottom)
    }
}

Here I want to display a workout. But whatever I change it, I always get "workout #1" and I don't know what I'm doing wrong.

Upvotes: 5

Views: 3495

Answers (5)

sgtpotatoe
sgtpotatoe

Reputation: 430

I think this is much better explained here SwiftUI sheet doesn't access the latest value of state variables on first appearance

struct DemoView: View {
  @State var showDetails: Bool = false
  var body: some View {
    VStack {
        Button(action: {
          showDetails = true
        }) {
            Text("Show sheet")
        }
    }.sheet(isPresented: $showDetails){
        SheetDetailView(flag: $showDetails)
    }
  }
}

struct SheetDetailView: View {
  @Binding var flag: Bool
     var body: some View {
       VStack {
          Text("showDetails: \(flag ? "yes" : "no")")
           }
     }
}

Just make the parameter in the sheet view binding, and send in a binding parameter, and you can still conserve the boolean toggle.

Upvotes: 2

Ronak Kalavadia
Ronak Kalavadia

Reputation: 397

  1. Pass a Binding instead of the value of the @State variable
  2. Use the value of the Binding in the body of the view included in the sheet
struct WorkoutList: View {
    @State var isDetailPresented = false
    @State var selectedWorkout: Workout?

    var body: some View {
        Button(action: {
            self.selectedWorkout = workouts[yourIndex]
            self.showWorkoutDetails.toggle()
        }) {
            Text("Test")
        }
        .sheet(isPresented: self.$showWorkoutDetails) {
            WorkoutDetails(workout: selectedWorkout)
        }
    }
}
struct WorkoutDetails: View {
    @Binding var workout: Workout?

    var body: some View {
        ZStack {
            Rectangle()
                .fill(self.workout.color)
            // your code
        }
    }
}

Upvotes: 2

Tai Le
Tai Le

Reputation: 9276

Basically we need to store selectedWorkout when the button tapped.

@State var isDetailPresented = false
@State var selectedWorkout: Workout?

...

Button(action: {
    self.selectedWorkout = workoutCard
    self.isDetailPresented.toggle()
}, label: {
    //Your view here
}).buttonStyle(PlainButtonStyle())
  .sheet(isPresented: $isDetailPresented) {
    WorkoutDetails(workout: self.selectedWorkout!)
}
...

Upvotes: 0

jnblanchard
jnblanchard

Reputation: 1200

Create a binding with an optional workout. Nil will be the case that the sheet is not presented, assigning a workout to this variable displays the corresponding sheet.

struct WorkoutList: View {
  private var workouts = workoutData
  @State var selectedWorkout: Workout?

  var body: some View {
    ScrollView(.horizontal, showsIndicators: false) {
      HStack(spacing: 20) {
        ForEach(workouts) { workoutCard in
          GeometryReader { geometry in
            Button(action: { self.selectedWorkout = workoutCard }) {
              WorkoutCardView(workout: workoutCard).rotation3DEffect(Angle(degrees: Double((geometry.frame(in: .global).minX - 30) / -30)), axis: (x: 0, y: 10, z: 0)).sheet(item: self.$selectedWorkout) { workout in
                WorkoutDetails(workout: workout)
              }
            }
          }.frame(width:246, height: 360) 
        }
      }.padding(30)
    }
  }
}

Upvotes: 5

Bartosz
Bartosz

Reputation: 388

I had similar problem. You want to assign workoutCard to @State and inside .sheet have some logic that decides what view to show

Here is more on your problem: Multiple sheet(isPresented:) doesn't work in SwiftUI

Upvotes: 0

Related Questions