swiftuiswiftui-table

How to sort children items in Table?


Sorting of items in a hierarchical table does not work!

Model

struct File: Decodable {
    /*File name (including relative path)*/
    let name: String
    /*File size (bytes)*/
    let size: Int64    
    var children: [File]?
}

extension File: Identifiable {
    var id: String { name } // unic for node
}

Table

struct TableView: View {
    
    @Environment(AppContext.self) private var appContext
    @State private var fileTree: [File] = []
    @State private var sortOrder = [KeyPathComparator(\File.name)]
    @State private var selectedFile: Set<String> = Set()
    
    var body: some View {
        Table(fileTree, children: \.children, selection: $selectedFile, sortOrder: $sortOrder) {
            TableColumn("Name", value: \.name) { file in
                ItemName(for: file)
                    .padding(.leading, 5)
            }
            .width(ideal: 300)
            TableColumn("Size", value: \.size) {
                Text($0.size, format: .byteCount(style: .memory, spellsOutZero: false))
                    .frame(maxWidth: .infinity, alignment: .trailing)
            }
            .width(ideal: 50)
        }
        .onChange(of: sortOrder) { _, newValue in
            fileTree.sort(using: newValue)
        }
    }
}

However, sorting only works for the root elements. Is there something I'm missing?


Solution

  • As you may already know, Table doesn't do any sorting for you. The sorting should be done by you, in onChange(of: sortOrder). You are doing fileTree.sort(using: newValue), which of course sorts the root elements.

    You need to write your own sorting logic that also sorts the files' children. If you want to sort each children array individually and recursively, you can write an extension like so:

    extension MutableCollection where Self: RandomAccessCollection {
        mutating func recursiveSort<S, Comparator>(
            using comparators: S,
            children: WritableKeyPath<Element, Self?>
        ) where
            S : Sequence,
            Comparator : SortComparator,
            Comparator == S.Element,
            Element == Comparator.Compared
        {
            sort(using: comparators)
            for i in indices {
                self[i][keyPath: children]?.recursiveSort(using: comparators, children: children)
            }
        }
    }
    

    Usage:

    .onChange(of: sortOrder) { _, newValue in
        fileTree.recursiveSort(using: newValue, children: \.children)
    }