mica
mica

Reputation: 4308

SwiftUI reduce spacing of rows in a list to null

I want to reduce the linespacing in a list to null.

My tries with reducing the padding did not work. Setting ´.environment(.defaultMinListRowHeight, 0)´ helped a lot.

struct ContentView: View {
  @State var data : [String] = ["first","second","3rd","4th","5th","6th"]

  var body: some View {
    VStack {
      List {
        ForEach(data, id: \.self)
        { item in
          Text("\(item)")
          .padding(0)
          //.frame(height: 60)
          .background(Color.yellow)
        }
        //.frame(height: 60)
        .padding(0)
        .background(Color.blue)
      }
      .environment(\.defaultMinListRowHeight, 0)
      .onAppear { UITableView.appearance().separatorStyle = .none }
      .onDisappear { UITableView.appearance().separatorStyle = .singleLine }
    }
  }
}

Changing the ´separatorStyle´ to ´.none´ only removed the Line but left the space.
Is there an extra ´hidden´ view for the Lists row or for the Separator between the rows? How can this be controlled?

Would be using ScrollView instead of a List a good solution?

ScrollView(.horizontal, showsIndicators: true)
        {
      //List {
        ForEach(data, id: \.self)
        { item in
          HStack{
          Text("\(item)")
            Spacer()
          }

Does it also work for a large dataset?

Upvotes: 6

Views: 10936

Answers (3)

Peter Kreinz
Peter Kreinz

Reputation: 8620

I do it the easy (iOS) SwiftUI way:

struct ContentView: View {
    
    init() {
        UITableView.appearance().separatorStyle = .none
    }
    
    var body: some View {
        List {
            ForEach(0..<10){ item in
                Color.green
            }
            .listRowInsets( EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0) )
        }
    }
}

Upvotes: 6

Asperi
Asperi

Reputation: 257523

Well, actually no surprise - .separatorStyle = .none works correctly. I suppose you confused text background with cell background - they are changed by different modifiers. Please find below tested & worked code (Xcode 11.2 / iOS 13.2)

demo

struct ContentView: View {
  @State var data : [String] = ["first","second","3rd","4th","5th","6th"]

  var body: some View {
    VStack {
      List {
        ForEach(data, id: \.self)
        { item in
          Text("\(item)")
            .background(Color.yellow) // text background
            .listRowBackground(Color.blue) // cell background
        }
      }
      .onAppear { UITableView.appearance().separatorStyle = .none }
      .onDisappear { UITableView.appearance().separatorStyle = .singleLine }
    }
  }
}

Update:

it's not possible to avoid the blue space between the yellow Texts?

Technically yes, it is possible, however for demo it is used hardcoded values and it is not difficult to fit some, while to calculate this dynamically might be challenging... anyway, here it is

demo2

it needs combination of stack for compression, content padding for resistance, and environment for limit:

  List {
    ForEach(data, id: \.self)
    { item in
        HStack {                                 // << A
          Text("\(item)")
            .padding(.vertical, 2)               // << B
        }
        .listRowBackground(Color.blue)
        .background(Color.yellow)
        .frame(height: 12)                       // << C
    }
  }
  .environment(\.defaultMinListRowHeight, 12)    // << D

Upvotes: 7

user3441734
user3441734

Reputation: 17534

Reduce row spacing is really tricky, try

struct ContentView: View {
    @State var data : [String] = ["first","second","3rd","4th","5th","6th"]

    var body: some View {
        VStack {
            ScrollView {
                ForEach(data, id: \.self) { item in
                    VStack(alignment: .leading, spacing: 0) {
                        Color.red.frame(height: 1)
                        Text("\(item)").font(.largeTitle)
                            .background(Color.yellow)
                    }.background(Color.green)
                    .padding(.leading, 10)
                    .padding(.bottom, -25)
                    .frame(maxWidth: .infinity)
                }
            }
        }
    }
} 

It use ScrollView instead of List and negative padding.

enter image description here

I didn't find any solution based on List, we have to ask Apple to publish xxxxStyle protocols and underlying structures.

UPDATE

What about this negative padding value? For sure it depends on height of our row content and unfortunately on SwiftUI layout strategy. Lets try some more dynamic content! (we use zero padding to demostrate the problem to solve)

struct ContentView: View {
    @State var data : [CGFloat] = [20, 30, 40, 25, 15]

    var body: some View {
        VStack {
            ScrollView {
                ForEach(data, id: \.self) { item in
                    VStack(alignment: .leading, spacing: 0) {
                        Color.red.frame(height: 1)
                        Text("\(item)").font(.system(size: item))
                            .background(Color.yellow)
                    }.background(Color.green)
                    .padding(.leading, 10)
                    //.padding(.bottom, -25)
                    .frame(maxWidth: .infinity)
                }
            }
        }
    }
}

enter image description here

Clearly the row spacing is not fixed value! We have to calculate it for every row separately.

Next code snippet demonstrate the basic idea. I used global dictionary (to store height and position of each row) and tried to avoid any high order functions and / or some advanced SwiftUI technic, so it is easy to see the strategy. The required paddings are calculated only once, in .onAppear closure

import SwiftUI

var _p:[Int:(CGFloat, CGFloat)] = [:]

struct ContentView: View {
    @State var data : [CGFloat] = [20, 30, 40, 25, 15]
    @State var space: [CGFloat] = []

    func spc(item: CGFloat)->CGFloat {
        if let d = data.firstIndex(of: item) {
            return d < space.count ? space[d] : 0
        } else {
            return 0
        }
    }

    var body: some View {
        VStack {
            ScrollView {
                ForEach(data, id: \.self) { item in
                    VStack(alignment: .leading, spacing: 0) {
                        Color.red.frame(height: 1)
                        Text("\(item)")
                            .font(.system(size: item))
                            .background(Color.yellow)
                    }
                    .background(
                        GeometryReader { proxy->Color in
                            if let i = self.data.firstIndex(of: item) {
                                _p[i] = (proxy.size.height, proxy.frame(in: .global).minY)
                            }
                            return Color.green
                        }
                    )
                    .padding(.leading, 5)
                    .padding(.bottom, -self.spc(item: item))

                    .frame(maxWidth: .infinity)
                }.onAppear {
                    var arr:[CGFloat] = []
                    _p.keys.sorted(by: <).forEach { (i) in
                        let diff = (_p[i + 1]?.1 ?? 0) - (_p[i]?.1 ?? 0) - (_p[i]?.0 ?? 0)
                        if diff < 0 {
                            arr.append(0)
                        } else {
                            arr.append(diff)
                        }
                    }
                    self.space = arr
                }
            }
        }
    }
}

Running the code I've got

enter image description here

Upvotes: 2

Related Questions