Search code examples
swiftxcodeswiftuiswappicker

how to swap two picker value when both of them are same in swiftUI


in this code I want to change two picker values when they are same to remain this two different from each other. I try to use swap but that's not working properly.

for example : when first picker value is one and second is two, when I try to change second value to "one", value immediately change to one and first picker become two!

 struct ContentView: View {
    @State private var list = ["one","two","three","four","five"]
    @State private var from: String = "one"
    @State private var from: String = "two"
         var body: some View {
       VStack {
          Picker("",selection: $from) {
             ForEach(list, id: \.self) { item in
                Text(item)
         }
       }
       .pickerStyle(.menu)
       .padding(.trailing)
       .onChange(of: from) { newValue in
                                    if newValue == to {
                                        (to,from) = (from, to)
                                    }
                                }  

          Picker("",selection: $to) {
             ForEach(list, id: \.self) { item in
                Text(item)
         }
       }
       .pickerStyle(.menu)
       .padding(.trailing)
    }
    }
}

I'm using this tuple solution for another section of the app but in this part that's not working :

(from, to) = (to, from) 

how can I perform immediately swap this two values when for example to going to the same with from?


Solution

  • I think there is a connection that is missing. You state

    If my change in first picker cause two picker become equal, so immediately swap them!

    If you swap 2 equal values you get 2 equal values.

    Assumption:

    What It seems you want is a solution for the standard interview question of swapping 2 values.

    Given x=1 and y=2. Swap the values

    The way to do this is

    let temp = y //holds a value
    y = x //starts swap
    x = temp //finishes swap
    

    Now your result is y=1 and x=2

    You need that temp variable to hold the value of one of them, while you are swapping values.

    Now, if we ge back to your statement, the fallacy is in waiting for the values to become equal.

    When you click on the new item the change is being made immediately, there is no way to retrieve what the value was previously.

    x = newValue or y = newValue
    

    The only way to achieve a "swapping" is to determine if there is a need to swap before the "source of truth" changes.

    1. Pick new value
    2. Check if swapping is needed
    3. Swap the source of truth values
    4. Tell SwiftUI there is an update

    You can achieve this setup in SwiftUI with an ObservableObject

    class AutoPickerVM: ObservableObject{
        ///Variable that holds the value for From - Source of truth
        private var storeFrom: String = "one"
        ///Processes the changes between the pickers and triggers view updates
        var from: String {
            get{
                storeFrom
            }
            set{
                if newValue == to{
                    //Take the value that is currently there
                    let curTo = to
                    //Start swapping
                    storeTo = storeFrom
                    //Place the previous value
                    storeFrom = curTo
                    
                }else{
                    storeFrom = newValue
                }
                objectWillChange.send()
            }
        }
        ///Variable that holds the value for to - Source of truth
        private var storeTo: String = "two"
        ///Processes the changes between the pickers and triggers view updates
        var to: String {
            get{
                storeTo
            }
            set{
                if newValue == from{
                    //Take the value that is currently there
                    let curFrom = from
                    //Start swapping
                    storeFrom = storeTo
                    //Place the previous value
                    storeTo = curFrom
                }else{
                    storeTo = newValue
                }
                
                objectWillChange.send()
            }
        }
        init(){}
    }
    

    Then your View would look like this

    struct AutoPicker: View {
        @StateObject var vm: AutoPickerVM = .init()
        @State private var list = ["one","two","three","four","five"]
        
        var body: some View {
            VStack {
                Picker("",selection: $vm.from) {
                    ForEach(list, id: \.self) { item in
                        Text(item)
                    }
                }
                .pickerStyle(.menu)
                .padding(.trailing)
                
                Picker("",selection: $vm.to) {
                    ForEach(list, id: \.self) { item in
                        Text(item)
                    }
                }
                .pickerStyle(.menu)
                .padding(.trailing)
            }
        }
    }