UPGRADE YOUR SKILLS: Learn advanced Swift and SwiftUI on Hacking with Swift+! >>

SOLVED: Problems updating button label on property change

Forums > iOS


I have a view that uses an environment variable containing an array of class objects. Each of these objects has a boolean property called 'Cached' that I need to toggle to allow the user to show either Image A or B on a button. Here is my code:

import UIKit
import SwiftUI
import Combine

class People : ObservableObject, Decodable  {

    @Published var list: [Person]

    init() { 
        self.list = JSONServices().getData()


class Person: ObservableObject, Identifiable {

    let id = UUID()
    let name: String
    let bio: String
    let category: Category
    let image: String
    @Published var cached: Bool = false
    @Published var metadata: ForecastMetaData?

    init(name: String, bio: String, category: Category, image: String, lat: Double, lon: Double, cached: Bool) {
        self.name = name
        self.bio = bio
        self.category = category
        self.image = image
        self.cached = cached
        self.metadata = Servive.getData()

struct ContentView: View {
    @State var searchText: String = ""
    @State var searchCategory: Int = 0

    // Filter the person per the search box
    var filteredPeople: [Person] {
        if !self.searchText.isEmpty && self.searchCategory == 0{ 
            return Employees.list.filter { $0.name.lowercased().prefix(searchText.count).contains(searchText.lowercased()) }
        } else if !self.searchText.isEmpty && self.searchCategory != 0 { 
            return Employees.list.filter { $0.name.lowercased().prefix(searchText.count).contains(searchText.lowercased()) && $0.category == searchCategory }
        } else if self.searchText.isEmpty && self.searchCategory != 0 { 
            return Employees.list.filter { $0.category == searchCategory }
        } else {
            return Employees.list

    // get the information from the environment object
    @EnvironmentObject var Employees: People

    var body: some View {
        NavigationView {
            List {
                CustomSearchBar(searchText: $searchText, searchCategory: $searchCategory)
                ForEach(filteredPeople) { employee in
                    ZStack {
                        NavigationLink(destination: DisplayEmployeeInfo(id: employee.id)) {
                    .swipeActions(edge: .leading, content: {
                        Button {
                            employee.cached.toggle() // THIS IS WHAT IS NOT WORKING

                        } label: {
                            Label("Cache", systemImage: !employee.cached ? "square.and.arrow.down" : "trash")
                        .tint(!employee.cached ? .green : .red)

            .navigationBarTitle(Text("My Employees"),displayMode: .large)

When the user clicks the button the image on the label does not change from "square.and.arrow.down" to "trash" (or viceversa) even though the property's value has in fact changed. I have checked this by attaching a "didSet" event on the property to output to the console once the property has changed. I am curious to know what my mistake is, why is the actual image not updating if the porperty has in fact changed values and it is set to advertise its changes as a published property of an observable object?

I should note that I can get this to work if I change the Person class to be a Struct instead. But I have to say I don't know why that matters if with it being a class I am making direct changes to the correct object's property. Any help would be appreciated!


I suspect that what's happening is, you're updating the cached property on a Person, but not calling the objectWillChange.send() method on People. It looks as though your view is observing People. So, the view will update when it thinks People has changed. Changing a single person needs to trigger the change notifier on the People class.



This was the problem. Thank-you for your reply. This is exactly why it would work when person was a struct because a struct's value semanticsso the change to one of its properties is also a change changes the whole object thus a change to the people array so @Published will send the notification and the View body will be recomputed.

I kept it as a class and just created a toggle function inside of people that will call objectWillChange.send () when a specified person changes.



Hacking with Swift is sponsored by RevenueCat

SPONSORED Take the pain out of configuring and testing your paywalls. RevenueCat's Paywalls allow you to remotely configure your entire paywall view without any code changes or app updates.

Learn more here

Sponsor Hacking with Swift and reach the world's largest Swift community!

Archived topic

This topic has been closed due to inactivity, so you can't reply. Please create a new topic if you need to.

All interactions here are governed by our code of conduct.

Unknown user

You are not logged in

Log in or create account

Link copied to your pasteboard.