SwiftUI - 为不同的 Picker 使用 Enum 的所有值,但只有一个值

Posted

技术标签:

【中文标题】SwiftUI - 为不同的 Picker 使用 Enum 的所有值,但只有一个值【英文标题】:SwiftUI - using all but one values of Enum for different Pickers 【发布时间】:2021-04-28 14:40:30 【问题描述】:

在我的应用程序中,我需要在两个不同的地方使用 Picker。在一个地方,它将一个属性设置为三个值之一:A、B 或 C。但是,在另一个地方,我想使用选择器过滤一组项目以显示所有项目(无过滤器)或仅 A、B 或 C 项。

所以,我的第一次尝试是像这样创建一个enum

enum Category 
  case all
  case A
  case B
  case C

使用它,我可以成功过滤我的列表。但是,当我尝试在数组中创建一个新项目时,我不希望 all 成为一个选项。用户应该只能看到带有 A、B 和 C 的选择器。

我可以试试:

enum Category 
  case A
  case B
  case C

但是我该如何添加过滤器选择器的all 选项?

(为了解决下面的一些问题,这里有一些关于我试图完成的具体事情的更多细节......

锦标赛可以是男子、女子或混合锦标赛。在我列出锦标赛的屏幕上,我希望能够显示所有锦标赛或仅显示男子锦标赛、仅显示女子锦标赛或仅显示混合锦标赛。所以,我有一个横跨 iPhone 宽度的选择器。外观和效果都很棒。

显然,当向列表中添加新项目时,我必须指定其类别,而不是“全部”。也是一个选择器。

因此,在一种情况下,我需要三个值,在另一种情况下,我需要相同的三个值,并在开头添加“All”。)

【问题讨论】:

【参考方案1】:

您应该定义您的 enum 而不使用 all 大小写,因为 all 不是项目的有效 Category。 (这是一个称为“make illegal states unrepresentable”的编程指南。)

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

使用该定义,用于设置项目属性的Picker 可以如下所示:

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

那么您应该认识到您的过滤器是可选的。您可以按项目类别过滤,也可以不执行过滤。所以你的过滤器属性应该被声明为可选的:

@Binding var categoryFilter: Category?

用于设置过滤器的Picker则需要小心使用可选标签:

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

【讨论】:

哦,这看起来也不错。那么,我可以将 Text 项目添加到 Picker?而且,当我选择“无”选项时,categoryFilter 将设置为 nil? 是的,您使用Text 将项目添加到Picker。是的,如果您选择None,它会将categoryFilter 设置为nil【参考方案2】:

你可以这样创建枚举

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

然后在需要从三个中选择时使用类别数组。

【讨论】:

这太完美了!快速、简单、有效。谢谢。【参考方案3】:

您可以将枚举设为CaseIterable,然后当您想显示所有枚举值时可以使用allCases 属性,当您只想显示某些情况时使用filter

注意:在我的示例代码中,您需要将 selection: .constant(Category.a) 替换为 @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)
                    
                
            
        
    

【讨论】:

将“all”排除在枚举之外,然后将 ForEach 置于 ["All"] + Category.allCases 之上,而不是过滤器不是更好吗? 通过阅读原帖but then how do I add the all option for the filter picker?,有一种情况需要呈现“All”。就个人而言,从 UI 的角度来看,这可能不是允许用户选择这些“类别”的最佳方式。也许是可用类别(A、B、C)的列表和自动选择的“全选”按钮。 @Jack 我添加了一些关于我正在做的原因的更多细节。有了这些额外的信息,我的问题是否更有意义? @Shadowrun 好问题。也许归结为 4 选项版本与 3 选项版本使用了多少次? @rob-mayoff 是对的 - all 不是一个类别 - 您可以将 4 路枚举视为直接表示 UI 中的行,那么 All 将是一个那些。但是不要将这样的枚举称为“类别”,因为 All 不是类别,而且无论如何您都可能需要模型层中的 3 路类别枚举,因此与其有 2 个相似的枚举,不如代表您安排的行的模型与 Category.allCases +“所有”行【参考方案4】:

如果我理解正确,请回答您的问题。

    修改你的枚举来实现 CaseIterable 看看下面。

     enum Category:CaseIterable
      case all
      case A
      case B   
      case C
     
    

    过滤掉您的类别与所有情况匹配的位置。下面的例子。

    let preferredcategory = Category.allCases.filter return $0 !=  Category.all
    

    要测试我们的结果,请参见下面的代码。

     print(preferredcategory.count)
     for catname in preferredcategory 
     print("Hello, \(catname)!")
    
    

    您可以在各个页面上阅读更多内容。

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

谢谢。

因此,您可以使用preferredCategory 变量向用户显示您不希望全部显示的类别。您还可以使用相同的过滤器来仅显示枚举中包含所有大小写的部分。

另外,好处是您可以保留枚举并在应用程序的任何地方重用它,而无需对值进行硬编码。如果你只想显示所有你只需要过滤掉剩余的情况。

有些人可能想在没有正当理由的情况下投反对票,但如果您认为该方法是错误的,请随时编辑或发表评论,说明如何改进解决方案。

【讨论】:

我确信这是解决问题的方法,无需在应用程序中的任何地方硬编码值,即使有人在没有正当理由的情况下投了反对票,我也不会感到难过。

以上是关于SwiftUI - 为不同的 Picker 使用 Enum 的所有值,但只有一个值的主要内容,如果未能解决你的问题,请参考以下文章

SwiftUI Segmented Picker 元素为灰色且禁用

SwiftUI Picker,将 String 转换为 Double

SwiftUI Picker 选择结构

SwiftUI 中的选择器选择和翻译冲突

SwiftUI:使用 Picker(分段)更改 ForEach 源或获取请求

SwiftUI:如何根据 Picker 的值更新 ForEach 循环的范围