skip to Main Content

I want to achieve something like in the attached screenshot. I am talking about the circled chevron displaying the forward/reverse order on the selected sorting rule.

screenshot

When selecting the same sort type, I want to actually use the same selected type but to reverse the order from forward to reverse or other way around.

This the menu I have right now but the button action does not execute at all.
How can I achieve this?

Menu {
    Picker("Filtering", selection: viewModel.filterBinding) {
        ForEach(FilterType.allCases) { filterType in
            Text(filterType.rawValue)
                .tag(filterType)
        }
    }
    
    Divider()
    
    Picker("Sorting", selection: viewModel.sortingBinding) {
        ForEach(SortType.allCases) { sortType in
            Button {
                if viewModel.sortType == sortType {
                    viewModel.toggleSortOrder()
                }
            } label: {
                HStack {
                    Text(sortType.rawValue)
                    Image(systemName: viewModel.sortOrder == .forward ? "chevron.up" : "chevron.down")
                        .opacity(viewModel.sortType == sortType ? 1 : 0)
                }
            }
            .tag(sortType)
        }
    }
} label: {
    Image(systemName: "line.3.horizontal.decrease.circle.fill")
}

Here are the enums you also need (probably):

enum FilterType: String, Identifiable, CaseIterable {
    var id: Self { self }

    case all = "Al"
    case active = "Active"
    case retired = "Retired"
}

enum SortType: String, Identifiable, CaseIterable {
    var id: Self { self }

    case brand = "by Brand"
    case model = "by Model"
    case distance = "by Distance"
    case aquisitionDate = " by Aquisition Date"
}

2

Answers


  1. I made an implementation to give you a hint on how you could achieve what you want without the Picker item. The issue with the Picker item, as far as my knowledge and testing go, is that it captures the tap events, and does not tell you when you tap on the same item.
    Here’s what I did:

    struct ContentView: View {
        
        @State private var show = false
        @State var filterBinding: FilterType = .all
        @State var sortingBinding: SortType = .brand
        @State var ascending = true
        
        var body: some View {
            NavigationStack {
                
                Button(action: {
                    show.toggle()
                }, label: {
                    Image(systemName: "line.3.horizontal.decrease.circle.fill")
                })
                .overlay {
                    if show {
                        SortingList()
                    }
                }
            }
            
            
        }
        
        @ViewBuilder
        func SortingList() -> some View {
            VStack {
                
                List {
                    ForEach(SortType.allCases) { sortType in
                        
                        HStack {
                            Image(systemName: "checkmark")
                                .opacity(sortingBinding == sortType ? 1 : 0)
                            Text(sortType.rawValue)
                            Image(systemName: self.ascending ? "chevron.up" : "chevron.down")
                                .opacity(self.sortingBinding == sortType ? 1 : 0)
                        }
                        .onTapGesture {
                            if sortType == sortingBinding {
                                self.ascending.toggle()
                                return // <-- Remove this if you want to make the overlay disappear when changing the order type
                            }
                            sortingBinding = sortType
                            withAnimation(.easeInOut(duration: 0.5)) {
                                show = false
                            }
                        }
                    }
                }
                
                List {
                    ForEach(FilterType.allCases) { filterType in
                        Text(filterType.rawValue)
                            .tag(filterType)
                    }
                }
                
            }
            .background(.ultraThinMaterial)
            .frame(width: 250, height: 500)
        }   
    }
    

    Now, it is not the best looking, of course, I did not put so much effort into that, I just wanted it to have the intended behavior. You can improve it visually as you like. Reverting sorting order

    Login or Signup to reply.
  2. You don´t need a picker here at all. Just use the ForEach loop inside the menu. And use Buttons instead of Text.

    e.g.:

    Menu{
        // add all filters to the menu
        ForEach(FilterType.allCases) { filterType in
            if filterType == viewmodel.filterBinding{
                // filter is selected so no action needed the item will be greyed out
                Label(filterType.rawValue, systemImage: "checkmark")
            } else {
                // to change the selection add a button that sets the value in the viewmodel
                Button{
                    viewmodel.filterBinding = filterType
                } label: {
                    Text(filterType.rawValue)
                }
            }
        }
        Divider()
        // add all sorttypes to the menu
        ForEach(SortType.allCases) { sortType in
            // if the sorting is selected
            if viewmodel.sortingBinding == sortType{
                Button{
                    // a second tap on the sorttype will change the sort order
                    viewmodel.toggleSortOrder()
                } label: {
                    // use a label with the chevron up or down
                    Label(sortType.rawValue, systemImage: viewmodel.sortOrder == .forward ? "chevron.up" : "chevron.down")
                }
            } else{
                // if a value is selected assign it to the viewmodel
                Button{
                    viewmodel.sortingBinding = sortType
                } label: {
                    Text(sortType.rawValue)
                }
            }
        }
    } label: {
        Image(systemName: "line.3.horizontal.decrease.circle.fill")
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search