首页 > 解决方案 > 如何使用绑定关联 Swift 枚举数组?

问题描述

我的问题与此类似->如何使用 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] }

标签: swiftswiftui

解决方案


好的,如果数组的大小是固定的:

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 协议上的 identify(by:) 方法已被弃用,取而代之的是专用的 init( :id:selection:rowContent:) 和 init( :id:content:) 初始化程序。(52976883, 52029393)

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

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

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

然后,如果您的数组大小不固定,您可能需要更多代码:

正如我在评论中提到的。您无法使枚举可识别(如果可以,请告诉如何操作!)。所以唯一的选择是id: \.selfForEach. 但要做到这一点,我们需要使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)
         }
     }
}

推荐阅读