swiftuiios16

Picker Selection "nil" is Invalid and doesn't have a Tag


I have an app that stores transaction data with a category classification. The category names may be selected from a pool of enclosed names or added through text entry. The problem that I am having is with the selection of categories from pool of names.

The structure GetCategory is designed to select a transaction category. For example, buying some items at the grocery store might be categorized under a food category. The return value of this struct is to return the category name string.

Category selection is producing a warning message:

The transaction category selection process is producing the error Picker: the selection "nil" is invalid and does not have an associated tag, this will give undefined results.

I have tried the selection process with and without a tag and also with and without an optional, but don't seem to be getting any closer to resolving this warning and error.

The category-in-use flag catInuse is set true when selected.

struct GetCategory: View {
    @EnvironmentObject var categories: Categories

    @Binding var entryCat: CatModel?

    var categoryList: [CatModel] {
        return categories.catItem.filter(\.catInUse)
    }

    var body: some View {

        ForEach(categoryList) { list in
            Text(list.catName)
        }

        Section(header: Text("Select Category")) {
            Picker("", selection: $entryCat) {

                ForEach(categoryList) { category in
                    Text(category.catName)
                        .tag(Optional(category.id))
                }
            }.pickerStyle(MenuPickerStyle())
        }
        if let gotName = entryCat {
            Text("Selected Category is \(gotName.catName)")
                .padding(.top, 15)
        }
    }
}

Solution

  • Upon reflextion, here is another approach that uses entryCat: CatModel? and test it is not nil before displaying the Picker. This way the option of nil selection is not possible.

    The code below is just some example code that works for me, ie it does not produce the warnings. You need to ajust the code to suit your needs.

    struct CatModel:  Codable, Identifiable, Hashable {
        var id = UUID()
        var catNum: Int         // used by setup categories
        var catName: String     // category name
        var catTotal: Double    // category total
        var catBudget: Double   // category budget
        var catPix: String      // sf symbol
        var catInUse: Bool      // catInUse: true = category in use
        var catCustom: Bool     // true = custom category (can be deleted)
    }
    
    struct ContentView: View {
        @StateObject var categories = Categories()
        @State var entryCat: CatModel? // <-- here
        
        var body: some View {
            VStack (spacing: 55) {
                Text("Selected is \(entryCat?.catName ?? "no selection")") // <-- here for testing
                GetCategory(entryCat: $entryCat)
                    .environmentObject(categories)
                
                // for testing, change some catInUse to true
                Button("click to test") {
                    // change the first false catInUse to true
                    if let first = categories.catItem.firstIndex(where: {!$0.catInUse}) {
                        categories.catItem[first].catInUse = true
                        entryCat = categories.catItem[first]
                    }
                }
            }
        }
    }
    
    class Categories: ObservableObject {
    
        // this is for testing purpose
        @Published var catItem = [
            CatModel(catNum: 0, catName: "Cat A", catTotal: 0.0, catBudget: 0, catPix: "a.circle", catInUse: false, catCustom: true),
            CatModel(catNum: 1, catName: "Cat B", catTotal: 0.0, catBudget: 0, catPix: "b.circle", catInUse: true, catCustom: true),
            CatModel(catNum: 2, catName: "Cat C", catTotal: 0.0, catBudget: 0, catPix: "c.circle", catInUse: true, catCustom: true)
            //..... more  CatModel
        ]
        
        //..... more code
    }
    
    struct GetCategory: View {
        @EnvironmentObject var categories: Categories
        
        @Binding var entryCat: CatModel? // <-- here
    
        var categoryList: [CatModel] {
            categories.catItem.filter(\.catInUse)
        }
        
        var body: some View {
            VStack {
                Section(header: Text("Select Category")) {
                    if entryCat != nil  {  // <-- here
                        Picker("", selection: $entryCat) {
                            ForEach(categoryList) { category in
                                Text(category.catName).tag(category as CatModel?) // <-- here, needed
                            }
                        }.pickerStyle(MenuPickerStyle())
                    } else {
                        Text("No selections available").foregroundColor(.red) // <-- here is entryCat == nil
                    }
                }
            }
            .onAppear {
                // <-- try to make entryCat not nil, for testing if needed
                if entryCat == nil  {
                    if let theEntryCat = categoryList.first {
                        entryCat = theEntryCat
                    }
                }
            }
        }
    }