Search code examples
swiftswiftuiswiftui-list

Apply color to list row background conditionally


One can use .listRowBackground to change the color of a list row in SwiftUI. I am trying to use this to color the background of a row conditionally.

What's the correct color to persist the system's default background color for a row where the condition does not apply? Color.clear is wrong, cause it really clears the background instead of falling back to the default. Color.white also is wrong, because it is not agnostic to dark mode...

So, what's the system agnostic default background color of a list in SwiftUI?

TL;DR:

Consider the following (Playground) code:

import SwiftUI
import PlaygroundSupport

struct Item: Identifiable {
  let id = UUID()
  var completed: Bool
}

struct ListView: View {
  let items = [
    Item(completed: true),
    Item(completed: false),
    Item(completed: false),
    Item(completed: false),
    Item(completed: true),
  ]
  
  var body: some View {
    List {
      ForEach(items) { item in
        VStack(alignment: .leading) {
          Text(item.id.uuidString)
        }
        .listRowBackground(item.completed ? Color.green : Color.clear)
      }
    }
  }
}

PlaygroundPage.current.setLiveView(ListView())

The preview looks like this (and please be aware the background of the rows is cleared out): enter image description here

If I comment out .listRowBackground(item.completed ? Color.green : Color.clear), the preview is this:

enter image description here

Even more TL;DR

I can help myself with the workaround of introducing a conditional view modifier:

extension View {
    @ViewBuilder func `if`<Content: View>(_ condition: @autoclosure () -> Bool, transform: (Self) -> Content) -> some View {
        if condition() {
            transform(self)
        } else {
            self
        }
    }
}

I can then apply this with

        .if(item.completed) { view in
          view
            .listRowBackground(Color.green)
        }

This does result in the desired view–but is a bit gnarly. There must be a better way I would hope...

enter image description here


Solution

  • I found the solution–by simply trying out .none for the false branch of the ternary expression:

    .listRowBackground(item.completed ? Color.green : .none)
    

    does exactly what I want. Amazes me that I couldn't find any mention on this neither in the documentation nor anywhere else…