Reputation: 527
If the variable comes from an array then the label is not automatically refreshed. Is there a specific reason for that?
@State private var categories: [ItemCategory] = getCategories()
@State private var isOn = true
Button(action: {
categories[1].chose = !categories[1].chose
}, label: {
Text(categories[1].chose ? "Add" : "Remove") // not automatically refreshed, only with view change (go to an other and then back)
})
Button(action: {
isOn = !isOn
}, label: {
Text(isOn ? "Add" : "Remove") // automatically refreshed
})
Update:
Sorry I missed the ItemCategory
class ItemCategory: Codable, Equatable, Identifiable, Hashable {
var name: String
var items: [Item]
var chose: Bool
var collapsed: Bool
}
Upvotes: 1
Views: 263
Reputation: 54526
The issue occurs because ItemCategory
is a class. Whenever you change its properties, the object remains the same. The @State
property wrapper reacts when the wrapped object is changed, not when only its properties are changed.
Here you can find more information about the difference between a class and a struct:
The simplest solution to your problem is to change ItemCategory
to be a struct (possibly change Item
as well):
struct ItemCategory: Codable, Equatable, Identifiable, Hashable {
var name: String
var items: [Item]
var chose: Bool
var collapsed: Bool
// ...
}
Alternatively, if you want ItemCategory
to remain a class, you can remove the category object and insert it again to the collection:
Button(action: {
let category = categories.remove(at: 1)
category.chose.toggle()
categories.insert(category, at: 1)
}, label: {
Text(categories[1].chose ? "Add" : "Remove")
})
Upvotes: 1
Reputation: 3831
For refreshing UI (observe value and update on it)
import Combine
Create a preview:
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView(list: CategoryList())
}
}
Create a custom model class for category
class ItemCategory {
var chose: Bool = false
}
OR create a model structure for category, don't require to send object change notification
struct ItemCategory {
var chose: Bool = false
}
Create a static list array observing value (you can assign your data list)
class CategoryList: ObservableObject {
@Published var items = [ItemCategory(),ItemCategory()]
}
Button text updating by observing in content view
struct ContentView: View {
@ObservedObject var list: CategoryList
var body: some View {
Button(action: {
list.items[1].chose = !list.items[1].chose
/// If using a custom model as class, then we have to send notification to update otherwise commenting this line
list.objectWillChange.send() // send manually update notification cos class is not auto update support
}, label: {
Text(list.items[1].chose ? "Add" : "Remove")
})
}
}
Upvotes: 0
Reputation: 21
Better - use state object
@StateObject var categories: Categories = ...
Where:
class Categories : ObservableObject {
@Published var list: [ItemCategory] = []
init() {...}
update(index: Int, row: ItemCategory) {
self.objectWillChange.send()
list[index] = row
}
}
And to update view from @Published:
objectWillChange.send()
You can make changes already in view:
Button(action: {
categories.objectWillChange.send()
categories.list[1].chose = !categories.list[1].chose
}, label: {
Text(categories[1].chose ? "Add" : "Remove") // not automatically refreshed, only with view change (go to an other and then back)
})
Upvotes: 0