0

How to pass data to .sheet(isPresented) I have 10 names that I get from my JSON API, but when I click on the name, the .sheet(isPresented) shows one name. In all my 10 .sheet(isPresented) only shows one first name

struct Course: Hashable, Codable {
    let name: String
}

class ViewModel: ObservableObject {
    @Published var courses: [Course] = []

    func fetch() {
        guard let url = URL(string: "JSON URL") else {return}
        let task = URLSession.shared.dataTask(with: url) { [weak self] data, _, error in
            guard let data = data, error == nil else {return}
            do {
                let courses = try JSONDecoder().decode([Course].self, from: data)
                DispatchQueue.main.async {
                    self?.courses = courses
                }
            }
            catch {
                print(error)
            }
        }
        task.resume()
    }
}


struct List: View {
    
    @StateObject var viewModel = ViewModel()
    
    @State var show = false
    
    let columns: [GridItem] = [
        
        GridItem(.flexible(), spacing: nil, alignment: nil),
        GridItem(.flexible(), spacing: nil, alignment: nil),
        GridItem(.flexible(), spacing: nil, alignment: nil)
        
    ]
    
    var body: some View {
        ScrollView(.vertical, showsIndicators: false) {
            LazyVGrid(columns: columns) {
                
                ForEach(viewModel.courses, id: \.self) { course in
                    
                    VStack {
                        
                        Text(course.name)
                            .foregroundColor(.white)
                            .lineLimit(1)
                            .padding(.bottom)
                        
                    }
                    .frame(width: 90, height: 90)
                    .background(Color.gray)
                    .onTapGesture {
                        self.show = true
                        
                    }.sheet(isPresented: self.$show, onDismiss: {
                        
                        self.show = false
                        
                    }, content: {
                        
                        Text(course.name)
                        
                    })
                }
            }
        }
        .onAppear {
            viewModel.fetch()
        }
    }
}

My JSON API from which I get name data looks like this

[
  {
    "id": 1,
    "name": "Alex"
  },
  {
    "id": 2,
    "name": "Sam"
  },
  {
    "id": 3,
    "name": "Den"
  }
]

sorry, I can't write a link here If you do not understand the essence of my question, ask me for the necessary details.

jelhan
  • 6,149
  • 1
  • 19
  • 35
  • Does this answer your question https://stackoverflow.com/a/63089069/12299030? – Asperi May 06 '22 at 16:17
  • No, I don't even use a `List` in my view, and I don't take the data from the project, but from the `JSON API` –  May 06 '22 at 16:26
  • As @Asperi alludes to (the `List` isn't the important part of the answer linked), use only *one* sheet. Also, you likely want to use `sheet(item:)` rather than `sheet(isPresented:)` to avoid stale data. – jnpdx May 06 '22 at 16:26
  • @jnpdx i dont want to use `List` because i am using `LazyVGrid` and `ForEach` –  May 06 '22 at 16:29
  • The `List` element is not relevant to the solution linked to. It's about placement of the `sheet` modifier. Instead of placing it *inside* the loop, place it outside and use `sheet(item:)` – jnpdx May 06 '22 at 16:30
  • @jnpdx I don't really understand how to do this, when I use `List` I get errors https://ibb.co/KNc3K39 –  May 06 '22 at 16:36
  • I am not suggesting you use `List`. The relevant part of the solution *does not have to do with using `List`*. The important part is moving the `sheet` modifier outside of your `ForEach`. – jnpdx May 06 '22 at 16:47
  • @jnpdx I still do not understand what I should do, can you show me with an example of my code? –  May 06 '22 at 16:51

1 Answers1

0

As discussed in the comments, you'll want to switch to the sheet(item:) form and move that outside of your ForEach. You'll also want your model to conform to Identifiable.

Also, even if you don't want to actively use a List here, you should still name your component something else to avoid a name collision with the SwiftUI component of the same name.

struct Course: Hashable, Codable, Identifiable {
    let id: Int
    let name: String
}

class ViewModel: ObservableObject {
    @Published var courses: [Course] = [
        .init(id: 1, name: "Course 1"),
        .init(id: 2, name: "Course 2"),
        .init(id: 3, name: "Course 3"),
    ]
    
    func fetch() {
        //...
    }
}


struct MyList: View {
    
    @StateObject private var viewModel = ViewModel()
    
    @State private var showItem: Course? = nil
    
    let columns: [GridItem] = [
        
        GridItem(.flexible(), spacing: nil, alignment: nil),
        GridItem(.flexible(), spacing: nil, alignment: nil),
        GridItem(.flexible(), spacing: nil, alignment: nil)
        
    ]
    
    var body: some View {
        ScrollView(.vertical, showsIndicators: false) {
            LazyVGrid(columns: columns) {
                
                ForEach(viewModel.courses, id: \.self) { course in
                    
                    VStack {
                        
                        Text(course.name)
                            .foregroundColor(.white)
                            .lineLimit(1)
                            .padding(.bottom)
                        
                    }
                    .frame(width: 90, height: 90)
                    .background(Color.gray)
                    .onTapGesture {
                        self.showItem = course
                    }
                }
                .sheet(item: $showItem) { course in
                    Text(course.name)
                }
            }
        }
        
        .onAppear {
            viewModel.fetch()
        }
    }
}
jnpdx
  • 45,847
  • 6
  • 64
  • 94