【问题标题】:ForEach loop over different variable types swiftForEach 快速循环不同的变量类型
【发布时间】:2021-01-25 21:59:22
【问题描述】:

我正在努力解决一个问题,我认为(希望)有一个简单的解决方法,您可以提供帮助。

我正在尝试对多个变量运行ForEach 循环。为了简单起见,我只包含了两个变量,但还有更多变量,因此我想使用ForEach 循环而不是为每个变量重复相同的代码。每个变量都基于不同的枚举。

预期结果

我想在我的代码中运行一个 ForEach 循环,该循环遍历一个变量数组并提取变量的描述和相关的 rawValue。

变量

var profileEyeColor: EyeColor = EyeColor()
var profileHairColor: HairColor = HairColor()

枚举

enum EyeColor: String, CaseIterable {
    case notSet
    case amber
    case blue
    case brown
    case gray
    case green
    case hazel
    case other
    case witheld
    
    var description: String {
        "Eye Color"
    }
    
    init() {
        self = .notSet
    }
}
enum HairColor: String, CaseIterable {
    case notSet
    case black
    case blond
    case brown
    case auburn
    case red
    case gray
    case white
    case other
    case witheld
    
    var description: String {
        "Hair Color"
    }
    
    init() {
        self = .notSet
    }
}

ForEach 循环

ForEach([profileEyeColor, profileHairColor], id: \.self) { item in
   if item != .notSet && item != .witheld {
       print(item.description)
       print(item.rawValue)
   }
}

实际结果

构建失败,xCode 错误包括:

  • 无法将“profileHairColor”类型的值转换为预期的元素类型“EyeColor”

尝试了替代方法

我尝试使用for 循环而不是ForEach 来运行。我正在使用 SwiftUI,因此无法在正文中实现 for 循环。 我也试过把它拆分成一个单独的函数,但是函数出错了。

func profileDetailsLoop(data: [Any]) -> View {
   ForEach(data, id: \.self) { item in
      if item != .notSet && item != .witheld {
         HStack {
            Text(item.description)
            Text(item.rawValue)
       }
   }
}

错误:协议类型'Any'的值不能符合'Hashable';只有结构/枚举/类类型可以符合协议

如果我将 [Any] 替换为 [enum],则会收到以下错误:预期的元素类型

如果我将 [Any] 替换为任何特定的枚举类型,例如 [EyeColor],这将不起作用(并且可能也不适用于不同的枚举类型)。

【问题讨论】:

  • 所有枚举都是一样的还是只是巧合?
  • 巧合(尽管即使上面的两个枚举也有不同的情况),因为示例中的两个是头发/眼睛的颜色。其他枚举包括状态、偏好、可见性等......

标签: swift for-loop foreach enums swiftui


【解决方案1】:

ForEach 有一定的要求,它喜欢迭代Identifiable 项目的集合。考虑到这一点,让我们提供每个属性都可以提供的struct Attribute

struct Attribute: Identifiable {
    let name: String
    let value: String
    let id = UUID()
}

您可以手动为每个提供此属性的枚举添加一个计算变量,但这将是很多重复的代码。

相反,让我们注意所有属性都提供了rawValuedescription,这就是初始化Attribute 真正需要的全部内容。

定义这个协议:

protocol AttributeType {
    var rawValue: String { get }
    var description: String { get }
}

然后为返回Attribute的协议创建这个extension

extension AttributeType {
    var attribute: Attribute? {
        guard self.isSet else { return nil }
        return Attribute(name: self.description, value: self.rawValue)
    }
    
    var isSet: Bool { return !["notSet", "witheld"].contains(self.rawValue) }
}

最后,让你所有的枚举都采用AttributeType

enum EyeColor: String, CaseIterable, AttributeType {
    case notSet
    case amber
    ...
    
    var description: String {
        "Eye Color"
    }
    
    init() {
        self = .notSet
    }
}


enum HairColor: String, CaseIterable, AttributeType {
    case notSet
    case black
    ...
    
    var description: String {
        "Hair Color"
    }
    
    init() {
        self = .notSet
    }
}

然后您可以通过访问.attribute 属性为每个属性获取Attribute

ForEach([profileEyeColor.attribute, profileHairColor.attribute].compactMap { $0 }) { attribute in
    HStack {
        Text(attribute.name)
        Text(attribute.value)
    }
}

为了避免为数组中的每个属性输入.attribute,你可以这样写:

ForEach([profileEyeColor as AttributeType, profileHairColor].compactMap { $0.attribute }) {

这将为长属性列表节省大量输入。

注意事项:

  • .attribute 返回一个可选 Attribute。如果属性为.notSet.witheld,则为nil
  • .compactMap { $0 } 用于删除 nil 值,并为 ForEach 创建一个 [Attribute] 以进行迭代。
  • 由于AttributeIdentifiable,它提供了id,因此没有必要明确声明。

【讨论】:

  • 太棒了,非常感谢。这解决了我的问题。我会注意到,为了编译代码,我需要做的唯一更改是将“$”插入 compactMap {$0} 而不仅仅是 compactMap {0}
  • 糟糕,这是一个错字!你也可以避免为每个属性输入.attribute,这样写:ForEach([profileEyeColor as AttributeType, profileHairColor].compactMap { $0.attribute })。通过仅将第一个属性标记为as AttributeType,Swift 能够确定数组的类型,然后.attribute 可以应用于compactMap
  • 非常感谢您每次都避免使用 .attribute 的提示。我可能很快找到了一种更简单的方法。通过首先定义一个数组...例如: let attributeArray:[AttributeType] = [profileEyeColor, profileHairColor] 您避免需要对 ForEach 循环中的第一项执行“作为 AttributeType”。这使我的代码更易于阅读和遵循。老实说,非常感谢!惊人的帮助
  • 是的,提前定义数组是一个很好的方法。我假设您想在 ForEach 中动态创建数组,并展示了如何做到这一点。无论哪种方式,在compactMap 中应用.attribute 可以将额外代码保持在最低限度。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-07-09
  • 2021-12-02
  • 1970-01-01
  • 2017-08-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多