WWDC22 SALE: Save 50% on all my Swift books and bundles! >>

SOLVED: Problems updating button label on property change

Forums > iOS

Hello:

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)) {
                            ListEmployees(employee:employee)
                        }
                    }
                    .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!

1      

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.

1      

@sbeitzel

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.

thank-you!

1      

Save 50% in my Black Friday sale.

SAVE 50% To celebrate WWDC22, all our books and bundles are half price, so you can take your Swift knowledge further without spending big! Get the Swift Power Pack to build your iOS career faster, get the Swift Platform Pack to builds apps for macOS, watchOS, and beyond, or get the Swift Plus Pack to learn advanced design patterns, testing skills, and more.

Save 50% on all our books and bundles!

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

Reply to this topic…

You need to create an account or log in to reply.

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.