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