Search code examples
ioslistviewswiftui

Apply custom border to each row in a List with SwiftUI


So, I want a List in SwiftUI, where the border of each row depends on some function of the contents of the row. Normally I can do this easily with .border() but List seems to take care of the styling for the elements itself, adding padding and that.

The closest I have is below, which is quite far from what I'm after. This produces the borders around the text fields, but I want instead to produce borders around the whole cell.

struct ListDemoView: View {
    @State private var nums: [Int] = [1, 2, 3, 4, 5]
    
    var body: some View {
        List (nums, id: \.self) { num in
            Section {
                Text("Num: \(num)")
            }
            .border(num % 2 == 0 ? Color.red : Color.blue)
        }

    }
}

enter image description here


Solution

  • A simple way to add a border is to stroke the border of the .listRowBackground. This way, the row insets can be left at the default.

    • When the list style is .insetGrouped (the default), the List applies its own rounded rectangle as clip shape to the section. The corner radius for this clip shape appears to be about 10.

    • To avoid thinning at the corners, the rounded rectangle for the rows needs to use a corner radius at least as large as the clip shape for the section, in other words >= 10.

    • If the border is stroked using .stroke then the clip shape also clips half the width of the border. Use .strokeBorder to keep the stroke inside the border of the shape.

    Btw, the issue of the clip shape affects all the answers to this post.

    List (nums, id: \.self) { num in
        Section {
            Text("Num: \(num)")
        }
        .listRowBackground(
            RoundedRectangle(cornerRadius: 10)
                .fill(.background)
                .strokeBorder(num % 2 == 0 ? Color.red : Color.blue, lineWidth: 2)
        )
    }
    

    Screenshot


    This approach will add a border around each row of the list. Currently, each row of the list is wrapped in its own Section, so the gaps between rows are actually gaps between sections. You can achieve the same look by removing the Section and adding .listRowSpacing to the List:

    List (nums, id: \.self) { num in
        Text("Num: \(num)")
            .listRowBackground(
                RoundedRectangle(cornerRadius: 10)
                    .fill(.background)
                    .strokeBorder(num % 2 == 0 ? Color.red : Color.blue, lineWidth: 2)
            )
    }
    .listRowSpacing(35)