0

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!

Dávid Pásztor
  • 51,403
  • 9
  • 85
  • 116
Xabi Linazasoro
  • 59
  • 2
  • 13
  • Wrap the `List` and the `ScrollView` in a `VStack` – lorem ipsum Jun 21 '23 at 13:09
  • Tried that and it doesn't work, unfortunately. Both areas still have independent scroll zones. – Xabi Linazasoro Jun 21 '23 at 13:13
  • Got it, there is no built in way to sync scrolling – lorem ipsum Jun 21 '23 at 13:15
  • So there's no way of recreating the Apple Music interface? :( – Xabi Linazasoro Jun 21 '23 at 13:16
  • Not with basic views, of your you can create your own, use UIKit, etc. SwiftUI is still for rapid production and leaves out a lot of fine tuning, it is getting better but it is meant for mass adoption. – lorem ipsum Jun 21 '23 at 13:18
  • 2
    One way is to treat the whole thing as one long `List`. Put the grid into a single row of the list, and use `.listRowSeparator(_:edges:)` modifier (and others) to hide row separators. You could also drop the list, have a `ScrollView` with a `VStack` inside it - and format the elements at the top so that they _look_ like a list. – ScottM Jun 21 '23 at 13:24
  • Thank you @ScottM, that makes a lot of sense, it seems the "Recently Added" text is a Section header. I'll try to create a single List view, thanks! – Xabi Linazasoro Jun 21 '23 at 13:45

1 Answers1

1

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.
timbre timbre
  • 12,648
  • 10
  • 46
  • 77