tmsblgh
tmsblgh

Reputation: 527

Button label not refreshing if variable is in array

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

Answers (3)

pawello2222
pawello2222

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

Rahul Mayani
Rahul Mayani

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

ceojosef
ceojosef

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

Related Questions