62

How to remove highlight on tap of List with SwiftUI?

List {

}.whatModifierToAddHere?

The selection manager documentation doesnt say anything about it.

Just a coder
  • 15,480
  • 16
  • 85
  • 138
  • Any luck finding a path forward? I am also stuck on this! – Charlie Jul 08 '19 at 14:08
  • 3
    I ended up using a ScrollView instead of a List. A scrollView is like a barebones list. You have to make all the extra stuff your self, which is good for me. – Just a coder Jul 08 '19 at 15:31
  • 2
    @Justacoder I encounter the same problem with a Scrollview too (iOS 14). The `.buttonStyle(PlainButtonStyle())` "solution" mentionned everywhere in this thread does not work. It just reduce the highlight effect, either during scroll or normal button clic. – Martin May 17 '21 at 08:38
  • 1
    I just spent about two days, first deducing that this was the problem and then trying various methods here and elsewhere to try to work around it. I'm on Xcode 12.5.1. Most had no effect at all, though maybe I was applying them wrong. What worked for me was putting a onTapGesture() that did nothing on the row view. Perhaps this intercept whatever was causing the row to become selected. Works for me now since I didn't want any accidental selection, but I may not be out of the woods yet – Bruce Cichowlas Jun 29 '21 at 12:51

12 Answers12

74

I know I'm a bit late, but it's for those of you who are searching (like me )

What I found

I guess you should take a look at the short article How to disable the overlay color for images inside Button and NavigationLink from @TwoStraws

Just add the .buttonStyle(PlainButtonStyle()) modifier to your item in the List and you'll have what you wanted. It also makes the Buttons work again in the List, which is another problem I encountered.

A working example for Swift 5.1 :

import Combine
import SwiftUI

struct YourItem: Identifiable {
    let id = UUID()
    let text: String
}

class YourDataSource: ObservableObject {
    let willChange = PassthroughSubject<Void, Never>()
    var items = [YourItem]()

    init() {
        items = [
            YourItem(text: "Some text"),
            YourItem(text: "Some other text")
        ]
    }
}

struct YourItemView: View {
    var item: YourItem

    var body: some View {
        VStack(alignment: .leading) {
            Text(item.text)
            HStack {
                Button(action: {
                    print("Like")
                }) {
                    Image(systemName: "heart.fill")
                }
                Button(action: {
                    print("Star")
                }) {
                    Image(systemName: "star.fill")
                }
            }
        }
        .buttonStyle(PlainButtonStyle())
    }
}

struct YourListView: View {
    @ObservedObject var dataSource = YourDataSource()

    var body: some View {
        List(dataSource.items) { item in
            YourItemView(item: item)
        }
        .navigationBarTitle("List example", displayMode: .inline)
        .edgesIgnoringSafeArea(.bottom)
    }
}

#if DEBUG
struct YourListView_Previews: PreviewProvider {
    static var previews: some View {
        YourListView()
    }
}
#endif

As said in the article, it also works with NavigationLinks. I hope it helped some of you

Rémi B.
  • 2,410
  • 11
  • 17
  • 11
    this solution didn't work on XCode 11.3.1, the selection still appears when tapping on the list item, the item i use is a ZStack – JAHelia Feb 25 '20 at 09:31
  • Are you sure you applied the modifier directly to the list item (and not a subview)? If so, I’ll try on my side and tell you if I have the same problem – Rémi B. Feb 26 '20 at 11:56
  • Tried both ways but same problem happens, giving that my list inside a navigation view – JAHelia Feb 26 '20 at 12:11
  • The selection still appears on the initial tap, but with this fix it is unselected on return (i.e. 'Back') from the NavigationLink, which is what I mainly wanted to fix. – Snips Nov 12 '21 at 11:05
45

Simple answer to you question. Any cell that you don't want to highlight when tapped just add this modifier

.buttonStyle(PlainButtonStyle())

Therefore the modifier is not for the whole List is for each cell inside

var body: some View{
    List{
        ForEach(self.getElementsForList()){ element in
            ElementCell(element: element)
                .buttonStyle(PlainButtonStyle())
        }
    }
}
Julio Bailon
  • 3,735
  • 2
  • 33
  • 34
24

.listRowBackground worked for me

List {
   ListItem()
       .listRowBackground(Color.clear)
}
Nico Cobelo
  • 557
  • 7
  • 18
  • 3
    This is the right answer, just by using `listRowBackground` the selected gray background disappears. – Lluis Gerard May 18 '22 at 09:35
  • 2
    This is the only answer that worked for me on iOS 16.1. I tried all the other solutions, nothing worked, thank you! Note I'm doing iOS/macOS so my actual solution was the following: ``` #if os(iOS) .listRowBackground(Color.white) #elseif os(macOS) .listRowBackground(Color.clear) #endif ``` – Daniel Walton Dec 15 '22 at 13:12
  • 1
    This's the right answer os of today (iOS16 + iOS15 compatibility) – valvoline May 17 '23 at 10:59
16

I know I'm a bit late but hope this will solve your problem. You need to use UIKit modifiers to remove this. I recommend you to place them on SceneDelegate.swift.

class SceneDelegate: UIResponder, UIWindowSceneDelegate {

    var window: UIWindow?


    func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
        // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`.
        // If using a storyboard, the `window` property will automatically be initialized and attached to the scene.
        // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead).

        // Create the SwiftUI view that provides the window contents.
        let contentView = TabController()

