Sheet View in Recipe App

I am attempting to make a recipe app similar to the one used in the iOS Foundations Course, but instead of the List View presenting like a Navigation Link with a Back Button, I want it to display a sheet like the Featured Recipe View does in the course’s app for each recipe I have.

I’ve tried pulling the relevant (in my opinion) code from each piece to make this work, but my current problem is that when I press the button created for each different recipe, the sheet that comes up only ever displays the first recipe in the index (position 0).

I have the @State var for this:

@State var isDetailViewShowing = false

I’m looping through each recipe and my Button’s action is set to toggle my @State var, and I have the .sheet modifier selected with the same isPresented parameters as the course’s recipe app:

ForEach(0..<model.recipes.count) { index in
                                
                                Button {
                                    self.isDetailViewShowing.toggle()
                                    
                                } label: {
                                    // Recipe card
                                    ZStack (alignment: .leading) {
                                        Rectangle()
                                            .foregroundStyle(.ultraThinMaterial)
                                        
                                        HStack(spacing: 10) {
                                            Image(model.recipes[index].image)
                                                .resizable()
                                                .scaledToFill()
                                                .frame(width: 90, height: 90, alignment: .center)
                                                .clipped()
                                                .cornerRadius(10)
                                            VStack (alignment: .leading) {
                                                Text(model.recipes[index].name)
                                                    .font(.title3)
                                                    .multilineTextAlignment(.leading)
                                                
                                                HStack {
                                                    Image(systemName: "timer")
                                                        .resizable()
                                                        .frame(width: 15, height: 15)
                                                    Text(model.recipes[index].totalTime)
                                                        .font(.caption)
                                                        .fontWeight(.light)
                                                        .padding(.trailing, 3)
                                                    
                                                    Image(systemName: "heart.text.square")
                                                        .resizable()
                                                        .frame(width: 15, height: 15)
                                                    Text("\(model.recipes[index].calories) calories")
                                                        .font(.caption)
                                                        .fontWeight(.light)
                                                    
                                                }
                                            }
                                        }
                                        
                                    }
                                }
                                .sheet(isPresented: $isDetailViewShowing) {
                                    // Show the Recipe Detail View
                                    RecipeDetailView(recipe: model.recipes[index])

I can’t figure this out and would love help in this area! Thanks so much in advance!

@colbyacorcoran

Hi Colby, Welcome to the community.

Suggestion:

Define a @State variable to keep track of your selectedRecipeIndex. ie:

@State private var selectedRecipeIndex = 0

In your button action before you toggle isDetailViewShowing insert the following:

selectedRecipeIndex = index

In your .sheet modifier closure change the code to:

RecipeDetailView(recipe: model.recipes[selectedRecipeIndex]

Let me know if that works.

That did it! Thank you so much for your help!

Since you now don’t have a back button to return to the previous view, as you would have if you were using a NavigationLink, you can either swipe the view down or implement a button to dismiss the view.

So the solution initially worked, and I implemented the code, but now I’m running into an issue where it doesn’t seem like selectedRecipeIndex isn’t updating the way it should. When I run the app, if I click on any recipe besides the first one, it still displays the first recipe’s sheet and information, as if selectedRecipeIndex is still set to 0 rather than updating to the index number from the ForEach.

I have to select the first recipe and dismiss the sheet multiple times before clicking on a different recipe actually pulls up the correct recipe sheet and information.

Any ideas what’s happening or what I might be doing wrong?

What does your ListView code look like overall?
What does your data model code look like (Recipe properties)?

Here’s my ListView code:

struct RecipeListView: View {
    
    @EnvironmentObject var model:RecipeModel
    @State var isDetailViewShowing = false
    @State var selectedRecipeIndex = 0
    @State private var filterBy = ""
    
    
    private var filteredRecipes: [Recipe] {
        
        if filterBy.trimmingCharacters(in: .whitespacesAndNewlines) == "" {
            return Array(model.recipes)
        }
        else {
            return model.recipes.filter { r in
                return r.name.contains(filterBy)
            }
        }
    }
    
    var body: some View {
        
        ZStack {
            
            LinearGradient (colors: [Color("Light Peach"), Color("Light Blue")], startPoint: .topLeading, endPoint: .bottomTrailing)
                .ignoresSafeArea()
            
            VStack (alignment: .leading) {
                
                HStack {
                    Text("Food Made Simple")
                        .font(.largeTitle)
                        .fontWeight(.bold)
                        .foregroundColor(Color("Dark Blue"))
                        .padding(.top, 20)
                        .padding(.leading, 10)
                        .padding(.trailing, 5)
                    
                    Image("Chef Image")
                        .resizable()
                        .renderingMode(.template)
                        .foregroundColor(Color("Dark Blue"))
                        .frame(width: 80, height: 80)
                }
                
                
                SearchBarView(filterBy: $filterBy)
                    .padding([.leading, .trailing, .bottom])
                
                
                if model.recipes.isEmpty {
                    ProgressView()
                        .padding(.leading, 50)
                }
                else {
                    ScrollView () {
                        LazyVStack (alignment: .center) {
                            ForEach(0..<model.recipes.count) { index in
                                
                                Button {
                                    selectedRecipeIndex = index
                                    self.isDetailViewShowing.toggle()
                                } label: {
                                    // Recipe card
                                    ZStack (alignment: .leading) {
                                        Rectangle()
                                            .foregroundStyle(.ultraThinMaterial)
                                        
                                        HStack(spacing: 10) {
                                            Image(model.recipes[index].image)
                                                .resizable()
                                                .scaledToFill()
                                                .frame(width: 90, height: 90, alignment: .center)
                                                .clipped()
                                                .cornerRadius(10)
                                            VStack (alignment: .leading) {
                                                Text(model.recipes[index].name)
                                                    .font(.title3)
                                                    .multilineTextAlignment(.leading)
                                                
                                                HStack {
                                                    Image(systemName: "timer")
                                                        .resizable()
                                                        .frame(width: 15, height: 15)
                                                    Text(model.recipes[index].totalTime)
                                                        .font(.caption)
                                                        .fontWeight(.light)
                                                        .padding(.trailing, 3)
                                                    
                                                    Image(systemName: "heart.text.square")
                                                        .resizable()
                                                        .frame(width: 15, height: 15)
                                                    Text("\(model.recipes[index].calories) calories")
                                                        .font(.caption)
                                                        .fontWeight(.light)
                                                    
                                                }
                                            }
                                        }
                                        
                                    }
                                }
                                .sheet(isPresented: $isDetailViewShowing) {
                                    // Show the Recipe Detail View
                                    RecipeDetailView(recipe: model.recipes[selectedRecipeIndex])
                                }
                                .buttonStyle(PlainButtonStyle())
                                .frame(width: 395, height:  90, alignment: .center)
                                .cornerRadius(10)
                                
                            }
                            
                            
                        }
                        
                        
                    }
                    
                }
                
            }

        }
        
        
    }
        
}

And then here are the Recipe properties from the Recipe Model code:

class Recipe: Identifiable, Decodable {
    
    var id:UUID?
    var name:String
    var favorite:Bool
    var image:String
    var prepTime:String
    var cookTime:String
    var totalTime:String
    var calories:String
    var fat:String
    var carbs:String
    var protein:String
    var servings:Int
    var actualServings:Int
    var category:String
    var ingredients:[Ingredient]
    var directions:[String]
    
}

class Ingredient: Identifiable, Decodable {
    
    var id:UUID?
    var name:String
    var num:Int?
    var denom:Int?
    var unit:String?
}

I’m thinking now that this is a bit difficult to test without having access to the entire project. I could dummy up some data but I’m struggling for time at the moment.

If it is OK with you can you share your project via DropBox or Google Drive by compressing it at the top level folder and posting that compressed file. If you want to share it privately rather than posting the link in a reply here then by all means direct message me.

I have to head out pretty soon (it’s morning where I am) so I can have a look at the project tonight.