How to build sort direction in menu like Files app?

Viewed 46

I'm displaying sort options in my Menu, but I'd also like the user to control the sort direction in the way the Files app works: tapping a second time toggles the sort direction.

enter image description here

Here's what I have:

@State private var selectedSort: SortOption = .name
@State private var isSortAscending = true

enum SortOption {
    case name
    case number
    case length
}

Menu {
    Picker(selection: $selectedSort, label: Text("Sorting options")) {
        Button {
            isSortAscending.toggle()
        } label: {
            HStack {
                Text("Name")
                Spacer()
                Image(systemName: isSortAscending ? "chevron.down" : "chevron.up")
            }
        }
        .tag(SortOption.name)
        Button {
            isSortAscending.toggle()
        } label: {
            HStack {
                Text("Number")
                Spacer()
                Image(systemName: isSortAscending ? "chevron.down" : "chevron.up")
            }
        }
        .tag(SortOption.number)
        Button {
            isSortAscending.toggle()
        } label: {
            HStack {
                Text("Length")
                Spacer()
                Image(systemName: isSortAscending ? "chevron.down" : "chevron.up")
            }
        }
        .tag(SortOption.length)
    }
}

Tapping doesn't toggle the sort state at all. Is there a better or more supported way to do this?

1 Answers

Item selected is handled internally, so we need selection side-effect. It is possible to do by injecting will-set side effect in computable binding.

Here is a possible approach tested with Xcode 13.4 / iOS 15.5

demo

Main part:

var sorting: Binding<SortOption> { .init(
    get: { self.selectedSort },
    set: {
        if self.selectedSort == $0 {
            self.isSortAscending.toggle()
        }
        self.selectedSort = $0
    }
)}

var body: some View {
    Menu("Sort") {
        Picker(selection: sorting, label: Text("Sorting options")) {
            ForEach(SortOption.allCases) { option in
                HStack {
                    Text(option.rawValue)
                    Spacer()
                    if selectedSort == option {
                        Image(systemName: isSortAscending ? "chevron.down" : "chevron.up")
                    }

Test module on GitHub

Related