Search code examples
sortingfilterswiftuitoolbarpicker

SwiftUI - Picker .onChange and didSet


I'm trying to achieve the behavior in the attached GIF:

enter image description here

Sorry for the High Speed, I had to compress it dramatically to be able to upload it here. The App is "Documents" from Readdle if you want to have a look on your own.

Anyways: I'm exactly trying to achieve this behavior (sorting and filtering, including the dynamic arrow up down icon).

I tried the following approach, however I'm not able to achieve this "ontap" expierience. On Change only triggers when I change the value but when I want to sort an existing value ascending and descending it's not working (which is obvious because it's not changing). I already played around with "didSet" but this also did not work.

Do you have an idea how this can be accomplished?

Below is my code:

import SwiftUI

struct ContentView: View {
    
    @State var selection = 0
    @State var sortByAsc = true
    @State var filterColumn = "A"
    
    //Test to set case via picker but picter doesnt execute didSet
    @State var myFilterTest: MyFilters = .alphabetical {
        didSet {
            switch myFilterTest {
            case .creationDate:
                sortByAsc.toggle()
                print("c")
                
            case .rating:
                sortByAsc.toggle()
                print("b")
                
            case .alphabetical:
                sortByAsc.toggle()
                print("a")
            }
        }
    }
    
    var body: some View {
        NavigationView {
            Text("Hello, World!")
                .padding()
                .navigationTitle("SwiftUI")
                .toolbar {
                    ToolbarItem(placement: .navigationBarTrailing) {
                        Menu(content: {
                            
                            Picker("My Picker", selection: $selection) {
                                Label("Title", systemImage: sortByAsc ? "arrow.down" : "arrow.up")
                                    .tag(0)
                                Label("Rating", systemImage: sortByAsc ? "arrow.down" : "arrow.up")
                                    .tag(1)
                                    .onTapGesture {
                                        print("tap")
                                    }
                            }
                            .onChange(of: selection) { tag in
                                print("Selected Tag: \(tag)")
                                sortByAsc.toggle()
                                if(tag == 0) {
                                    filterColumn = "Title"
                                }
                                if(tag == 1) {
                                    filterColumn = "Rating"
                                }
                            }
                        }, label: {
                            Image(systemName: "ellipsis.circle")
                        })
                    }
                }
        }
    }
}

enum MyFilters: CaseIterable {
    case alphabetical
    case rating
    case creationDate
}


Solution

  • Solved It. Here's the Code:

    struct PickerView: View {
        
        @State private var pickerIndex = 0
        @State private var previousPickerIndex = 0
        @State var sortByAsc = true
        
        var body: some View {
            
            let pickerSelection = Binding<Int>(get: {
                return self.pickerIndex
            }, set: {
                self.pickerIndex = $0
                if(pickerIndex == previousPickerIndex) {
                    sortByAsc.toggle()
                }
                previousPickerIndex = pickerIndex
            })
            
            NavigationView {
                Text("Hello, World!")
                    .padding()
                    .navigationTitle("SwiftUI")
                    .toolbar {
                        ToolbarItem(placement: .navigationBarTrailing) {
                            Menu(content: {
                                Picker("My Picker", selection: pickerSelection) {
                                    ForEach(0..<4, id: \.self) { index in
                                        Label("Title \(index)", systemImage: getSortingImage(menuItem: index))
                                            .tag(index)
                                    }
                                }
                            }, label: {
                                Image(systemName: "ellipsis.circle")
                            })
                        }
                    }
            }
        }
        
        func getSortingImage(menuItem: Int) -> String {
            if(menuItem == pickerIndex) {
                if(sortByAsc) {
                    return "arrow.down"}
                else {
                    return "arrow.up"
                }
            }
            else {
                return ""
            }
        }
        
    }