Search code examples
swiftuiequatableswiftui-foreachswiftui-ontapgestureidentifiable

SwiftUI - Comparison of a Binding variable in onTapGesture (to toggle selection of a row in a Grid)


I'm able to get a row item selected using onTapGesture, and deselected if it is tapped again.

The question is why does the following code work, but the code shown at the end did not?

@Binding var selectedRow: RowItem?

var body: some View {

    ForEach(modelData.rows) { row in

        LazyVGrid(columns: columns) {
            Text("text")
            Text("text")
        }
        .onTapGesture {
            if (selectedRow?.id == row.id) {
                selectedRow = nil
            } else {
                selectedRow = row
            }
        }
    }
}

where modelData.rows is [RowItem], and RowItem is: struct RowItem: Identifiable and has an equality function defined:

static func == (lhs: RowItem, rhs: RowItem) -> Bool {
        rhs.id == lhs.id
    }

Why must the id be used, and the following didn't work?

.onTapGesture {
    if (selectedRow? == row) {
        selectedRow = nil
    } else {
        selectedRow = row
    }
}

(Fails with:)

The compiler is unable to type-check this expression in reasonable time; try breaking up the expression into distinct sub-expressions


Solution

  • The error "The compiler is unable to type-check this expression in reasonable time" is something you often see when a parenthesis is missing, or there is a typo, or you are trying to access a property that doesn't exist, or there is some other trivial error that most compilers would be able to spot without difficulty. But not Xcode.

    When I tried your failing case with Xcode 16.1, the error was actually a bit more helpful:

    Value of optional type 'RowItem?' must be unwrapped to a value of type 'RowItem'

    This confusion is because the equality function is unnecessary. So you can begin by deleting the equality function. The error then changes to:

    Operator function '==' requires that 'ContentView.RowItem' conform to 'Equatable'

    This problem is fixed by specifically declaring that RowItem conforms to Equatable:

    struct RowItem: Identifiable, Equatable { ...
    

    The error then changes to:

    '?' must be followed by a call, member lookup, or subscript

    So the final fix is to remove the ?. In Swift, you are allowed to compare an optional with a non-optional, the optional is automatically unwrapped if in fact it is not nil. Btw, you don't need to surround the expression with parentheses in Swift either:

    .onTapGesture {
        if selectedRow == row {
            selectedRow = nil
        } else {
            selectedRow = row
        }
    }