listmacosswiftuicolorsbackground-color

SwiftUI: Change Color based on List selection background


If you choose the color primary as foreground color for text or symbol images in SwiftUI list rows that can be selected, the color will change if the row is selected, which will greatly improve visibility. I want the same behavior for any other color as well.

Here's an example list:

struct Item: Identifiable, Hashable {
    var id = UUID()
    var title: String
}

struct ContentView: View {
    @State var listContent = [Item(title: "A"), Item(title: "B"), Item(title: "C")]
    @State var selection: Item?

    var body: some View {
        List(selection: $selection, content: {
            ForEach(listContent, id: \.self) { item in
                HStack {
                    Text(item.title)
                        .foregroundStyle(.primary) //Becomes white if selected 
                    Text(item.title)
                        .foregroundStyle(.red) //Stays red if selected
                    Text(item.title)
                        .foregroundStyle(selection == item ? .white : .red) //Bad experience on macOS
                }
            }
        })
    }
}

The result on macOS is this:

first list example

As you can see I can achieve the wanted behavior by checking if the row belongs to the selected item.

However the row background color changes on mouse down but the row itself is being re-rendered at mouse up. So if I click and hold on a list, I get this result:

second list example

The old selection still has the white text as if it is still being selected and the real selection is rendered as if it isn't selected. As soon as I release the mouse, everything is being rendered as expected. The time between mouse down and up is too long and can make the user interface feel slow and unresponsive.

Is there any other way to change the color based on the background view? I've experimented with different blend modes but had no success.


Solution

  • After searching for answers I found that Label elements have a tint applied to them when their enclosing list item is selected. You can change the default color (the unselected color) using the listItemTint modifier like so:

        var body: some View {
            List(selection: $selection, content: {
                ForEach(listContent, id: \.self) { item in
                    HStack {
                        Label { Text(item.title) } icon: { EmptyView() }
                            .listItemTint(Color.red) // Will change to white when selected
                    }
                    .tag(item) // Also make sure SwiftUI knows which list item is being selected
                }
            })
        }