swiftswiftuiswiftui-tabviewios17

How to remove the empty space from TabView using page style in SwiftUI?


I have a NavigationStack where I have a TabView and its style as page style and a List after the TabView.

Inside the TabView I have another custom view that has a horizontal List and I want to remove the space between the the inner child and the parent.

I tried with padding, plain list style but the space is still there.

I can use frame modifier but I don't want a hard coded value for the height.

Here is the code I tried:

import SwiftUI

struct CustomView: View {
    var dataArray: [String]

    var body: some View {
        List {
            LazyHStack {
                ForEach(dataArray, id: \.self) { item in
                    Button("\(item)") {
                    }
                    .buttonStyle(.bordered)
                }
            }
        
        }
        .scrollDisabled(true)
    }
}

struct ContentView: View {
    struct Sea: Hashable, Identifiable {
        let name: String
        let id = UUID()
    }

    struct OceanRegion: Identifiable {
        let name: String
        let seas: [Sea]
        let id = UUID()
    }

    private let oceanRegions: [OceanRegion] = [
        OceanRegion(name: "Pacific",
                    seas: [Sea(name: "Australasian Mediterranean"),
                           Sea(name: "Philippine"),
                           Sea(name: "Coral"),
                           Sea(name: "South China")]),
        OceanRegion(name: "Atlantic",
                    seas: [Sea(name: "American Mediterranean"),
                           Sea(name: "Sargasso"),
                           Sea(name: "Caribbean")]),
        OceanRegion(name: "Indian",
                    seas: [Sea(name: "Bay of Bengal")]),
        OceanRegion(name: "Southern",
                    seas: [Sea(name: "Weddell")]),
        OceanRegion(name: "Arctic",
                    seas: [Sea(name: "Greenland")])
    ]

    @State private var singleSelection: UUID?

    @State private var weekIndex = 0
    @State private var weeks = [ -1, 0, 1 ]

    @State private var dataArray1 = ["1","2","3","4","5","6","7"]
    @State private var dataArray2 = ["8","9","10","11","12","13","14"]
    @State private var dataArray3 = ["15","16","17","18","19","20","21"]

    var body: some View {
        NavigationStack {
            VStack {
                TabView(selection: $weekIndex) {
                    CustomView(dataArray: dataArray1).tag(weeks[0])
                    CustomView(dataArray: dataArray2).tag(weeks[1])
                    CustomView(dataArray: dataArray3).tag(weeks[2])
                }
                .tabViewStyle(.page(indexDisplayMode: .never))
                .indexViewStyle(.page(backgroundDisplayMode: .never))
            }
                
            List(selection: $singleSelection) {
                ForEach(oceanRegions) { region in
                    Section(header: Text("Major \(region.name) Ocean Seas")) {
                        ForEach(region.seas) { sea in
                            NavigationLink(destination: EmptyView()) {
                                Text(sea.name)
                            }
                        }
                    }
                }
            }
            .navigationTitle("Oceans and Seas")
            .toolbar {
                ToolbarItem {
                    Button(action: {}) {
                        Label("Add", systemImage: "plus")
                    }
                }
            }
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

Here is a screenshot:

enter image description here

Is there a way to remove that empty space?

How can I expand the list inside the tab view to take all the width and zero padding at the top?

How can I set TabView's height dynamically, to take its child height?


Solution

  • As Benzy mentioned the TabView used inside NavigationStack is not recommended so I finaally found an alternative that works really well.

    I used ScrollViewReader and ScrollView(.horizontal) together with some modifiers like .containerRelativeFrame(.horizontal), .scrollTargetLayout() and .scrollTargetBehavior(.paging). Other modifiers can be used to get the scroll position for example.

    It is taking the minimum space and is swipeable.

    The ScrollViewReader is not entirely necessary if the start is at index 0 but if the initial display is not index 0 then it is necessary to preset to which index to scrollTo. Also this can be controlled using a button for example.

    Here is my code:

    import SwiftUI
    
    struct CustomView: View {
        var dataArray: [String]
    
        var body: some View {
            HStack {
                ForEach(dataArray, id: \.self) { item in
                    Button("\(item)") {
                        print("\(item)")
                    }
                    .buttonStyle(.bordered)
                }
            }
        }
    }
    
    struct ContentView: View {
        struct Sea: Hashable, Identifiable {
            let name: String
            let id = UUID()
        }
    
        struct OceanRegion: Identifiable {
            let name: String
            let seas: [Sea]
            let id = UUID()
        }
    
        private let oceanRegions: [OceanRegion] = [
            OceanRegion(name: "Pacific",
                        seas: [Sea(name: "Australasian Mediterranean"),
                               Sea(name: "Philippine"),
                               Sea(name: "Coral"),
                               Sea(name: "South China")]),
            OceanRegion(name: "Atlantic",
                        seas: [Sea(name: "American Mediterranean"),
                               Sea(name: "Sargasso"),
                               Sea(name: "Caribbean")]),
            OceanRegion(name: "Indian",
                        seas: [Sea(name: "Bay of Bengal")]),
            OceanRegion(name: "Southern",
                        seas: [Sea(name: "Weddell")]),
            OceanRegion(name: "Arctic",
                        seas: [Sea(name: "Greenland")])
        ]
    
        @State private var singleSelection: UUID?
    
        @State private var weekIndex: Int?
        @State private var weeks = [ -1, 0, 1 ]
    
        @State private var dataArray1 = ["1","2","3","4","5","6","7"]
        @State private var dataArray2 = ["8","9","10","11","12","13","14"]
        @State private var dataArray3 = ["15","16","17","18","19","20","21"]
    
        var body: some View {
            NavigationStack {
                ScrollViewReader { value in
                    ScrollView(.horizontal) {
                        HStack {
                            CustomView(dataArray: dataArray1)
                                .containerRelativeFrame(.horizontal)
                                .id(weeks[0])
                            CustomView(dataArray: dataArray2)
                                .containerRelativeFrame(.horizontal)
                                .id(weeks[1])
                            CustomView(dataArray: dataArray3)
                                .containerRelativeFrame(.horizontal)
                                .id(weeks[2])
                        }
                        .scrollTargetLayout()
                    }
                    .scrollTargetBehavior(.paging)
                    .scrollIndicators(.hidden)
                    .onAppear {
                        value.scrollTo(weeks[1])
                    }
                    .scrollPosition(id: $weekIndex)
                }
            
                List(selection: $singleSelection) {
                    ForEach(oceanRegions) { region in
                        Section(header: Text("Major \(region.name) Ocean Seas")) {
                            ForEach(region.seas) { sea in
                                NavigationLink(destination: EmptyView()) {
                                    Text(sea.name)
                                }
                            }
                        }
                    }
                 }
                .navigationTitle("Oceans and Seas")
                .toolbar {
                    ToolbarItem {
                        Button(action: {}) {
                            Label("Add", systemImage: "plus")
                        }
                    }
                }
            }
        }
    }
    
    struct ContentView_Previews: PreviewProvider {
        static var previews: some View {
            ContentView()
        }
    }
    

    enter image description here

    Thanks everyone!