Reputation: 1311
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
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.
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 inLocationResultsTableViewController
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
}
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
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
Reputation: 501
You need to update datasource and replace newest data to preserve the state, otherwise your tableviewcells are reusable.
Upvotes: 0