Galen Smith
Galen Smith

Reputation: 387

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)
        }
    }
}

Upvotes: 1

Views: 3286

Answers (2)

Dennis Vennink
Dennis Vennink

Reputation: 1173

One way is to have a Divider tagged with, in your case, CatModel?(nil) as the last item in Picker. Since Dividers in menus are not rendered if they're the last item it will silence your warning and it will not be selectable:

Divider().tag(CatModel?(nil))

As an aside, in order to select a default value with data that is fetched from elsewhere, assign it within an .onAppear(perform:) on Picker:

.onAppear {
  if self.entryCat == nil {
    self.entryCat = self.categoryList.first
  }
}

Upvotes: 2

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
                }
            }
        }
    }
}

Upvotes: 1

Related Questions