1

I want to create a sortable Table where each table cell is a TextField so that the cell contents can be edited.

Let's start with this unsortable Table that has only one column. In the real code, this is of course going to have more columns and rows can be added and removed too.

struct ContentView: View {
    @State var rows = [
        Row(name: "A"),
        Row(name: "B"),
    ]

    var body: some View {
        Table($rows) {
            TableColumn("Name") { $row in
                TextField("Enter Name", text: $row.name)
            }
        }
    }
}

struct Row: Identifiable, Hashable {
    let id = UUID()
    var name: String
}

To make this sortable, I would need to pass a sortOrder: binding to Table, as well as well as manually sorting the rows array in onChange(of: sortOrder) { ... }.

However, this doesn't work:

@State var sortOrder: [KeyPathComparator<Binding<Row>>] = []

var body: some View {
    Table($rows, sortOrder: $sortOrder) {
        TableColumn("Name", value: \.wrappedValue.name) { $row in
            TextField("Enter Name", text: $row.name)
        }
    }
    .onChange(of: sortOrder) { _, sortOrder in
        rows.sort(using: sortOrder) // error
    }
}

Instance method sort(using:) requires the types Row and Binding<Row> be equivalent

I understand that this is because I am trying to sort an array of Rows using sort comparators that compare Binding<Row>s.

However, the Table initialiser requires that the type of sortOrder: to be

Binding<[KeyPathComparator<Binding<Row>>]>

because this is a table of Binding<Row>s.

How can I sort the rows in onChange? Perhaps there is a way to convert [KeyPathComparator<Binding<Row>>] to [KeyPathComparator<Row>]?


Just in case this is relevant, I only want the table to be re-sorted when sortOrder changes. I do not want the table to be re-sorted when some row's name is edited.

2 Answers 2

0

You could try this approach using a dedicated Binding in the TextField, rather than having a $rows in the Table.

Example code:


struct ContentView: View {
    @State private var rows = [Row(name: "A"), Row(name: "B")]
    @State private var sortOrder = [KeyPathComparator(\Row.name)] // <-- here
    
    var body: some View {
        Table(rows, sortOrder: $sortOrder) {  // <-- here
            TableColumn("Name", value: \Row.name) { row in
                TextField("Enter Name", text: Binding(  // <-- here
                    get: { row.name },
                    set: {
                        if let index = rows.firstIndex(of: row) {
                            rows[index].name = $0  // <-- here
                        }
                    }))
            }
        }
        .onChange(of: sortOrder) {
            rows.sort(using: sortOrder)
        }
    }
}

struct Row: Identifiable, Hashable {
    let id = UUID()
    var name: String
}
Sign up to request clarification or add additional context in comments.

Comments

0

Since the Binding is only used for sorting, one can map the rows to .constant bindings and let those be sorted, then map them back.

.onChange(of: sortOrder) { _, sortOrder in
    rows = rows.map { Binding.constant($0) }
        .sorted(using: sortOrder)
        .map(\.wrappedValue)
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.