Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Sheet inside ForEach doesn't loop over items SwiftUI

I have an issue using a sheet inside a ForEach. Basically I have a List that shows many items in my array and an image that trigger the sheet. The problem is that when my sheet is presented it only shows the first item of my array which is "Harry Potter" in this case.

Here's the code

struct ContentView: View {
    @State private var showingSheet = false
    
    var movies = ["Harry potter", "Mad Max", "Oblivion", "Memento"]
    var body: some View {
        NavigationView {
            List {
                ForEach(0 ..< movies.count) { movie in
                    HStack {
                        Text(self.movies[movie])
                        Image(systemName: "heart")
                    }
                        .onTapGesture {
                            self.showingSheet = true
                    }
                    .sheet(isPresented: self.$showingSheet) {
                        Text(self.movies[movie])
                    }
                }
            }
        }
    }
}
like image 247
xmetal Avatar asked Jul 25 '20 13:07

xmetal


3 Answers

There should be only one sheet, so here is possible approach - use another sheet modifier and activate it by selection

Tested with Xcode 12 / iOS 14 (iOS 13 compatible)

extension Int: Identifiable {
    public var id: Int { self }
}

struct ContentView: View {
    @State private var selectedMovie: Int? = nil

    var movies = ["Harry potter", "Mad Max", "Oblivion", "Memento"]
    var body: some View {
        NavigationView {
            List {
                ForEach(0 ..< movies.count) { movie in
                    HStack {
                        Text(self.movies[movie])
                        Image(systemName: "heart")
                    }
                        .onTapGesture {
                            self.selectedMovie = movie
                    }
                }
            }
            .sheet(item: self.$selectedMovie) {
                Text(self.movies[$0])
            }
        }
    }
}
like image 158
Asperi Avatar answered Nov 03 '22 05:11

Asperi


I changed your code to have only one sheet and have the selected movie in one variable.

extension String: Identifiable {
    public var id: String { self }
}

struct ContentView: View {
    @State private var selectedMovie: String? = nil
    
    var movies = ["Harry potter", "Mad Max", "Oblivion", "Memento"]
    var body: some View {
        NavigationView {
            List {
                ForEach(movies) { movie in
                    HStack {
                        Text(movie)
                        Image(systemName: "heart")
                    }
                    .onTapGesture {
                        self.selectedMovie = movie
                    }
                }
            }
            .sheet(item: self.$selectedMovie, content: { selectedMovie in
                Text(selectedMovie)
            })
        }
    }
}
like image 26
José Augusto Paiva Avatar answered Nov 03 '22 05:11

José Augusto Paiva


Wanted to give my 2 cents on the matter. I was encountering the same problem and Asperi's solution worked for me. BUT - I also wanted to have a button on the sheet that dismisses the modal.

When you call a sheet with isPresented you pass a binding Bool and so you change it to false in order to dismiss. What I did in the item case is I passed the item as a Binding. And in the sheet, I change that binding item to nil and that dismissed the sheet.

So for example in this case the code would be:

var movies = ["Harry potter", "Mad Max", "Oblivion", "Memento"]
var body: some View {
    NavigationView {
        List {
            ForEach(0 ..< movies.count) { movie in
                HStack {
                    Text(self.movies[movie])
                    Image(systemName: "heart")
                }
                    .onTapGesture {
                        self.selectedMovie = movie
                }
            }
        }
        .sheet(item: self.$selectedMovie) {
            Text(self.movies[$0])

            // My addition here: a "Done" button that dismisses the sheet

            Button {
                selectedMovie = nil
            } label: {
                Text("Done")
            }

        }
    }
}
like image 1
Rutang Avatar answered Nov 03 '22 03:11

Rutang