I want to use code such as the following:
import Foundation
import Combine
import SwiftUI
final class DataStore: ObservableObject {
@Published var bools: [Bool] = [true, false]
}
struct ContentView: View {
@EnvironmentObject var dataStore: DataStore
var body: some View {
HStack {
Spacer()
Toggle(isOn: $dataStore.bools[0]) {
Text(dataStore.bools[0] ? "On" : "Off")
}
Spacer()
Toggle(isOn: $dataStore.bools[1]) {
Text(dataStore.bools[1] ? "On" : "Off")
}
Spacer()
}
}
}
(Actually this code is quite useless, but it's just about that I want to pass an element of an array as a binding to a subview.)
In Xcode beta 2 this worked, but since beta 5 I get the following warning on both the "Toggle" lines:
'subscript(_:)' is deprecated: See Release Notes for migration path.
And the app crashes when I try to launch it.
Indeed, I have read the release notes, and I believe that the issue has something to do with that "the Binding structure’s conditional conformance to the Collection protocol is removed".
The problem is that I don't understand how to use the sample code that they give with the code that I want to use. Can someone help me with that?
Xcode 11, beta 6 UPDATE:
Good news! Just as I suspected, in beta 6, the Binding
conformance to MutableCollection
has been been replaced with something else. Instead of conforming to MutableCollection, it now let your access the elements via @dynamicMemberLookup
. The result is you now can keep doing dataStore.bools[0]
and no longer get a warning!
Xcode 11, beta 5 (old answer)
If you want to get rid of the deprecation, you can use the code below:
I used something similar to answer a slightly different question. In that other case, it was a Binding, not an ObservableObject (that is why I do not mark it as duplicate). However, the basics are the same: https://stackoverflow.com/a/57333200/7786555
I have the feeling that in the next beta, something will change again, as there are some contradictions in the release notes.
final class DataStore: ObservableObject {
@Published var bools: [Bool] = [true, false]
func element(idx: Int) -> Binding<Bool> {
return Binding<Bool>(get: { () -> Bool in
return self.bools[idx]
}) {
self.bools[idx] = $0
}
}
}
struct ContentView: View {
@EnvironmentObject var dataStore: DataStore
var body: some View {
HStack {
Spacer()
Toggle(isOn: dataStore.element(idx: 0)) {
Text(dataStore.bools[0] ? "On" : "Off")
}
Spacer()
Toggle(isOn: dataStore.element(idx: 1)) {
Text(dataStore.bools[1] ? "On" : "Off")
}
Spacer()
}
}
}
Alternatively, you can use the solution for the other question, which involves extending Binding:
final class DataStore: ObservableObject {
@Published var bools: [Bool] = [true, false]
}
extension Binding where Value: MutableCollection, Value.Index == Int {
func element(_ idx: Int) -> Binding<Value.Element> {
return Binding<Value.Element>(
get: {
return self.wrappedValue[idx]
}, set: { (value: Value.Element) -> () in
self.wrappedValue[idx] = value
})
}
}
struct ContentView: View {
@EnvironmentObject var dataStore: DataStore
var body: some View {
HStack {
Spacer()
Toggle(isOn: $dataStore.bools.element(0)) {
Text(dataStore.bools[0] ? "On" : "Off")
}
Spacer()
Toggle(isOn: $dataStore.bools.element(1)) {
Text(dataStore.bools[1] ? "On" : "Off")
}
Spacer()
}
}
}
Based on kontiki's very helpful answer, I have made the following extension, so that you can use subscript just like before:
extension Binding where Value: MutableCollection, Value.Index == Int {
public subscript(position: Value.Index) -> Binding<Value.Element> {
Binding<Value.Element>(get: {
self.wrappedValue[position]
}, set: {
self.wrappedValue[position] = $0
})
}
}
In theory, if needed, you could do the same for subscript(bounds:).