lukecsmith.co.uk

Replicating UITableView cell selection with a SwiftUI List

Luke, 25 May 2020

SwiftUI Lists are fantastically convenient and quick for displaying what is commonly known in the UIKit world as a UITableView. But they don’t come with some previous UITableView functionality out of the box, namely the ability to quickly select a cell, and use the data that the cell represents – or to colour the cell when selecting either. Because it’s not just given out of the box, theres a few different ways of tackling the problem. Heres my solution, which also gets around the fact that selecting the cell isn’t as simple as you might hope.

The example below uses a simple data struct called Claim, which has an id property (for equatable and so it works with the List) and a description property which is the string that the cell displays. There is an @State property in the Content view below which will be populated with a Claim instance when we select the cell representing that instance.

struct Claim: Equatable {
    var id: Int
    var description: String
}

struct ContentView: View {
    
    @State var selectedClaim: Claim?
    
    var claims = [Claim(id: 0, description: "Value 1"),
                  Claim(id: 1, description: "Value 2"),
                  Claim(id: 2, description: "Value 3")]
    
    var body: some View {
        VStack {
            Spacer()
            List(claims, id: \.id) { claim in
                ClaimCell(claim: claim, selectedClaim: self.$selectedClaim)
            }
                .padding()
                .frame(maxHeight:180)
                .background(Color.gray)
                .cornerRadius(8)
            Text("Use : \(self.selectedClaim?.description ?? "none")").onTapGesture {
                print("Do something (eg go to detail view) with \(self.selectedClaim?.description ?? "none")")
            }.padding()
            Spacer()
        }
    }
}

This solution works by passing that selectedClaim property into the cell view as an @Binding property so that the cell itself can set its own claim as the selected one. Heres the code for the cell view :

struct ClaimCell: View {
    
    var claim: Claim
    @Binding var selectedClaim: Claim?
    
    var body: some View {
        ZStack {
            claim == selectedClaim ? Color.green : Color.white
            Text(claim.description)
                .padding()
        }.onTapGesture {
            self.selectedClaim = self.claim
        }
    }
}

So heres how the cell selection works : the .onTapGesture above sets the selectedClaim property (which has been passed in from the parent List view via @Binding) to be the local claim property. In the main ZStack a background colour to the cell is set – this checks whether or not the local claim property is equal to selectedClaim, and sets the colour accordingly. Thats all the important functionality – the cell ‘selects’, and the selectedClaim property (on the parent View) has been set.

Heres another important detail though : Ive put a ZStack on this cell view and put the background colour and the text within it. The .onTapGesture command is attached to this ZStack, which means we can tap anywhere on the cell, not just the text – to select it. This feels much more like a UITableViewCell selection.

So back on the parent Content view, we can simply use whatever is within the selectedClaim property. Its optional, so if nothing is selected it will be nil. An example of how to use that selected item is also there in the Text view at the bottom. Any thoughts welcome on this solution and how to improve it.

Tagged with: