如何使用绑定Swift Swift枚举数组?

时间:2019-08-20 06:02:29

标签: swift swiftui

我的问题与此类似-> How to use Bind an Associative Swift enum?

我将提供的示例修改为数组。 GroupView接受绑定作为参数,因为我希望GroupView修改枚举中的数据。原始问题与这个问题的区别在于,在这个问题中,枚举是一个数组,而不是单个数组。

我如何从枚举中提取绑定,以便GroupView可以正确地修改枚举? 这是修改后的代码

import SwiftUI
struct ContentView: View {
    @ObservedObject var viewModel = ViewModel()
    var body: some View {
        VStack {
            ForEach(0..<viewModel.box.instructions.count) { index -> GroupView in
                let instruction = self.viewModel.box.instructions[index]

                return GroupView(v: ????) // How do i extract the binding here???
            }
        }
    }
}
struct GroupView: View {
    @Binding var v: Group
    var body: some View {
        Button("Hello: \(self.v.groupValue)") {
            self.v.groupValue += 1
        }
    }
}
class ViewModel : ObservableObject {
    @Published var box: Box!
    init() {
        box = Box(instructions: [
            Instruction.group(Group(groupValue: 10)),
            Instruction.group(Group(groupValue: 20))
        ])
    }
}

struct Group { var groupValue: Int }
enum Instruction { case group(Group) }
struct Box { var instructions: [Instruction] }

1 个答案:

答案 0 :(得分:5)

好,如果数组大小固定:

ForEach(0..<viewModel.box.instructions.count) { index -> GroupView in
   return GroupView(v: self.viewModel.bindingGroup(idx: index))
}
class ViewModel : ObservableObject {
    @Published var box: Box!

    init() {
        box = Box(instructions: [
            Instruction.group(Group(groupValue: 10)),
            Instruction.group(Group(groupValue: 20))
        ])
    }

    func bindingGroup(idx: Int) -> Binding<Group> {
        return Binding<Group>(get: { () -> Group in
            if case .group(let g) = self.box.instructions[idx] {
                return g
            } else {
                return Group(groupValue: 0)
            }
        }) {
            self.box.instructions[idx] = .group($0)
        }
    }
}

如果阵列不是固定的,则应从iOS13发行说明中考虑:

  

不赞成在Collection协议上使用identified(by :)方法   支持专用init(:id:selection:rowContent :)和   init(:id:content :)初始化程序。 (52976883,52029393)

     

追溯   删除了Int与Identifiable协议的一致性。改变任何   依赖于此一致性的代码将.self传递给id   相关初始化程序的参数。 Int的恒定范围继续   被接受:

     
List(0..<5) {
    Text("Rooms")
}
     

但是,您不应传递在运行时更改的范围。如果使用在运行时更改的变量来定义   范围,列表将根据初始范围显示视图,   忽略对该范围的任何后续更新。

然后,如果阵列大小未固定,则可能需要更多代码:

正如我在评论中提到的。您无法使枚举可识别(如果可以,请告诉如何!)。因此,唯一的选择是在id: \.self中使用ForEach。但是要做到这一点,我们需要使Instruction符合Hashable

此外,要获取绑定,我们需要其位置的索引。这里的解决方案(findIndex)可能并不是最佳的性能选择,但我不希望您的Instructions数组具有成千上万个元素...所以应该没事。

import SwiftUI

struct ContentView: View {
    @ObservedObject var viewModel = ViewModel()
    var body: some View {
        VStack {
            ForEach(viewModel.box.instructions, id: \.self) { (instruction: Instruction) -> GroupView in
                let idx = self.viewModel.box.instructions.firstIndex(of: instruction)! // I am assuming it will always return a value
                return GroupView(v: self.viewModel.bindingGroup(idx: idx))
            }

            Button("Add Instruction") {
                self.viewModel.objectWillChange.send()
                self.viewModel.box.instructions.append(Instruction.group(Group(groupValue: 123)))
            }
        }
    }
}

struct GroupView: View {
    @Binding var v: Group
    var body: some View {
        Button("Hello: \(self.v.groupValue)") {
            self.v.groupValue += 1
        }
    }
}

struct Group { var groupValue: Int }
enum Instruction: Hashable {
    case group(Group)

    static func == (lhs: Instruction, rhs: Instruction) -> Bool {
        guard case .group(let gL) = lhs else { return false }
        guard case .group(let gR) = rhs else { return false }

        return gL.groupValue == gR.groupValue
    }

    func hash(into hasher: inout Hasher) {
        if case .group(let g) = self {
            hasher.combine(g.groupValue)
        }
    }
}

struct Box { var instructions: [Instruction] }

class ViewModel : ObservableObject {
    @Published var box: Box!

    init() {
        box = Box(instructions: [
            Instruction.group(Group(groupValue: 10)),
            Instruction.group(Group(groupValue: 20))
        ])
    }

    func bindingGroup(idx: Int) -> Binding<Group> {
         return Binding<Group>(get: { () -> Group in
             if case .group(let g) = self.box.instructions[idx] {
                 return g
             } else {
                 return Group(groupValue: 0)
             }
         }) {
             self.box.instructions[idx] = .group($0)
         }
     }
}