In a List row on macOS, changing Image color when row is selected

When using an image in a List item, you sometimes want to tint that image, but only if the item isn’t selected. When it’s selected, you usually want the contents of the list item to be all-white, for contrast.

The backgroundProminence Environment value ostensibly exists for this purpose, but in my tests, it never seems to change. Am I doing something wrong? Is there an alternative solution?

For instance, this code:

import SwiftUI

struct ProminentBackgroundInList: View {
	var body: some View {
		List(selection: .constant(0)) {
			ListItem().tag(0)
			ListItem().tag(1)
		}
	}
}

struct ListItem: View {
	@Environment(\.backgroundProminence) var backgroundProminence
	
	var body: some View {
		HStack {
			Image(systemName: "person.fill")
				.foregroundStyle(backgroundProminence == .standard ? .orange : .primary)
			
			Text("Person")
		}
	}
}

#Preview {
	ProminentBackgroundInList()
}

Produces this result:

Answered by DTS Engineer in 875006022

Hello Cykelero,

Thanks for your question. Please see the following section in the entry for backgroundProminence:

Note that the use of backgroundProminence was used by a view that was nested in additional stack containers within the row. This ensured that the value correctly reflected the environment within the list row itself, as opposed to the environment of the list as a whole. One way to ensure correct resolution would be to prefer using this in a custom ShapeStyle instead, for example:

and

Views like List and Table as well as standard shape styles like ShapeStyle.selection will automatically update the background prominence of foreground views.

By modifying your code to use a custom ShapeStyle, I was able to colorize the Image accordingly:

struct ListItem: View {    
    var body: some View {
        HStack {
            Image(systemName: "person.fill")
                .foregroundStyle(FillStyle())
            
            Text("Person")
        }
    }
}
extension ListItem {
    struct FillStyle: ShapeStyle {
        func resolve(in env: EnvironmentValues) -> some ShapeStyle {
            switch env.backgroundProminence {
            case .standard: return AnyShapeStyle(.orange)
            default: return AnyShapeStyle(.primary)
            }
        }
    }
}

Thanks as always,

Richard Yeh  Developer Technical Support

Accepted Answer

Hello Cykelero,

Thanks for your question. Please see the following section in the entry for backgroundProminence:

Note that the use of backgroundProminence was used by a view that was nested in additional stack containers within the row. This ensured that the value correctly reflected the environment within the list row itself, as opposed to the environment of the list as a whole. One way to ensure correct resolution would be to prefer using this in a custom ShapeStyle instead, for example:

and

Views like List and Table as well as standard shape styles like ShapeStyle.selection will automatically update the background prominence of foreground views.

By modifying your code to use a custom ShapeStyle, I was able to colorize the Image accordingly:

struct ListItem: View {    
    var body: some View {
        HStack {
            Image(systemName: "person.fill")
                .foregroundStyle(FillStyle())
            
            Text("Person")
        }
    }
}
extension ListItem {
    struct FillStyle: ShapeStyle {
        func resolve(in env: EnvironmentValues) -> some ShapeStyle {
            switch env.backgroundProminence {
            case .standard: return AnyShapeStyle(.orange)
            default: return AnyShapeStyle(.primary)
            }
        }
    }
}

Thanks as always,

Richard Yeh  Developer Technical Support

Thank you for your reply! I understand now, and was able to fix our code.

I’d read the documentation page too quickly and misunderstood what it was saying exactly. It's pretty interesting that you need to read the environment not from within the row view, but from a descendant of it.

For anyone else reading this, here’s some additional tests that helped me understand exactly the required setup. The third row especially (ListItemThinWrapper) seems to suggest you should be thinking in terms of resolved views, or something like that.

struct ProminentBackgroundInList: View {
	var body: some View {
		List(selection: .constant(Set([0, 1, 2, 3]))) {
			ListItem().tag(0) // doesn't work
			
			ZStack {
				ListItem() // works
			}.tag(1)
			
			ListItemThinWrapper().tag(2) // doesn't work
			
			ListItemStackWrapper().tag(3) // works
		}
	}
	
	struct ListItem: View {
		@Environment(\.backgroundProminence) var backgroundProminence
		
		var body: some View {
			HStack {
				Image(systemName: "person.fill")
					.foregroundStyle(backgroundProminence == .standard ? .orange : .primary)
				
				Text("Person")
			}
		}
	}
	
	struct ListItemThinWrapper: View {
		var body: some View {
			ListItem()
		}
	}
	
	struct ListItemStackWrapper: View {
		var body: some View {
			ZStack {
				ListItem()
			}
		}
	}
}

#Preview {
	ProminentBackgroundInList()
}

In a List row on macOS, changing Image color when row is selected
 
 
Q