SwiftUI List with NavigationLink how to make custom highlight on tap
Asked Answered
S

3

5

How I can make custom overlay over List row that will highlight it on tap. I am using NaviagationLink and I've changed

UITableViewCell.appearance().cellSelectionStyle = .none 

in order to not use default gray selection.

I've tried to add some @State isSelected to my Cell but I do not know how/when to change this to get this affect. I've tried to add onTapGesture() but it prevents NavigationLink, and doesn't provide begin, ended states, just ended.

Sclaff answered 28/11, 2019 at 12:54 Comment(0)
S
9

I do it this way:

List {
    ForEach(0..<self.contacts.count) { i in
       ZStack {
           NavigationLink(destination: ContactDetails(contact: self.contacts[i])) {
                EmptyView()
           }.opacity(0)

           Button(action: {}) {
                   ContactRow(contact: self.contacts[i])
           }.buttonStyle(ListButtonStyle())
        }.listRowBackground( (i%2 == 0) ? Color("DarkRowBackground") : .white)
           .listRowInsets(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0))
     }
}

And ButtonStyle:

struct ListButtonStyle: ButtonStyle {

    func makeBody(configuration: Self.Configuration) -> some View {

        configuration.label
            .overlay(configuration.isPressed ? Color("Dim").opacity(0.4) : Color.clear)
    }
}
Sclaff answered 29/11, 2019 at 9:55 Comment(4)
I tried this and it's disabling the NavigationLink from going to the details. Without the Button it worksExpostulation
You can add buttonStyle directly on NavigationLinkLeucoma
Use .background instead because if you use .overlay then the color is added and blended with the title.Primacy
@BinChen This should be accepted answer. Easy and clean. 👍Yellowtail
I
3

Ok, here is very simple demo of the approach. The idea is to keep NavigationLink out of List and activate it manually on row tap (which becomes easily tappable w/o navigation link inside)... everything else, like animations, effects, and kind of highlight is up to you.

import SwiftUI
import UIKit

struct TestCustomCellHighlight: View {
    @State var selection: Int = -1
    @State var highlight = false
    @State var showDetails = false

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

    var body: some View {
        NavigationView {
            VStack {
                NavigationLink(destination: Text("Details \(self.selection)"), isActive: $showDetails) {
                    EmptyView()
                }
                List(0..<20, id: \.self) { i in
                    HStack {
                        Text("Item \(i)")
                        Spacer()
                    }
                    .padding(.vertical, 6)
                    .background(Color.white) // to be tappable row-wide
                    .overlay(self.highlightView(for: i))
                    .onTapGesture {
                        self.selection = i
                        self.highlight = true

                        // delay link activation to see selection effect
                        DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
                            self.highlight = false
                            self.showDetails = true
                        }
                    }
                }
            }
        }
    }

    private func highlightView(for index: Int) -> AnyView {
        if self.highlight && self.selection == index {
            return AnyView(Rectangle().inset(by: -5).fill(Color.red.opacity(0.5)))
        } else {
            return AnyView(EmptyView())
        }
    }
}

struct TestCustomCellHighlight_Previews: PreviewProvider {
    static var previews: some View {
        TestCustomCellHighlight()
    }
}
Incubate answered 28/11, 2019 at 17:26 Comment(1)
not bad but now I considering implementing this approach https://mcmap.net/q/131056/-how-to-set-custom-highlighted-state-of-swiftui-buttonHypostatize
K
0

For me this slightly changed version of @Michał Ziobro answer works best:

struct DemoView: View {
    let listNames = ["Navigation1", "Navigation2", "Navigation3"]
    var body: some View {
        NavigationView {
            List {
                ForEach(listNames, id: \.self) { listName in
                    ZStack {
                        // NavigationLink tint is applied to invisible EmptyView
                        NavigationLink(destination: Text(listName)) {
                            EmptyView()
                        }.opacity(0)
                        // Button handles tint selection
                        Button(action: {}) {
                            Text(listName) // displayed list item (cell)
                        }
                    }
                }
            }
        }
    }
}
Keare answered 22/3, 2023 at 12:27 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.