        // Use a UIHostingController as window root view controller.
        if let windowScene = scene as? UIWindowScene {

            //MARK: Disable selection.
            UITableView.appearance().allowsSelection = false
            UITableViewCell.appearance().selectionStyle = .none

            let window = UIWindow(windowScene: windowScene)
            window.rootViewController = UIHostingController(rootView: contentView)
            self.window = window
            window.makeKeyAndVisible()

        }
    }
}

EDIT: This will disable all table view selections in your app. If instead, you want to disable the selection on a specific table view, you can disable it inside init().

struct SomeViewWithTableView: View {

    init() {
        //MARK: Disable selection.
        UITableView.appearance().allowsSelection = false
        UITableViewCell.appearance().selectionStyle = .none
    }

    var body: some View {
        //your view code here
    }
}
amodrono
  • 1,900
  • 4
  • 24
  • 45
  • 1
    I can confirm this works, I didn't even know that SwiftUI's List is just a UITableView wrapper! – malhal Nov 26 '19 at 17:17
  • 1
    U can disable selection on a view basis, in the view itself. Like so: init() { UITableView.appearance().allowsSelection = false UITableViewCell.appearance().selectionStyle = .none } – Ranic Dec 15 '19 at 21:42
  • 1
    Is there a version of this that works for ScrollViews? They don't seem to respond to TableView settings. – eResourcesInc Dec 20 '19 at 15:33
  • `UITableViewCell.appearance().selectionStyle = .none` alone was enough for me. – Cinn Jul 05 '20 at 09:20
5

This needs different approaches depending on the iOS.

For iOS 14:

.onAppear {
            UITableViewCell.appearance().selectionStyle = .none
    }

For iOS 15:

List {
   ListItem()
       .listRowBackground(Color.clear)
}
Vkharb
  • 856
  • 11
  • 19
2

Just adding the .buttonStyle(PlainButtonStyle()) modifier to the item in the List, unfortunately didn't work for my case. Instead I was able to obtain the desired effect -- not seeing any highlighting effect when tapping on a row -- by using the .onAppear() modifier on the List and the Appearance API for UITableViewCell.

As in the following example:

List {
    ForEach(items) { item in
        NavigationLink(destination: DetailView(item)) {
            RowView(item)
        }
    }
}
.onAppear {

    // this will disable highlighting the cell when is selected
    UITableViewCell.appearance().selectionStyle = .none

    // you can also remove the row separators
    UITableView.appearance().separatorStyle = .none

}
Aleph_0
  • 55
  • 3
0

Edit:

The following only works if the SwiftUI view is embedded in a UIKit parent.



For anyone still wondering, you can use .onAppear modifier to achieve this.

.onAppear {
            UITableViewCell.appearance().selectionStyle = .none
    }

This is similar to viewWillAppear in UIKit. Source: HackingWithSwift

user3752049
  • 167
  • 1
  • 14
  • Just checked in a fresh project and you're right, it doesn't work. So I checked the original project I used it in, turns out it works in a different context. I have a SwiftUI view embedded in a Scrollview as I needed a pull to refresh functionality. And that is probably why it works for me as in this case it uses UIKit apis. Couldn't make it work with a pure SwiftUI approach. – user3752049 Oct 22 '20 at 11:42
0

A possible solution is to use a ZStack. And adjust insets to .zero. Change Color.red to your color. Your List:

ForEach(items) { item in
    ZStack {
        NavigationLink(destination: DetailView()) {
            EmptyView()
        }
        ItemRowWrap(item: item)
            .background(Color.red)
    }
    
}
.listStyle(InsetListStyle())
.listRowInsets(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0))

Your ItemRowView:

struct ItemRowWrap: View {
    
    let item: ListItem
    
    var body: some View {
        ItemRow(item: item)
            .padding(EdgeInsets(top: 5, leading: 8, bottom: 5, trailing: 8))
    }
    
}

You can adjust paddings as you need.

mazy
  • 652
  • 1
  • 10
  • 18
0

Setting the global AccentColor in Assets.xcassets as descirbed in

enter image description here

小弟调调
  • 1,315
  • 1
  • 17
  • 33
0

I met the same question, and I think it's a bug.

If I put a Text top the List, the navigation link item will be highlight;

If I remove it or put it below List, the navigation link item won't be highlight.

VStack {
    Text("Some word") // Remove it or put it below List
    List {
        NavigationLink(destination: RedirectionFromList(data: data.first!)) {
            Text("test")
        }
    }
    
}

If put the Text top the List, I can't find a way to dismiss highlight, very annoyed.

pfcstyle
  • 97
  • 5
0
.onDisappear {
    UITableViewCell.appearance().isSelected = false
}

You can set selected to false when the interface disappears or appears.

hupo
  • 21
  • 3
0

If I understand the question right, you want to select an item by clicking/tapping on it, and deselecting it when you click again.

struct SimpleSelectionView: View {
    @State var items = ["First", "Second", "Third"]
    @State var selectedItem: String?
    
    var body: some View{
        List{
            ForEach(items, id: \.self) { item in
                Text(item)
            .onTapGesture {
                if selectedItem == nil || selectedItem != item {
                selectedItem = item
                } else {
                    selectedItem = nil
                        }
                    }
                  .listRowBackground(self.selectedItem == item ? Color(red: 0.275, green: 0.420, blue: 0.153, opacity: 0.3) : .white)
                }
            }
    }
}

The downside is that on macOS, you have to click the content (Text) rather than the row as such, but it's a nice lightweight solution, and if you want to do something with the selection, you can do it in didSet.

green_knight
  • 1,319
  • 14
  • 26