Multi-Selection List : changing Binding Array to Binding Set and back again

I am trying to create a menu picker for two or three text items. Small miracles, but I have it basically working. Problem is it uses a set, and I want to pass arrays. I need to modify PickerView so the Bound Parameter is an [String] instead of Set<String>. Have been fighting this for a while now... Hoping for insights.


struct PickerView: View {
    @Binding  var colorChoices: Set<String>    
    let defaults = UserDefaults.standard
    var body: some View {
        let possibleColors = defaults.object(forKey: "ColorChoices") as? [String] ?? [String]()
        Menu {
            ForEach(possibleColors, id: \.self) { item in
                Button(action: {
                    if colorChoices.contains(item) {
                        colorChoices.remove(item)
                    } else {
                        colorChoices.insert(item)
                    }
                }) {
                    HStack {
                        Text(item)
                        Spacer()
                        if colorChoices.contains(item) {
                            Image(systemName: "checkmark")
                        }
                    }
                }
            }
        } label: {
            Label("Select Items", systemImage: "ellipsis.circle")
        }
        Text("Selected Colors: \(colorChoices, format: .list(type: .and))")
    }        
}
#Preview("empty") {
    @Previewable @State var colorChoices: Set<String> = []
    PickerView(colorChoices: $colorChoices)
}
#Preview("Prefilled") {
    @Previewable @State var colorChoices: Set<String> = ["Red","Blue"]
    PickerView(colorChoices: $colorChoices)
}

My Content View is suppose to set default values the first time it runs, if no values already exist...

import SwiftUI

struct ContentView: View {
    @State private var viewDidLoad: Bool = false

    var body: some View {
        HomeView()
            .onAppear { // The following code should execute once the first time contentview loads.  If a user navigates back to it, it should not execute a second time.
                if viewDidLoad == false {
                    viewDidLoad = true
                    // load user defaults
                    let defaults = UserDefaults.standard
                    // set the default list of school colors, unless the user has already updated it prior
                    let defaultColorChoices: [String] = ["Black","Gold","Blue","Red","Green","White"]
                    let colorChoices = defaults.object(forKey: "ColorChoices") as? [String] ?? defaultColorChoices
                    defaults.set(colorChoices, forKey: "ColorChoices")
                }
            }
    }
}

#Preview {
    ContentView()
}

PickLoader allows you to dynamically add or delete choices from the list...

import SwiftUI

struct PickLoader: View {
    @State private var newColor: String = ""

    
    var body: some View {
        Form {
            Section("Active Color Choices") {
                 // we should have set a default color list in contentview, so empty string should not be possible.
                let defaults = UserDefaults.standard
                let colorChoices = defaults.object(forKey: "ColorChoices") as? [String] ?? [String]()
                List {
                    ForEach(colorChoices, id: \.self) { color in
                        Text(color)
                    }
                    .onDelete(perform: delete)
                    HStack {
                        TextField("Add a color", text: $newColor)
                        Button("Add"){
                            defaults.set(colorChoices + [newColor], forKey: "ColorChoices")
                            newColor = ""
                        }
                    }
                }
            }
        }
        .navigationTitle("Load Picker")
        Button("Reset Default Choices") {
            let defaults = UserDefaults.standard
            //UserDefaults.standard.removeObject(forKey: "ColorChoices")
            let colorChoices: [String] = ["Black","Gold","Blue","Red","Green","White"]
            defaults.set(colorChoices, forKey: "ColorChoices")
            }
        Button("Clear all choices") {
            let defaults = UserDefaults.standard
            defaults.removeObject(forKey: "ColorChoices")
            }
        }
    }
    func delete(at offsets: IndexSet) {
        let defaults = UserDefaults.standard
        var colorChoices = defaults.object(forKey: "ColorChoices") as? [String] ?? [String]()
        colorChoices.remove(atOffsets: offsets)
        defaults.set(colorChoices, forKey: "ColorChoices")
    }
