Search code examples
iosswiftswiftuiswiftui-navigationstack

Can ScrollViews and NavigationStacks work together in SwiftUI?


I've started to develop on SwiftUI very recently (hence the simple question), so as an exercise to work with layouts, I'm trying to recreate Apple's own Music app. The main issue I'm facing is NavigationStack and lack of resources about it considering how new it is.

I've written the following code to replicate the Library tab:

NavigationStack{
    
    List(menuCategories) {
        menuCategory in NavigationLink(value: menuCategory
        ){
            Label{
                Text(menuCategory.name)
                    .font(.title2)
            } icon:{ Image(systemName: menuCategory.imageName).foregroundStyle(.red)
            }
        }
        .padding(.horizontal, -20)
        
    }.navigationDestination(for: MenuCategory.self) {
        menuCategory in
        Text(menuCategory.name)
    }
    
    
    ScrollView(.vertical, showsIndicators: true){
        
        VStack(alignment: .leading){
            
            Text("Recently Added")
                .font(.headline)
                .padding(.horizontal, 20.0)
            
            LazyVGrid(columns: [GridItem(.adaptive(minimum: 130), spacing: 20)]
            ){
                ForEach(recentAlbums, id: \.self) {
                    
                    item in
                    VStack(alignment: .leading){
                        ZStack{
                            Color(item.color)
                            Text(item.name)
                        }
                        
                        .cornerRadius(10)
                        .frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity)
                        .aspectRatio(1.0, contentMode: .fit)
                        
                        Text(item.name)
                        Text(item.artist)
                            .foregroundStyle(Color(.gray))
                    }
                    .padding(.bottom, 10.0)
                    .font(.body)
                    .lineLimit(1)
                }
            }
            .padding(.horizontal, 20.0)
            
        }
        .navigationTitle("Library")
        .toolbar {
            ToolbarItem(placement: .navigationBarTrailing) {
                Button("Edit") {
                    self.screen = 0;
                }
            }
        }
    }
}
.scrollContentBackground(.hidden)
.tabItem {
    Image(systemName: "play.square.stack")
    Text("Library")
}

Here's a preview of how it's looking:

Preview

The issue is, on both the Preview and the Simulator, the top List (Artists, Albums, etc) and the bottom Grid (Recently Added albums) have separate scrolling, so the whole page can't be scrolled down, you scroll on either of the areas, see the bottom area of the following screenshot as an example: Top and bottom halves scroll separately

I've tried to move the top List into the ScrollView, but the whole List disappears and only the Grid is shown on screen. I've tried to move the different elements around but haven't found a combination that works yet... Any help will be greatly appreciated.

Thank you!


Solution

  • From my experience it's very hard to get any good result if you are using List with anything else. So I suggest one of 2 options:

    1. Use only List. It doesn't have to contain all the same items. You can put your links on top, and LazyVGrid at the bottom, e.g.:
    struct HorizontalRow: View { // <-- first item type in the list
        
        var text: String
        var systemName: String
        
        var body: some View {
            HStack {
                Text(text)
                Image(systemName: systemName)
            }
        }
    }
    
    struct GridView: View { // <-- second item type in the list
        
        var recentAlbums = [ "A", "B", "C" ]
        
        var body: some View {
            LazyVGrid(columns: [GridItem(.adaptive(minimum: 130), spacing: 20)]){
                ForEach(recentAlbums, id: \.self) { item in
                    VStack {
                        Image(systemName: "mic")
                        Text(item)
                    }
                }
            }
        }
    }
    
    struct MainScreenView: View { 
        
        var listItems = ["One", "Two", "Three"]
        
        var body: some View {
            List { // <-- the list with different items
                ForEach(listItems, id: \.self) { item in
                    HorizontalRow(text: item, systemName: "keyboard")
                }
                GridView()
            }
        }
    }
    
    1. Replace the list with ScrollView { LazyVStack {, and place your elements (including grid) directly in LazyVStack. This is better if you want a full control of how things look. But downside is you need to provide more styling where list already has it.