pjmanning
pjmanning

Reputation: 1311

Swift UITableViewCell Button State Change on Scroll

So I have a button in my UITableViewCell that I change the state and update the database. However, when I scroll and go back the state is in the original state as when the view was loaded.

How do I get the state to stay changed after scrolling?

I tried setting the state images in prepareForReuse and that did not work.

// MARK: - Outlets
@IBOutlet weak var locationImage: UIImageView!
@IBOutlet weak var displayName: UILabel!
@IBOutlet weak var countryLabel: UILabel!
@IBOutlet weak var beenHereLabel: SpringLabel!
@IBOutlet weak var needToGoLabel: SpringLabel!
@IBOutlet weak var wavetrotterButton: SpringButton!
@IBOutlet weak var checkmarkButton: SpringButton!

// MARK: - Variables
var db:Firestore!
let selection = UISelectionFeedbackGenerator()
let notification = UINotificationFeedbackGenerator()
var documentId:String!

// MARK: - Nib shown
override func awakeFromNib() {
    super.awakeFromNib()
    // Initialization code

    db = Firestore.firestore()

}

func customInit(displayName: String, id: String, country: String, image: UIImage) {
    self.displayName.text = displayName
    self.documentId = id
    self.countryLabel.text = country
    self.locationImage.image = image
}

// MARK: - Actions
@IBAction func checkmarkButtonPressed(_ sender: UIButton) {
    notification.notificationOccurred(.success)
    checkmarkButton.animation = "pop"
    beenHereLabel.animation = "pop"
    if checkmarkButton.isSelected == true {
        checkmarkButton.animate()
        beenHereLabel.animate()
        checkmarkButton.isSelected = false
        // Delete location surfed
        if let user = Auth.auth().currentUser {
            Firestore.firestore().collection("users").document(user.uid).collection("surfed").document("\(documentId!)").delete() { err in
                if let err = err {
                    print("Error removing document: \(err)")
                } else {
                    print("\(self.documentId!) successfully removed!")
                }

            }
        }
    } else {
        checkmarkButton.animate()
        beenHereLabel.animate()
        checkmarkButton.isSelected = true
        // Add location surfed
        if let user = Auth.auth().currentUser {
            Firestore.firestore().collection("users").document(user.uid).collection("surfed").document("\(documentId!)").setData([
                "name":displayName.text ?? "",
                "country":countryLabel.text ?? ""
            ])  { err in
                if let err = err {
                     print("Error writing document: \(err)")
                } else {
                    print("\(self.documentId!) added to surfed locations")
                }
            }
        } 
    }
}

@IBAction func wavetrotterButtonPressed(_ sender: UIButton) {
    notification.notificationOccurred(.success)
    wavetrotterButton.animation = "pop"
    needToGoLabel.animation = "pop"
    if wavetrotterButton.isSelected == true {
        wavetrotterButton.animate()
        needToGoLabel.animate()
        wavetrotterButton.isSelected = false
        // Delete location wantToSurf
        if let user = Auth.auth().currentUser {
            Firestore.firestore().collection("users").document(user.uid).collection("wantToSurf").document("\(documentId!)").delete() { err in
                if let err = err {
                    print("Error removing document: \(err)")
                } else {
                    print("\(self.documentId!) successfully removed!")
                }
            }
        }
    } else {
        wavetrotterButton.animate()
        needToGoLabel.animate()
        wavetrotterButton.isSelected = true
        // Add location wantToSurf
        if let user = Auth.auth().currentUser {
            Firestore.firestore().collection("users").document(user.uid).collection("wantToSurf").document("\(documentId!)").setData([
                "name":displayName.text ?? "",
                "country":countryLabel.text ?? ""
            ])  { err in
                if let err = err {
                    print("Error writing document: \(err)")
                } else {
                    print("\(self.documentId!) added to surfed locations")
                }
            }
        }
    }
}

LocationResultsTableViewController.swift

   // MARK: - Variables
var listName: String?
var listId: String?
var db:Firestore!
let storage = Storage.storage().reference()
var locationArray = [Location]()
var userSurfedArray = [String]()
var userWantToSurfArray = [String]()
let pullToRefreshControl = UIRefreshControl()
var selectedDocumentId: String?