#Preview {
    PickLoader()
}

And finally HomeView is where I am testing from - to see if binding works properly...

import SwiftUI

struct HomeView: View {
    //@State private var selection: Set<String> = []
    //@State private var selection: Set<String> = ["Blue"]
    @State private var selection: Set<String> = ["Blue", "Red"]

    var body: some View {
        NavigationStack {
            List {
                Section("Edit Picker") {
                    NavigationLink("Load Picker") {
                        PickLoader()
                    }
                }
                Section("Test Picker") {
                    PickerView(colorChoices: $selection)
                }
                Section("Current Results") {
                    Text("Current Selection: \(selection, format: .list(type: .and))")
                }
            }
            .navigationBarTitle("Hello, World!")
        }
    }
}

#Preview {
    HomeView()
}

If anyone uses this code, there are still issues - buttons on Loader don't update the list on the screen for one, and also dealing with deleting choices that are in use - how does picker deal with them? Probably simply add to the list automatically and move on. If anyone has insights on any of this also, great! but first I just need to understand how to accept an array instead of a set in pickerView.

I have tried using a computed value with a get and set, but I can't seem to get it right.

Thanks for any assistance! Cheers!

I tested the code in Xcode 16.4. I can select colors (but only one by one). It seems to work

So could you explicit very clearly what the problem is (are) ? With more details than a single sentence.

 

buttons on Loader don't update the list on the screen for one

Could you explain (possibly with some screenshots) ?

 

dealing with deleting choices that are in use - how does picker deal with them?

Same question: Could you explain (possibly with some screenshots) ?

Could you show where set / array is an issue in your code ?

Absolutely, sorry for the opacity.
First and foremost, the Pickerview takes a bound set as a parameter. I know while testing I am using a set as a parameter, but I really need to figure out how to redesign and take an array as a parameter instead. I believe the selection of colors has to be a set. So I will need to accept a Bound array, transform it into a set for use, and then back to an array again to get the bound value to update in my parent view...

My reason for doing so is that all the rest of my data is Array based. So with hope of making this a very reusable piece of code, I either will need to make it accept an array, or I will have to do the opposite transformation dozens of times in the parent views.

As to the other stuff... First whenever you add or delete colors using the Pickloader view, it doesn't update the list view until you exit and reload the view, or change a second item. Similarly, if you make changes, and then click the button at the bottom to reset to defaults, or clear the list entirely, it does what it says, but it does not update the view until the next change is made. It's like the view is always one step behind the actions. I am trying to use the list of colors stored in userDefaults directly, and I think I am going to need to copy it out, use it, and then save it back later... Not sure.

With regard to deleting choices that are being used, this can be demonstrated by viewing the canvas preview with preset data. I have it set up for Red and Blue. When you run that preview, it works perfectly, setting up Red and Blue as the choices, and allowing you to edit them and returning the edited set. However, if you then were to delete one of the colors - red or blue - and go back, you will find that it is listed in the selection set still, but can't be deleted or changed. The only way to remove it currently would be to add it back in to the list of choices. My plan there was to eventually check any presets passed in to verify it is in the current list, and add anything in that isn't in the list.

I think I can figure out the rest of this stuff, though I am certainly not turning away advice! May take me a while, but I think I know what to do there. I am drawing dead with respect to the first issue, though. I know it works right now, but I need to work with array's, not sets. I either will need to do this once here, or the reverse a dozen times over on the other side. For the life of me I cannot figure out how to get it to work. Thanks for the response

ps I am trying to teach myself swift. I learned Cobol, fortran, and 6800 assembly back in the day, and been out of touch since. No sir I did not forget a zero on that number. Best way to learn is to do, so I have a Swift Data Model... Schools with classes full of teachers and students etc. Trying to put it all together. No other reason then to learn how to do it before I actually attempt something I care about.

Multi-Selection List : changing Binding Array to Binding Set and back again
 
 
Q