SwiftUI VStack cell onTap acting strange

676 views Asked by At

I have a bunch of cells in a vertical scroll view, but am seeing some weird behavior from the onTapGesture and fullScreenCover for those cells. When I click on a cell, it registers the correct cell in onTapGesture but presents the wrong object, in the fullScreenCover it looks like it's being executed many times, and presenting the wrong object. Not sure why this is happening, anyone have any insight on what the issue could be?

Here are the print statements from clicking 1 cell:

TAP HERE: 1935
FULL SCREEN: 1942
FULL SCREEN: 1940
FULL SCREEN: 1935
FULL SCREEN: 1934

    struct TakesList: View {
        
        @ObservedObject var viewModel: TakesListViewModel
        @State private var isPresented = false
        
        var body: some View {
            ScrollView(.vertical, showsIndicators: false) {
                LazyVStack {
                    ForEach(viewModel.takes, id: \.self) { take in
                        ListTakeCell(presenter: TakeCellViewModel(take: take))
                            .padding(.horizontal, 10)
                            .padding(.vertical, 6)
                            .onTapGesture {
                                let _ = print("TAP HERE: \(take.id)")
                                UIImpactFeedbackGenerator(style: .light).impactOccurred()
                                isPresented = true
                            }
                            .fullScreenCover(isPresented: $isPresented, onDismiss: nil, content: {
                                let _ = print("FULL SCREEN: \(take.id)")
                                SingleTakeView(take: take)
                                    .edgesIgnoringSafeArea(.bottom)
                            })
                            
                    }
                }
            }
        }
    }
1

There are 1 answers

0
MarlonJames On

Maybe you can try this:

struct TakesList: View {
        
    @ObservedObject var viewModel: TakesListViewModel
    @State private var isPresented = false

    private var selectedTake: Take? = nil {
        didSet {
            guard selectedTake != nil else { return }
            isPresented = true
        }
    }
        
    var body: some View {
        ScrollView(.vertical, showsIndicators: false) {
            LazyVStack {
                ForEach(viewModel.takes, id: \.self) { take in
                    ListTakeCell(presenter: TakeCellViewModel(take: take))
                        .padding(.horizontal, 10)
                        .padding(.vertical, 6)
                        .onTapGesture {
                            let _ = print("TAP HERE: \(take.id)")
                            UIImpactFeedbackGenerator(style: .light).impactOccurred()
                            selectedTake = take
                        }   
                }
            }
            .fullScreenCover(isPresented: $isPresented, onDismiss: nil, content: {
                guard let take = selectedTake else { return }
                let _ = print("FULL SCREEN: \(take.id)")
                SingleTakeView(take: take)
                    .edgesIgnoringSafeArea(.bottom)
            })
        }
    }
}

Added the selectedTake property. This is kind of a workaround for not being able to use fullScreenCover(item. When the cell is tapped, you set the selectedTake property with the take that was tapped. Then a didSet gets triggered which checks to make sure selectedTake isn't nil. If it isn't, then it sets isPresented to true. The fullScreenCover(isPresented modifier is moved out of the list onto the LazyVStack which still gets triggered if isPresented becomes true. I haven't tested this, but it's an idea that could work.

Curious if this works for you. Cheers!