// MARK: - View Did Load
override func viewDidLoad() {
    super.viewDidLoad()

    self.title = listName

    db = Firestore.firestore()

    SVProgressHUD.show()

    getListLocations()
    getUserSurfedArray()
    getUserWantToSurfArray()

    // Configure the cell to the nib file
    let nib = UINib(nibName: "LocationCell", bundle: nil)
    tableView.register(nib, forCellReuseIdentifier: "locationCell")

    self.refreshControl = pullToRefreshControl
    pullToRefreshControl.addTarget(self, action: #selector(refreshTable), for: .valueChanged)

    self.navigationItem.backBarButtonItem = UIBarButtonItem(title: "", style: UIBarButtonItemStyle.plain, target: nil, action: nil)

}

// MARK: - View Will Appear
override func viewWillAppear(_ animated: Bool) {
    tableView.reloadData()
}

// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
    // #warning Incomplete implementation, return the number of sections
    return 1
}

override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    // #warning Incomplete implementation, return the number of rows
    return locationArray.count
}

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: "locationCell", for: indexPath) as! LocationCell
    // Configure the cell...

    let location = locationArray[indexPath.row]

    cell.documentId = location.documentId

    // Set button states
    if self.userSurfedArray.contains(cell.documentId!) {
        cell.checkmarkButton.isSelected = true
    } else {
        cell.checkmarkButton.isSelected = false
    }

    if self.userWantToSurfArray.contains(cell.documentId!) {
        cell.wavetrotterButton.isSelected = true
    } else {
        cell.wavetrotterButton.isSelected = false
    }

    let locationImageRef = storage.child("locationImages/"+(location.documentId)+".jpg")
    // Download in memory with a maximum allowed size of 1MB (1 * 1024 * 1024 bytes)
    locationImageRef.getData(maxSize: 1 * 1024 * 1024) { data, error in
        if let error = error {
            // Uh-oh, an error occurred! Display Default image
            print("Error - unable to download image: \(error)")
        } else {
            // Data for "locationImages/(locationId).jpg" is returned
            cell.customInit(displayName: location.name, id: location.documentId, country: location.country, image: UIImage(data: data!)!)
        }
        SVProgressHUD.dismiss()
    }

    return cell
}

override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
    selectedDocumentId = locationArray[indexPath.row].documentId
    self.performSegue(withIdentifier: "goToLocationProfileSegue", sender: self)
}

override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
    return 260
}


// MARK: - Functions
func getListLocations() {
    if Auth.auth().currentUser != nil {
        db.collection("locations").whereField("lists."+listId!, isEqualTo: true).getDocuments() { (querySnapshot, error) in
            if let error = error {
                print("Error getting documents: \(error)")
            } else {
                print(querySnapshot?.documents.count ?? "0")
                for document in querySnapshot!.documents {
                    self.locationArray.append(Location(documentId: document.documentID, name: document["name"] as! String, country: document["country"] as! String))
                }
                DispatchQueue.main.async {
                    self.tableView.reloadData()
                }
            }
        }
    }
}

func getUserSurfedArray() {
    if let user = Auth.auth().currentUser {
        db.collection("users").document(user.uid).collection("surfed").getDocuments() { (querySnapshot, error) in
            if let error = error {
                print("Error getting documents: \(error)")
            } else {
                for document in querySnapshot!.documents {
                    self.userSurfedArray.append(document.documentID)
                }
                DispatchQueue.main.async {
                    self.tableView.reloadData()
                }
            }
        }
    }
}

func getUserWantToSurfArray() {
    if let user = Auth.auth().currentUser {
        db.collection("users").document(user.uid).collection("wantToSurf").getDocuments() { (querySnapshot, error) in
            if let error = error {
                print("Error getting documents: \(error)")
            } else {
                for document in querySnapshot!.documents {
                    self.userWantToSurfArray.append(document.documentID)
                }
                DispatchQueue.main.async {
                    self.tableView.reloadData()
                }
            }
        }
    }
}

Upvotes: 1

Views: 1437

Answers (3)

Ankit Jayaswal
Ankit Jayaswal

Reputation: 5679

As I can see from your code, you are loading the buttons from below lines:

In cellForRowAt indexPath

// Set button states
if self.userSurfedArray.contains(cell.documentId!) {
    cell.checkmarkButton.isSelected = true
} else {
    cell.checkmarkButton.isSelected = false
}

