Search code examples
iosswiftfirebasebooleanibaction

Toggling Property Does Not Show Change in Real Time


I have a star button that I want to toggle between filled and empty when tapping. enter image description here

I implemented the following function:

func setStarButton() {
        
        let currentItem = data[currentItemIndex!]
        if currentItem.isStarred == true {
            starButton.setImage(UIImage(systemName: "star.fill"), for: .normal)
        }
        else {
            starButton.setImage(UIImage(systemName: "star"), for: .normal)
        }
        
    }

@IBAction func starTapped(_ sender: Any) {
        
        // Change the property in the Array
        var currentItem = item[currentItemIndex!]
        currentItem.isStarred?.toggle()
        
        // Update the database
        model.updateStarredStatus(currentItem.docID!, currentItem.isStarred!)
        
        // Update the button
        setStarButton()
        
    }

I am then calling that function in the viewDidAppear:

override func viewDidAppear(_ animated: Bool) {
        
        // Set the status of the star button
        setStarButton()
        
    }

So the following expected behaviour is happening:

  1. The star button is showing as the correct filled/unfilled status only when I open the view controller for the first time
  2. When I tap the star button, my Firebase Firestore database updates the Bool value

What I am trying to make happen: for the star button to become filled/unfilled in the view as I continue to tap it in real time.

Any thoughts? I'm think I might need the view to listen for changes in the database to make visual update but I'm not sure. Any guidance is much appreciated!


Solution

  • Your item array contains structs. Structs in Swift are value types. You effectively assign a copy of the struct to currentItem when you say var currentItem = item[currentItemIndex!]. This copy is then modified and thrown away when the function returns.

    Later, when you access data[currentItemIndex!] in setStarButton you are accessing the original, unmodified struct. After you refresh the array from Firebase you see the correct value.

    A simple way to avoid the problem is just to eliminate the local variable currentItem and operate on the struct in the array directly.

    Personally, I would rater pass the relevant value to setStarred; that way you eliminate the duplicated code of access the items array. You should also avoid force unwrapping wherever possible. It also doesn't make a lot of sense for properties like isStarred and docID to be optional - An item is either starred or it isn't. A default value of false makes more sense than nil. All documents from Firebase will have a docID

    func setStarButton(starred: Bool) {
         
        let imageName = starred ? "star.fill" : "star"
    
        starButton.setImage(UIImage(systemName: imageName), for: .normal)
     
    }
    
    @IBAction func starTapped(_ sender: Any) {
        guard itemIndex = self.currentItemIndex else {
            return
        }
    
        var isStarred = item[itemIndex].isStarred ?? false
    
        isStarred.toggle()
        item[itemIndex].isStarred = isStarred
            
            // Update the database
        model.updateStarredStatus(currentItem.docID!, isStarred)
            
            // Update the button
        setStarButton(starred: isStarred)
            
    }
    
    override func viewDidAppear(_ animated: Bool) {
            // Set the status of the star button
        if let itemIndex = self.currentItemIndex {
            setStarButton(starred: self.item[itemIndex].isStarred ?? false)
        } 
    }