Ben Myers
Ben Myers

Reputation: 1395

Remove top padding from List

I've seen similar questions like this one here on Stack Overflow, but none of them have been able to answer my question.

I have created a List, but I want to remove the padding space (marked with a red arrow) above the content in the List. How can I remove it when using a GroupedListStyle?

Screenshot

This is a List within a VStack within a NavigationView that is displayed via fullscreenCover:

var body: some View {
  NavigationView {
    VStack {
      taskEventPicker
      myList
    }
    .navigationBarTitle("Add Task", displayMode: .inline)
  }
}

where taskEventPicker is a segmented Picker (boxed in green):

var taskEventPicker: some View {
  Picker("Mode", selection: $selection) { /* ... */ }
  .pickerStyle(SegmentedPickerStyle())
  .padding()
}

and myList is the form in question (boxed in yellow):

var myList: some View {
  List { /* ... */ }
  .listStyle(GroupedListStyle())
}

What I've Tried

Note: I'm looking for an answer that can apply to the GroupedListStyle. I understand that this issue does not occur with PlainListStyle. This padding issue also occurs with the default listStyle.

Xcode version: 12.5

Upvotes: 44

Views: 37207

Answers (16)

Ashot Kirakosyan
Ashot Kirakosyan

Reputation: 1

List {
Section(header: Spacer(minLength: -16)) {
ForEach(viewModel.filteredAccounts, id: \.id) { account in
}.listStyle(InsetGroupedListStyle())
.scrollContentBackground(.hidden)
.environment(\.defaultMinListHeaderHeight, 0)
you need to use these 2 modifiers together
 Section(header: Spacer(minLength: -16)) {
.environment(\.defaultMinListHeaderHeight, 0)

Upvotes: 0

Xaxxus
Xaxxus

Reputation: 1839

The correct approach is to use:

List {
   ///...
}
.contentMargins(0, .top, .scrollContent)

Unfortunately, this api is iOS 17+ only.

Upvotes: 1

Patrick
Patrick

Reputation: 2432

If you are targeting iOS 17 or later, the recommended approach is to use the contentMargins modifier. You can remove the vertical spacing like this:

List {
    // ...
}
.contentMargins(.top, 0)

Upvotes: 20

Andrei G.
Andrei G.

Reputation: 1557

While some of the above suggestions do apply, in my case it was something else.

I had similar extra spacing that I couldn't get rid of, but it was caused by a .contentMargins(.top, 50) modifier that was following the .inspector sheet loading the view containing the List.

Upvotes: 0

michael23
michael23

Reputation: 3934

Took me a while to find a solution I was happy with. The issue is when using .navigationBarTitleDisplayMode(.inline) it won't allow to have no header for the first section, so a header is added automatically with default listRowInsets and defaultMinListHeaderHeight.

To control the top spacing, there are two things:

  • add a section header with 0 height to the first Section and no list edge insets
  • adjust .environment(\.defaultMinListHeaderHeight, 12) with a value you're happy with. This will be your top padding.
NavigationStack {
        List {
            Section {
                Text("Row 1")
                Text("Row 2")
                Text("Row 3")
            } header: {
                Spacer(minLength: 0).listRowInsets(EdgeInsets())
            }
        }
        .listStyle(.insetGrouped)
        .environment(\.defaultMinListHeaderHeight, 0)
        .navigationTitle("Title")
        .navigationBarTitleDisplayMode(.inline)
    }

Upvotes: 16

MkVal
MkVal

Reputation: 1064

In iOS 17, just set the toolbarTitleDisplayMode to inline like so:

var body: some View {
  NavigationStack(path: $path) {
    List {
      // Your items here
    }
    .listStyle(.plain)
    .toolbarTitleDisplayMode(.inline)
  }
}

enter image description here

Upvotes: 0

swiftsquirrel
swiftsquirrel

Reputation: 33

You specifically mention it is not an option for you to use PlainListStyle, which is understandable. However, are you simply looking for an alternative to PlainListStyle that does not have the padded sides? If so, I'd recommend trying InsetListStyle.

The fat header is a core feature of GroupedListStyle. Generally if I have to hack my way out of a core feature of anything, it's a red flag. I think we'd have to know more about why GroupedListStyle feels like the only option for you, but my gut feeling here is there is an easier, more out-of-the-box way, and it may be InsetListStyle.

Upvotes: 1

OgabekDev
OgabekDev

Reputation: 281

There is another possible solution

List {...}
.onAppear {
   UICollectionView.appearance().contentInset.top = -20
}

you can use this modifier in the list

Upvotes: 1

theNoobDev10
theNoobDev10

Reputation: 425

Add top padding for List and Spacer with minLength 0 for first Section Header

NavigationView {
    List { 
       Section {
         Text("My Header")
       } header: {
            Spacer(minLength: 0)
    }
    .listStyle(.grouped)
    .padding(.top, -20)
}
.navigationBarTitleDisplayMode(.inline)

}

Upvotes: 0

lukbl
lukbl

Reputation: 277

Another option is to add

List { ... }.offset(x: 0, y: -30).edgesIgnoringSafeArea(.bottom)

to the List. Since you won't be able to scroll all the way up when using this add a Spacer(minLength: 30) at the very bottom of the List.

Upvotes: 3

Ashi
Ashi

Reputation: 161

extension View {
    /// Clear tableview and cell color
    func tableClearColor() {
        let tableHeaderView = UIView(frame: .zero)
        tableHeaderView.frame.size.height = 0.5
        UITableView.appearance().tableHeaderView = tableHeaderView
    }
}

Upvotes: -1

Niels
Niels

Reputation: 57

The contentInsent part of the accepted answer works perfectly fine unless for instance as in my case, at the end of a navigation stack you want to use it in some detail view containing a List with navigationBarTitleDisplayMode set to .inline. When navigating back in the stack to a view using another List with navigationBarTitleDisplayMode set to .large, the List content and NavigationBar interlace awfully. And they probably might as well do whatever the navigationBarTitleDisplayMode is set to.

Switching to listStyle(.plain) in my case isn't a feasable option, because then in the detail view, I loose the beautifully animated built-in transitions to and from List's EditMode, that seem to exist only in the grouped listStyle varieties.

Finally, after quite a few days of frustration I figured out that tableHeaderView is the approach that works best for my problem - and of course, it works just as well to solve the original question. It's all in here ...:

XCode documentation

... and in this sentence:

"When assigning a view to this property [i.e. tableHeaderView], set the height of that view to a nonzero value"

So I do like:

List {
  // bla bla bla
}
.listStyle(.grouped)
.onAppear {
  let tableHeaderView = UIView(frame: .zero)
  tableHeaderView.frame.size.height = 1
  UITableView.appearance().tableHeaderView = tableHeaderView
}

It's as simple as that. Hope it might help you, too!

Upvotes: 4

ingconti
ingconti

Reputation: 11666

my 2 cents. I arrived here for the same reason.

take a look at: https://developer.apple.com/forums/thread/662544

it' seems the correct approach.

Upvotes: -1

rbaldwin
rbaldwin

Reputation: 4888

Firstly, I would say that GroupedListStyle is working as intended.

On iOS, the grouped list style displays a larger header and footer than the plain style, which visually distances the members of different sections.

You say you have tried this, but it does work for me (Xcode 12.5.1):

    List { ... }    
    .onAppear(perform: {
        UITableView.appearance().contentInset.top = -35
    })

You could also hide the list header, by using a ZStack with the List at the bottom of the stack and the Picker over the top. The Picker does have transparency, so you would also have to add an opaque view to act as background for the Picker.

var body: some View {
    NavigationView {
        ZStack(alignment: .top) {
            List { ... }
            .listStyle(.grouped)
            .padding(.top, 30)
            
            Color.white
                .frame(height: 65)
            
            Picker { ... }
            .pickerStyle(.segmented)
            .padding()
        }
        .navigationBarTitle("Add Task", displayMode: .inline)
    }
}

enter image description here

As far as I can see this just appears the same as PlainListStyle would do, but I assume you have a particular reason for wanting to use GroupedListStyle.

Upvotes: 34

Codezy
Codezy

Reputation: 5570

I had a similar setup where I had some views and a List in a VStack and I had an undesired space that appeared on the top of my List. In my case I needed to set the VStack spacing to 0 and that solved the issue because it was actually just spacing from the VStack.

VStack(spacing: 0) { ... }

Upvotes: 6

SlimeBaron
SlimeBaron

Reputation: 775

Give a header to the first section in your list: Section(header: Spacer(minLength: 0))

Disclaimer: this doesn't totally remove the top spacing, but it does yield the same result as the native Settings app.

Note: This worked for me on iOS 14.5

VStack {
    List {
        Section(header: Spacer(minLength: 0)) {
            Text(verbatim: "First Section")
        }

        Section {
            Text(verbatim: "Second Section")
        }
    }
    .listStyle(GroupedListStyle())
}

Without Section Header With Section Header Native Settings App

Upvotes: 10

Related Questions