if self.userWantToSurfArray.contains(cell.documentId!) {
    cell.wavetrotterButton.isSelected = true
} else {
    cell.wavetrotterButton.isSelected = false
}

The thing is on scrolling, when table cell hide it get dequeued and when it appears again this cell reloads with dequeueReusableCell(withIdentifier:) and you are not updating the userSurfedArray & userWantToSurfArray on clicking the buttons.

You need to update the userSurfedArray & userWantToSurfArray on button click.

It it seems insufficient, please post your code where you are loading these arrays so that I can help you to explain how to update these.


Update:

In your updated code, I can see you are loading the userSurfedArray & userWantToSurfArray in viewDidLoad() only.

To update userSurfedArray & userWantToSurfArray: Create a delegate in UITableViewCell and conform this protocol in LocationResultsTableViewController like following:

In UITableViewCell: (Supposing table cell's name is LocTableViewCell)

import UIKit

protocol UpdateUserArrayDelegate: class {
    func updateUserSurfedArray(documentId: String, isAdd: Bool)
    func updateUserWantToSurfArray(documentId: String, isAdd: Bool)
}

class LocTableViewCell: UITableViewCell {

weak var cellDelegate: UpdateUserArrayDelegate?

// MARK: - Outlets
@IBOutlet weak var locationImage: UIImageView!
@IBOutlet weak var displayName: UILabel!
@IBOutlet weak var countryLabel: UILabel!
@IBOutlet weak var beenHereLabel: SpringLabel!
@IBOutlet weak var needToGoLabel: SpringLabel!
@IBOutlet weak var wavetrotterButton: SpringButton!
@IBOutlet weak var checkmarkButton: SpringButton!

// MARK: - Variables
var db:Firestore!
let selection = UISelectionFeedbackGenerator()
let notification = UINotificationFeedbackGenerator()
var documentId:String!

// MARK: - Nib shown
override func awakeFromNib() {
    super.awakeFromNib()
    // Initialization code

    db = Firestore.firestore()

}

func customInit(displayName: String, id: String, country: String, image: UIImage) {
    self.displayName.text = displayName
    self.documentId = id
    self.countryLabel.text = country
    self.locationImage.image = image
}

// MARK: - Actions
@IBAction func checkmarkButtonPressed(_ sender: UIButton) {
    notification.notificationOccurred(.success)
    checkmarkButton.animation = "pop"
    beenHereLabel.animation = "pop"
    if checkmarkButton.isSelected == true {
        checkmarkButton.animate()
        beenHereLabel.animate()
        checkmarkButton.isSelected = false
        // Delete location surfed
        if let user = Auth.auth().currentUser {
            Firestore.firestore().collection("users").document(user.uid).collection("surfed").document("\(documentId!)").delete() { err in
                if let err = err {
                    print("Error removing document: \(err)")
                } else {
                    cellDelegate?.updateUserSurfedArray(documentId: self.documentId, isAdd: false)
                    print("\(self.documentId!) successfully removed!")
                }

            }
        }
    } else {
        checkmarkButton.animate()
        beenHereLabel.animate()
        checkmarkButton.isSelected = true
        // Add location surfed
        if let user = Auth.auth().currentUser {
            Firestore.firestore().collection("users").document(user.uid).collection("surfed").document("\(documentId!)").setData([
                "name":displayName.text ?? "",
                "country":countryLabel.text ?? ""
            ])  { err in
                if let err = err {
                    print("Error writing document: \(err)")
                } else {
                    cellDelegate?.updateUserSurfedArray(documentId: self.documentId, isAdd: true)
                    print("\(self.documentId!) added to surfed locations")
                }
            }
        }
    }
}

@IBAction func wavetrotterButtonPressed(_ sender: UIButton) {
    notification.notificationOccurred(.success)
    wavetrotterButton.animation = "pop"
    needToGoLabel.animation = "pop"
    if wavetrotterButton.isSelected == true {
        wavetrotterButton.animate()
        needToGoLabel.animate()
        wavetrotterButton.isSelected = false
        // Delete location wantToSurf
        if let user = Auth.auth().currentUser {
            Firestore.firestore().collection("users").document(user.uid).collection("wantToSurf").document("\(documentId!)").delete() { err in
                if let err = err {
                    print("Error removing document: \(err)")
                } else {
                    cellDelegate?.updateUserWantToSurfArray(documentId: self.documentId, isAdd: false)
                    print("\(self.documentId!) successfully removed!")
                }
            }
        }
    } else {
        wavetrotterButton.animate()
        needToGoLabel.animate()
        wavetrotterButton.isSelected = true
        // Add location wantToSurf
        if let user = Auth.auth().currentUser {
            Firestore.firestore().collection("users").document(user.uid).collection("wantToSurf").document("\(documentId!)").setData([
                "name":displayName.text ?? "",
                "country":countryLabel.text ?? ""
            ])  { err in
                if let err = err {
                    print("Error writing document: \(err)")
                } else {
                    cellDelegate?.updateUserWantToSurfArray(documentId: self.documentId, isAdd: true)
                    print("\(self.documentId!) added to surfed locations")
                }
            }
        }
    }
}
}

In LocationResultsTableViewController

Add an extension with protocol methods

 extension LocationResultsTableViewController: UpdateUserArrayDelegate {
        func updateUserSurfedArray(documentId: String, isAdd: Bool) {
            if isAdd {
                self.userSurfedArray.append(documentId)
            } else {
                if self.userSurfedArray.contains(documentId) {
                    self.userSurfedArray.remove(at: self.userSurfedArray.index(of: documentId)!)
                }
            }
        }

    func updateUserWantToSurfArray(documentId: String, isAdd: Bool) {
        if isAdd {
            self.userWantToSurfArray.append(documentId)
        } else {
            if self.userWantToSurfArray.contains(documentId) {
                self.userWantToSurfArray.remove(at: self.userWantToSurfArray.index(of: documentId)!)
            }
        }
    }
}

Update the cellForRowAt indexPath as: (Add cell.cellDelegate = self)

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: "locationCell", for: indexPath) as! LocationCell
    // Configure the cell...

    let location = locationArray[indexPath.row]

    cell.documentId = location.documentId

    // Conform table cell delegate here
    cell.cellDelegate = self

    // Set button states
    if self.userSurfedArray.contains(cell.documentId!) {
        cell.checkmarkButton.isSelected = true
    } else {
        cell.checkmarkButton.isSelected = false
    }

    if self.userWantToSurfArray.contains(cell.documentId!) {
        cell.wavetrotterButton.isSelected = true
    } else {
        cell.wavetrotterButton.isSelected = false
    }

    let locationImageRef = storage.child("locationImages/"+(location.documentId)+".jpg")
    // Download in memory with a maximum allowed size of 1MB (1 * 1024 * 1024 bytes)
    locationImageRef.getData(maxSize: 1 * 1024 * 1024) { data, error in
        if let error = error {
            // Uh-oh, an error occurred! Display Default image
            print("Error - unable to download image: \(error)")
        } else {
            // Data for "locationImages/(locationId).jpg" is returned
            cell.customInit(displayName: location.name, id: location.documentId, country: location.country, image: UIImage(data: data!)!)
        }
        SVProgressHUD.dismiss()
    }

    return cell
}

Update

For case isAdd, we do not need to check self.userSurfedArray.contains(documentId):

extension LocationResultsTableViewController: UpdateUserArrayDelegate {
    func updateUserSurfedArray(documentId: String, isAdd: Bool) {
        if isAdd {
            self.userSurfedArray.append(documentId)
        } else {
            if self.userSurfedArray.contains(documentId) {
                self.userSurfedArray.remove(at: self.userSurfedArray.index(of: documentId)!)
            }
        }
    }

    func updateUserWantToSurfArray(documentId: String, isAdd: Bool) {
        if isAdd {
            self.userWantToSurfArray.append(documentId)
        } else {
            if self.userWantToSurfArray.contains(documentId) {
                self.userWantToSurfArray.remove(at: self.userWantToSurfArray.index(of: documentId)!)
            }
        }
    }
}

Upvotes: 1

Shehata Gamal
Shehata Gamal

Reputation: 100503

The reason behind that is cell reusing , you must save the state for the button at that indexPath , and restore it in cellForRowAt

Upvotes: 1

Aragunz
Aragunz

Reputation: 501

You need to update datasource and replace newest data to preserve the state, otherwise your tableviewcells are reusable.

Upvotes: 0

Related Questions