I've been looking for a few days and I'm still can't find a solution.
I'd like to group a list of structs that are binding in a list, and still be able to update them.
In the example below I'd like to be able to toggle isOn and the bindable array be updated. This is as close as I've got...
enum UserType: Int, CaseIterable {
case one = 1
case two = 2
}
struct User: Identifiable {
var id = UUID()
var name: String
var type: UserType
var isOn: Bool
}
struct UserView: View {
@State var users: [User] = [
User(name: "James", type: .one, isOn: false),
User(name: "Josh", type: .two, isOn: false),
User(name: "Jim", type: .one, isOn: false),
User(name: "Jake", type: .two, isOn: false)
]
var body: some View {
List(){
ForEach(UserType.allCases) { type in
Section(header: Text("\(type.rawValue)")) {
ForEach(users) { user in
Text("user.name")
Toggle(isOn: user, label: Text("isOn"))
}
}
}
}
}
}
You should change your Data models to a class
instead of a struct
.
It is how Apple recommends managing model data
https://developer.apple.com/documentation/swiftui/managing-model-data-in-your-app
It requires changing the model a little
class User: Identifiable, ObservableObject {
@Published var id: UUID
@Published var name: String
@Published var type: UserType
@Published var isOn: Bool
init(id: UUID = UUID(), name: String, type: UserType, isOn: Bool) {
self.id = id
self.name = name
self.type = type
self.isOn = isOn
}
}
And using @ObservedObject
to see changes and use @Binding
for the value types.
struct RowView: View {
@ObservedObject var user: User
var body: some View {
Text(user.name)
Toggle(isOn: $user.isOn, label: {
Text("isOn")
})
}
}
Then your UserView
can look something like.
struct UserView: View {
@State var users: [User] = [
User(name: "James", type: .one, isOn: false),
User(name: "Josh", type: .two, isOn: false),
User(name: "Jim", type: .one, isOn: false),
User(name: "Jake", type: .two, isOn: false)
]
var grouped: [UserType: [User]] {
Dictionary(grouping: users) { element in
element.type
}
}
var body: some View {
List{
ForEach(Array(grouped), id:\.key) { (type, users) in
Section(header: Text("\(type.rawValue)")) {
ForEach(users) { user in
RowView(user: user)
}
}
}
}
}
}
For iOS 17 @Published
and ObservableObject
are no longer used you use use @Observable
but you can adapt your code with a few changes.
@Observable
class User: Identifiable {
var id: UUID = .init()
var name: String = ""
var type: UserType = UserType.allCases.randomElement()!
var isOn: Bool = Bool.random()
init(id: UUID = UUID(), name: String, type: UserType, isOn: Bool) {
self.id = id
self.name = name
self.type = type
self.isOn = isOn
}
}
And use @Bindable
for the row.
struct RowView: View {
@Bindable var user: User
var body: some View {
Text(user.name)
Toggle(isOn: $user.isOn, label: {
Text("isOn")
})
}
}