How to use Bind an Associative Swift enum array?

2019-08-28 01:19发布

My question is similar to this one -> How to use Bind an Associative Swift enum?

I have modified the example provided to be an array. The GroupView accepts a binding as a parameter because I want the GroupView to modify the data in the enum. The difference between the original question and this one is that in this one, the enums are an array instead of a single one.

How to i extract a binding from the enums so that the GroupView can modify the enums correctly? Here is the modified code

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] }

标签: swift swiftui
1条回答
等我变得足够好
2楼-- · 2019-08-28 02:09

Ok, if the array is fixed in size:

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)
        }
    }
}

If your array is not fixed, you should consider this from iOS13 release notes:

The identified(by:) method on the Collection protocol is deprecated in favor of dedicated init(:id:selection:rowContent:) and init(:id:content:) initializers. (52976883, 52029393)

The retroactive conformance of Int to the Identifiable protocol is removed. Change any code that relies on this conformance to pass .self to the id parameter of the relevant initializer. Constant ranges of Int continue to be accepted:

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

However, you shouldn’t pass a range that changes at runtime. If you use a variable that changes at runtime to define the range, the list displays views according to the initial range and ignores any subsequent updates to the range.

Then, if your array is not fixed in size, you may need more code:

As I mentioned in the comments. You cannot make an enum identifiable (if you can, please do tell how!). So the only alternative is to use id: \.self in the ForEach. But to do that, we need to make Instruction conform to Hashable.

Also, to get the binding, we need the index of its position. The solution here (findIndex), may not be the best thing performance wise, but I don't expect your Instructions array to have thousands of elements... so that should be ok.

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)
         }
     }
}
查看更多
登录 后发表回答