26

The default colour of a list row when tapped is grey.

I know how to change background colour using .listRowBackground, but then it changes to the whole list.

How can I change to a custom colour when tapped, so ONLY the tapped row stays red?

import SwiftUI

struct ExampleView: View {
    @State private var fruits = ["Apple", "Banana", "Grapes", "Peach"]

    var body: some View {
        NavigationView {
            List {
                ForEach(fruits, id: \.self) { fruit in
                    Text(fruit)
                }
                .listRowBackground(Color.red)

            }


        }
    }


}

struct ExampleView_Previews: PreviewProvider {
    static var previews: some View {
        ExampleView()
    }
}

enter image description here

Mane Manero
  • 3,086
  • 5
  • 25
  • 47
  • 2
    Consider approaches provided in [SwiftUI List with NavigationLink how to make custom highlight on tap](https://stackoverflow.com/questions/59089400/swiftui-list-with-navigationlink-how-to-make-custom-highlight-on-tap) – Asperi Dec 02 '19 at 16:20
  • Does this answer your question? [SwiftUI List with NavigationLink how to make custom highlight on tap](https://stackoverflow.com/questions/59089400/swiftui-list-with-navigationlink-how-to-make-custom-highlight-on-tap) – gotnull Dec 03 '19 at 03:48

8 Answers8

32

First, you want the .listRowBackground modifier on each row, not the whole list. You can then use it to conditionally set the background color on each row.

If you save the tapped row's ID in a @State var, you can set the row to red or the default color based on selection state. Here's the code:

import SwiftUI

struct ContentView: View {
    @State private var fruits = ["Apple", "Banana", "Grapes", "Peach"]
    @State private var selectedFruit: String?

    var body: some View {
        NavigationView {
            List(selection: $selectedFruit) {
                ForEach(fruits, id: \.self) { fruit in
                    Text(fruit)
                        .tag(fruit)
                        .listRowBackground(fruit == selectedFruit ? Color.red : nil)
                }
            }
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}
Daniel Saidi
  • 6,079
  • 4
  • 27
  • 29
Chuck H
  • 7,434
  • 4
  • 31
  • 34
  • Can this be made to work with non-ForEach-based lists? Such as ones using the built-in selection-management feature? `List(values, selection: $selection) { val in`? – MikeyWard Jun 06 '21 at 18:49
  • I would think it should work and is likely to be a better overall solution. This particular solution is only trying to directly answer the original question, not to suggest a best practice. Give it a shot and if you have trouble getting what you want, ask a new question and post a comment with a link to it. – Chuck H Jun 10 '21 at 00:27
  • How to make it for a `LazyVStack` ? – piotr_ch Jul 02 '21 at 22:05
  • 3
    doesn't work when the cell is in a navigationLink – Peter Lapisu Jun 08 '22 at 10:21
  • it doesn't work if the cell is in a navigationLink... – Gianni Jul 19 '22 at 09:54
11

Following on @Chuck H,

I'm using:

        List {
            ForEach(viewModel.sharingGroups, id: \.self) { group in
                Button(action: {
                    self.selectedGroup = group
                }, label: {
                    Text(group.name)
                })
                .listRowBackground(self.selectedGroup == group ? Color.gray : Color(UIColor.systemGroupedBackground))
            }
        }

If you use a Button instead if Text directly, this enables me to tap anywhere on the row, not just on the text.

Chris Prince
  • 7,288
  • 2
  • 48
  • 66
3

Here is improved elegant solution without buttons, whole row is tappable. Works on iOS13 too.

@ObservedObject var viewModel: TypesListViewModel
@State private var selectedType: Type?   

 List{
       ForEach(viewModel.types, id: \.id){ type in
            TypeRowView(type: type)
                 .contentShape(Rectangle()) //makes whole row tappable
                 .onTapGesture {
                      selectedType = type
                     }
                 .listRowBackground(selectedType == type ? Color(.systemFill) : Color(.systemBackground))
           }//ForEach
       }//List
Lukasz D
  • 241
  • 4
  • 8
2

If you are seeking an alternative for styling purposes on iOS and don't want to introduce state management, since Lists are backed by a UITableView you can use

UITableViewCell.appearance().selectedBackgroundView = UIView()

to remove the selection color entirely. OR:

UITableViewCell.appearance().selectedBackgroundView = {
            let view = UIView()
            view.backgroundColor = .blue
            return view
        }()

to set a specific highlight color

Be warned that this applies to the whole application, and only on platforms that support the UITableView, so MacOS or WatchOS would need another solution.

bitwit
  • 2,589
  • 2
  • 28
  • 33
0

Apple has silly things breaking in SwiftUI. After banging my head against wall, finally i got some success. Just add a Zstack and Empty Button.

var body: some View {
    List {
        ForEach(data, id: \.self) { item in
            ZStack {
                Button("") {}
                NavigationLink(destination: ItemView(item: item)) {
                    ItemRow(item: item)
                }
            }
        }
    }
}
Amrit
  • 301
  • 2
  • 14
0

Not perfect but it works, please leave a comment if you have an improvement:

struct CustomNavigationLink<ContentView: View, DestinationView: View>: View {

    let content: () -> ContentView
    let destinationView: () -> DestinationView
    @State private var showDetail = false
    @State private var isTouchDown = false

    var body: some View {
        ZStack(alignment: .leading) {
            Color(.red)
                .opacity(isTouchDown ? 0.3 : 0)
            HStack {
                content()
                Spacer()
                NavigationLink(destination: destinationView(),
                               isActive: self.$showDetail) { EmptyView() }
                               .hidden()
                               .frame(width: 0)
                Image(systemName: "chevron.right")
                    .foregroundColor(.red)
            }
            .contentShape(Rectangle())
            .onTapGesture {
                showDetail = true
            }
            ._onButtonGesture { isTouchDown in
                withAnimation {
                    self.isTouchDown = isTouchDown
                }
            } perform: {}
        }
    }
}
Nico S.
  • 3,056
  • 1
  • 30
  • 64
0

I appreciate all answers but non of them worked for me, so I wanted to share my solution.

The heart of the functionality is rely on .onAppear of the centre view (in iPad solid view mode) after cell is selected, detail view displays and fire the 2nd action. Here second action that I need was to set the selectedIndex for keeping the selected cell in the selected colour.

I put a simplified version of my code. I hope it helps for specific cases.

@State private var selectedIndex: Int?
var body: some View {
    List{
        ForEach( vm.qCategories.indices, id: \.self ){ index in
            NavigationLink{
                SplitCentreView()
                .onAppear{
                    selectedIndex = index
                    print("selectedIndex: \(String(describing: selectedIndex))")
                }
            } label:{
                ItemRowView(index: index)
            }
            // Selected cell colour
            .listRowBackground(selectedIndex == index ? Color(.systemFill) : Color(.secondarySystemGroupedBackground))
            .buttonStyle(PlainButtonStyle())
            
            
        } // foreach
    } // list
}
Trevor
  • 1,051
  • 12
  • 16
0

In SwiftUI, you can change the List row highlight color when tapped by using the .tint() modifier applied to the List. This will modify both the tint color of the List and the selection color. Here's an example:

struct ExampleView: View {
    @State private var fruits = ["Apple", "Banana", "Grapes", "Peach"]

    var body: some View {
        NavigationView {
            List {
                ForEach(fruits, id: \.self) { fruit in
                    Text(fruit)
                }
                .tint(Color.red) // Change this color to your desired row highlight color
            }

        }
    }
}

You can replace Color.red with any other color you'd like to use for the row highlight.

Remember that the .tint() modifier is available only in iOS 15 and later. If you need to support earlier versions of iOS, you might need to consider alternative solutions, such as custom row views and managing the selection state manually.