Zonker.in.Geneva
Zonker.in.Geneva

Reputation: 1499

SwiftUI - using all but one values of Enum for different Pickers

In my app, I need to use a Picker in two different places. In one place, it's to set a property to one of three values: A, B, or C. But, in another place, I want to use the picker to filter a array of items to show either all items (no filter) or only the A, B, or C items.

So, my first attempt was to create an enum like so:

enum Category {
  case all
  case A
  case B
  case C
}

Using this, I can filter my list successfully. But, when I try to create a new item in the array, I don't want all to be an option. The user should only see a picker with A, B, and C.

I could try:

enum Category {
  case A
  case B
  case C
}

but then how do I add the all option for the filter picker?

(To address some comments below, here are some more details on the specific thing I'm try to accomplish...

A tournament can be a men's, women's, or mixed tournament. On the screen where I list the tournaments, I want to be able to show all tournaments or just the men's, just the women's or just the mixed tournaments. So, I have a picker that spans the width of the iPhone. Looks and works great.

Obviously, when adding a new item to the list, I have to specify its category, but not "all". Also a picker.

So, in one case, I need three values, in the other case, I need the same three values with "All" added at the beginning.)

Upvotes: 3

Views: 2748

Answers (4)

rob mayoff
rob mayoff

Reputation: 385650

You should define your enum without the all case, because all is not a valid Category for an item. (This is a programming guideline known as “make illegal states unrepresentable”.)

enum Category: Hashable, CaseIterable {
    case a
    case b
    case c
}

With that definition, the Picker for setting an item's property can look like this:

Picker("Category", selection: $category) {
    ForEach(Category.allCases, id: \.self) { category in
        Text(verbatim: "\(category)")
            .tag(category)
    }
}

Then you should recognize that your filter is optional. You can filter by item category, or you can perform no filtering. So your filter property should be declared optional:

@Binding var categoryFilter: Category?

The Picker for setting the filter then needs to be careful to use optional tags:

Picker("Category Filter", selection: $categoryFilter) {
    Text("None")
        .tag(Category?.none)
    ForEach(Category.allCases, id: \.self) { category in
        Text(verbatim: "\(category)")
            .tag(Category?.some(category))
    }
}

Upvotes: 7

Abiola Akinnubi
Abiola Akinnubi

Reputation: 313

To answer your question if I understood you correctly.

  1. Modify your enum to implement the CaseIterable take a look at it below.

     enum Category:CaseIterable{
      case all
      case A
      case B   
      case C
     }
    
  2. Filter out where your category matches the all case. Example below.

    let preferredcategory = Category.allCases.filter{ return $0 !=  Category.all}
    
  3. To test our result see the code below.

     print(preferredcategory.count)
     for catname in preferredcategory {
     print("Hello, \(catname)!")
    }
    
  4. You can read more on this respective pages.

https://developer.apple.com/documentation/swift/caseiterable

https://developer.apple.com/documentation/swift/caseiterable/2994869-allcases

https://www.hackingwithswift.com/example-code/language/how-to-list-all-cases-in-an-enum-using-caseiterable

Thanks.

So you can use the preferredCategory variable to show the user your category for the one you do not want all to show. you can also use the same filter to show only the part of the enum that has all case as well.

Also, the good thing is you get to keep your enum and reuse it everywhere in your application without having to hardcode values. if you want to show only All you just have to filter out the remaining case.

Some people may want to downvote without a valid reasons but if you believe the approach is wrong feel free to edit or leave a comment on how to improve the solution.

Upvotes: 0

Jack
Jack

Reputation: 2671

You can make your enum CaseIterable and then you can use the allCases property when you want to display all enum values, and use a filter when you only want certain cases displayed.

NOTE: In my sample code, you'll need to replace selection: .constant(Category.a) with an @Binding

struct Test {
    enum Category: String, CaseIterable {
        case all = "All"
        case a = "A"
        case b = "B"
        case c = "C"
    }
    
    struct TestView: View {
        var body: some View {
            VStack {
                Picker("Picker Title", selection: .constant(Category.a)) {
                    ForEach(Category.allCases, id: \.self) { category in
                        Text(category.rawValue).tag(category)
                    }
                }
                
                Picker("Picker Title", selection: .constant(Category.a)) {
                    ForEach(Category.allCases.filter({ $0 != .all }), id: \.self) { category in
                        Text(category.rawValue).tag(category)
                    }
                }
            }
        }
    }
}

Upvotes: 1

Deepa Bhat
Deepa Bhat

Reputation: 214

You can create enum in this way

enum Category {
    case all
    case A
    case B
    case C
    
    static let categories: [Category] = [.A, .B, .C]
}

And then use the categories array when you need to choose among three.

Upvotes: 2

Related Questions