Search code examples
iosswiftswiftuiswiftui-navigationlink

NavigationLink and Menu inside Horizontal ScrollView


I am trying to create an app, where I have a horizontal scrollview inside list. And tapping on that horizontal scrollview, each item should navigate to another screen and there is a button on each item, which shows the context menu. But, both of these functionalities don't work together. If navigatiionlink works, menu doesnt pop up on tapping(it does pop on long press though).

Here's the code:

    
    var categoryName: String
    
    var items: [Post]
    
    var body: some View {
        
        VStack(alignment: .leading) {
            
            Text(categoryName)
                
                .font(.headline)
                
                .padding(.leading, 15)
            
            ScrollView(.horizontal, showsIndicators: false) {
                
                HStack(alignment: .top, spacing: 0) {
                    
                    ForEach(items) { post in
                        
                        VStack {
                            
                            NavigationLink(
                                destination: PostView(post: post)) {
                                EmptyView()
                            }
                        
//                            NavigationLink(
//                                destination: PostView(post: post)) {
                                
                                CategoryItem(categoryName: "Featured", post: post)
                                    
                                    .frame(width: (UIScreen.main.bounds.width - 90), height: 155)
                                    
                                    .padding(.leading, 10)
                                    
                                    .onAppear {
                                        if (self.items.last?.id == post.id) {
                                            print("Last Featured")
                                        }
                                    }
//                            }
                        }
                        
                    }
                    
                }
                
            }
            
            .frame(height: 185)
            
            .padding(.top, 0)
            
        }
        
    }
    
}

And here is the category item:

VStack(alignment: .trailing, spacing: nil, content: {
                
                Menu {
                    
                    VStack {
                        
                        Button(action: {
                            print("Hello")
                            if (!checkIfAlreadySaved(post: post, viewContext: viewContext)) {
                                self.bookmarkPost(post: post, viewContext: viewContext) {
//                                    self.isBookmarked = true
                                }
                            } else {
                                self.unBookmarkPost(post: post, viewContext: viewContext) {
//                                    self.isBookmarked = false
                                }
                            }
                        }) {
                            Text(!checkIfAlreadySaved(post: post, viewContext: viewContext) ? "Add to Favourites" : "Remove from Favourites")
                        }
                        Button(action: {
                            print("Hello")
                            self.actionSheet(urlString: post.link)
                        }) {
                            Text("Share")
                        }
                        
                    }

                }  label: {
                    Image(systemName: "ellipsis")
                        
                        .font(.subheadline)
                        
                        .foregroundColor(.white)
                        
//                        .rotationEffect(.degrees(-90))
                        
                        //                        .position(CGPoint(x: 0.0, y: 20.0))
                        
//                        .frame(width: 30, height: 30, alignment: .trailing)
                        
                        .padding(8)
                    
//                        .zIndex(2.0)
                        
                }
            })

There is more code in CategoryItem too, but, there are just other views and all of this is inside ZStack.

With the above code, menu pops up, but navigation link doesn't work. As you can see, I have also tried putting CategoryItem inside NavigationLink(commented out code), this results in navigationlink being working, but menu only pops on long press.

Can anyone please help with this? Thanks

P.S. The whole view is navigated from another view, which is embedded inside a List, which is further embedded in a NavigationView. Also, the issue is in a scrollview, it works with list, but i need a horizontal scroll view, so, can't use that.


Solution

  • It could be how you are organizing your Views maybe the ZStack you aren't showing is what is conflicting.

    In your code the NavigationLink likely doesn't pop up because you have made it an EmptyView

    If you make clear distinctions for the tappable areas you should not have an issue.

    The first example makes the tappable area for the ellipsis a small square

    import SwiftUI
    
    struct NavLinkMenuView: View {
        var body: some View {
            GeometryReader{geo in
                NavigationView{
                    List{
                        ForEach(0..<3){ count in
                            VStack{
                                Text("category \(count)")
                                NavLinkRowView(geo: geo)
                            }
                        }
                    }.listStyle(PlainListStyle())
                }
            }
        }
    }
    struct NavLinkRowView: View {
        let geo: GeometryProxy
        var body: some View {
            ScrollView(.horizontal){
                HStack{
                    ForEach(0..<15){ count in
                        ZStack{
                            //The the user to the next page
                            NavigationLink(destination: Text(count.description), label: {
                                Text(count.description)
                                //Make the NavigationLink take all the available space
                                    .frame(maxWidth: .infinity, maxHeight: .infinity)
                                //Just to make a visible marke of the view
                                    .border(Color.green, width: 2)
                            })
                            HStack{
                                Spacer()
                                //CategoryItem
                                VStack(alignment: .trailing, spacing: nil){
                                    Menu(content: {
                                        Text("context menu")
                                    }, label: {
                                        
                                        Image(systemName: "ellipsis")
                                        //stretch the tappable area to the ellipsis column
                                            .frame(maxWidth: .infinity, maxHeight: .infinity)
                                        
                                            .font(.subheadline)
                                            .foregroundColor(.gray)
                                            .padding(8)
                                    }
                                    )
                                }
                                .aspectRatio(1, contentMode: .fit)
                                //Limit the size of the ellipsis column
                                .frame(maxWidth: geo.size.width * 0.08)
                                
                                //Just to make a visible marke of the view
                                .border(Color.red)
                            }
                        }
                        //Just to make a visible marke of the view
                        .border(Color.orange)
                        //Size of the overall view
                        .frame(width: (geo.size.width - 90), height: 155)
                        //Just to make a visible marke of the view
                        .border(Color.blue)
                        .padding(.leading, 10)
                    }
                }
            }
        }
    }
    

    The second example creates a tappable column for the ellipsis

    import SwiftUI
    
    struct NavLinkMenuView: View {
        var body: some View {
            GeometryReader{geo in
                NavigationView{
                    List{
                        ForEach(0..<3){ count in
                            VStack{
                                Text("category \(count)")
                                NavLinkRowView(geo: geo)
                            }
                        }
                    }.listStyle(PlainListStyle())
                }
            }
        }
    }
    struct NavLinkRowView: View {
        let geo: GeometryProxy
        var body: some View {
            ScrollView(.horizontal){
                HStack{
                    ForEach(0..<15){ count in
                        HStack(spacing: 0){
                            //The the user to the next page
                            NavigationLink(destination: Text(count.description), label: {
                                Text(count.description)
                                //Make the NavigationLink take all the available space
                                    .frame(maxWidth: .infinity, maxHeight: .infinity)
                                //Just to make a visible marke of the view
                                    .border(Color.green, width: 2)
                            })
                            
                            //CategoryItem
                            VStack(alignment: .trailing, spacing: nil){
                                Menu(content: {
                                    Text("context menu")
                                }, label: {
                                    
                                    Image(systemName: "ellipsis")
                                    //stretch the tappable area to the ellipsis column
                                        .frame(maxWidth: .infinity, maxHeight: .infinity)
                                    
                                        .font(.subheadline)
                                        .foregroundColor(.gray)
                                        .padding(8)
                                }
                                )
                            }
                            //Limit the size of the ellipsis column
                            .frame(maxWidth: geo.size.width * 0.08, maxHeight: .infinity)
                            
                            //Just to make a visible marke of the view
                            .border(Color.red)
                            
                        }
                        //Just to make a visible marke of the view
                        .border(Color.orange)
                        //Size of the overall view
                        .frame(width: (geo.size.width - 90), height: 155)
                        //Just to make a visible marke of the view
                        .border(Color.blue)
                        .padding(.leading, 10)
                    }
                }
            }
        }
    }