Reputation: 99
I am reading data from Firebase and then displaying this data in tableview cells. There is also a file stored locally that contains the read/unread status for each cell. The file contains the key of the each child in Firebase, and I match this with the data in each cell.
Basically, if the cell has a key that is also found in the file, then this data is considered "read" by the user. However, if the cell has a key that has no equivalent in the local file, then this data is considered "unread".
So far this works perfectly. I have then added two swipe functions for each cell. First to mark as read, and also mark as unread. When marking as "read", the swipe invokes a function that writes the key for the cell to the local file. Conversely, when marking as "unread", the swipe invokes another function that removes the key from the local file. This also works perfectly.
However, on swipe, I also have to change the cell background colour to reflect "read" or "unread". This causes a problem because the cells are reloaded for reuse... in my case, I get 7 cells before it reloads.
This means that if I have more than 7 cells, and I mark something as "read", I end up changing the background colour of more than one cell. The file writing stuff works fine... just the background colour is duplicated.
I change the colour in the editActionsForRowAt function as follows:
func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]? {
let Action2 = UITableViewRowAction(style: .default, title: "Mark Read", handler: { (action, indexPath) in
// function goes on to do stuff before setting the colour...
if filteredMessageIDforSwipe.count == 0 {
let selectedCell = self.Nomination2TableView.cellForRow(at: indexPath) as! Nomination2TableViewCell
selectedCell.contentView.backgroundColor = UIColor(hexString: "#ffffff")
Note that I am not selecting a cell... rather swiping, and then trying to change the colour of the cell background for the cell that is swiped.
Obviously I only want the cell I swiped on to change colour, and not another cell (if I have more than 7 cells).
Thanks!
N.
EDIT: I've modified editActionsForRowAt as recommended below, but without effect.
func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]? {
selectedRowIndexForReadUnread = indexPath.row
let Action2 = UITableViewRowAction(style: .default, title: "Mark Read", handler: { (action, indexPath) in
if filteredMessageIDforSwipe.count == 0 && selectedRowIndexForReadUnread = indexPath.row {
let selectedCell = self.Nomination2TableView.cellForRow(at: indexPath) as! Nomination2TableViewCell
selectedCell.contentView.backgroundColor = UIColor(hexString: "#ffffff")
Edit: For reference, here is the editActionsForRowAt function... Action2 and Action5 are relevant.
func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]? {
selectedRowIndexForReadUnread = indexPath.row
if tableView == self.Nomination2SearchTableView {
let Action1 = UITableViewRowAction(style: .default, title: "Select", handler: { (action, indexPath) in
self.Nomination2SearchTableView.isHidden = true
self.Nomination2TableView.isHidden = false
NominationState = "Active"
let SearchObject: Nomination2SearchModel
SearchObject = Nomination2SearchList[indexPath.row]
userIDStringforNominationSearch = SearchObject.UserIDString!
let FullName = SearchObject.Full_Name
self.FullNameLabel.isHidden = false
self.FullNameLabel.text = "Showing " + FullName! + "'s Shout Outs"
self.LoadDataFromNominationafterSearch()
self.ActiveButton.setTitleColor(.orange, for: .normal)
let banner = NotificationBanner(title: "Tip", subtitle: "Swipe to select a user.", style: .info)
banner.haptic = .none
banner.show()
//We are setting the editor mode to the opposite of what is displayed in the tableview, because EditorMode text is what is displayed when you swipe the cell.
// EditorMode = "Set to Complete"
})
return [Action1]
}
if tableView == self.Nomination2TableView{
if NominationState == "Active" {
let Action2 = UITableViewRowAction(style: .default, title: "Mark Read", handler: { (action, indexPath) in
let searchObject9: Nomination2Model
searchObject9 = Nomination2List[indexPath.row]
let messageIDKey = searchObject9.key
let filename = self.getDocumentsDirectory().appendingPathComponent("output.txt")
do {
let textStringFromFile = try String(contentsOf: filename, encoding: .utf8)
var result: [(messageID: String, readStatus: String)] = []
// var indexCount = result.count
let rows = textStringFromFile.components(separatedBy: "\n")
for row in rows {
let columns = row.components(separatedBy: ",")
result.append((columns[0], columns[1]))
}
filteredMessageIDforSwipe = result.filter { $0.messageID == (messageIDKey) }
}
catch{
}
if filteredMessageIDforSwipe.count == 0 {
let selectedCell = self.Nomination2TableView.cellForRow(at: indexPath) as! Nomination2TableViewCell
// let selectedCellForReadMessages:UITableViewCell = self.Nomination2TableView.cellForRow(at: indexPath)! as! Nomination2TableViewCell
selectedCell.contentView.backgroundColor = UIColor(hexString: "#ffffff")
// let myReadCell = self.Nomination2TableView.cellForRow(at: indexPath) as! Nomination2TableViewCell
// myReadCell.NominationNameLabel.textColor = UIColor.lightGray
// myReadCell.NominationTextLabel.textColor = UIColor.lightGray
let NominationObject: Nomination2Model
NominationObject = Nomination2List[indexPath.row]
nominationKeyForWhenCellSelected = NominationObject.key
self.writeFile()
// self.removeRowFromFile()
}
if filteredMessageIDforSwipe.count > 0 {
let alert = UIAlertController(title: "Failure",
message: "Shout-out is already read!",
preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "OK", style: .default))
self.present(alert, animated: true, completion: nil)
}
})
let Action5 = UITableViewRowAction(style: .default, title: "Mark Unread", handler: { (action, indexPath) in
let searchObject9: Nomination2Model
searchObject9 = Nomination2List[indexPath.row]
let messageIDKey = searchObject9.key
let filename = self.getDocumentsDirectory().appendingPathComponent("output.txt")
do {
let textStringFromFile = try String(contentsOf: filename, encoding: .utf8)
var result: [(messageID: String, readStatus: String)] = []
// var indexCount = result.count
let rows = textStringFromFile.components(separatedBy: "\n")
for row in rows {
let columns = row.components(separatedBy: ",")
result.append((columns[0], columns[1]))
}
filteredMessageIDforSwipe = result.filter { $0.messageID == (messageIDKey) }
}
catch{
}
if filteredMessageIDforSwipe.count > 0 {
let selectedCell:UITableViewCell = tableView.cellForRow(at: indexPath)!
selectedCell.contentView.backgroundColor = UIColor(hexString: "#fcf0e5")
// let myCell = self.Nomination2TableView.cellForRow(at: indexPath) as! Nomination2TableViewCell
// myCell.NominationNameLabel.textColor = UIColor.orange
// myCell.NominationTextLabel.textColor = UIColor.black
let NominationObject: Nomination2Model
NominationObject = Nomination2List[indexPath.row]
nominationKeyForWhenCellSelected = NominationObject.key
// self.writeFile()
self.removeRowFromFile()
}
if filteredMessageIDforSwipe.count == 0 {
let alert = UIAlertController(title: "Failure",
message: "Shout-out is already unread!",
preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "OK", style: .default))
self.present(alert, animated: true, completion: nil)
}
})
Action2.backgroundColor = UIColor.lightGray
Action5.backgroundColor = UIColor.blue
return[Action2, Action5]
}
if NominationState == "Approve"{
let Action3 = UITableViewRowAction(style: .default, title: NominationState, handler: { (action, indexPath) in
let searchObject9: Nomination2Model
searchObject9 = Nomination2List[indexPath.row]
ForName = searchObject9.ForName!
FromName = searchObject9.FromName!
ForWhat = searchObject9.ForWhat!
key = searchObject9.key!
ForUserID = searchObject9.ForUserID!
FromUserID = searchObject9.FromUserID!
PointsGifted = searchObject9.PointsGifted!
NominationText = searchObject9.NominationText!
ImageNameNominations = searchObject9.ImageNameString!
ImageURLNominations = searchObject9.ImageURL!
databaseReference = Database.database().reference()
databaseReference.child("Users").child(FromUserID).observeSingleEvent(of: DataEventType.value, with: { (snapshot) in
let value = snapshot.value as? NSDictionary
let CurrentPointsRedeemed = value?["Points_Redeemed"] as? Int
let CurrentPointsBalance = value? ["Points_Balance"] as? Int
let NewPointsRedeemed = CurrentPointsRedeemed! + PointsGifted
let NewPointsBalance = CurrentPointsBalance! - PointsGifted
if NewPointsBalance >= 0{
databaseReference.child("Users").child(FromUserID).updateChildValues(["Points_Redeemed": NewPointsRedeemed])
databaseReference.child("Users").child(FromUserID).updateChildValues(["Points_Balance": NewPointsBalance])
self.postToFirebase()
self.giftPoints()
self.pendingCount()
Nomination2List.remove(at: indexPath.row)
tableView.deleteRows(at: [indexPath], with: .fade)
}
if NewPointsBalance <= 0{
let alert = UIAlertController(title: "Nominations",
message: "Not Enough Points for approval",
preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "OK", style: .default))
self.present(alert, animated: true, completion: nil)
}
})
})
Action3.backgroundColor = UIColor.blue
return[Action3]
}
//here
//Action2.backgroundColor = UIColor.blue
//return[Action2]
else{
return [UITableViewRowAction]()
}
}
else {
return [UITableViewRowAction]()
}
}
Upvotes: 0
Views: 541
Reputation: 352
You can manage this by using didSelectRow and cellForRow delegate methods of UITableView. Use one variable for managing selected row index. Use below code:
var selectedRowIndex:Int = -1
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = self.Nomination2TableView.dequeueReusableCell(withIdentifier: cellReuseIdentifier) as Nomination2TableViewCell!
if self.selectedRowIndex == indexPath.row {
cell.contentView.backgroundColor = UIColor(hexString: "#efefef")
} else {
cell.contentView.backgroundColor = UIColor(hexString:"defaultColor")
}
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
self.selectedRowIndex = indexPath.row
self.Nomination2TableView.reloadRows(at:[indexPath], with: .none)
}
As you edited your question, you can try below method:
func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]? {
DispatchQueue.main.async {
self.Nomination2TableView.beginUpdates()
self.selectedRowIndex = indexPath.row
self.Nomination2TableView.reloadRows(at:[indexPath], with: .none)
self.Nomination2TableView.endUpdates()
}
let Action2 = UITableViewRowAction(style: .default, title: "Mark Read", handler: { (action, indexPath) in
Upvotes: 0
Reputation: 2407
Table view cells get reused this is why the background color of other cells changes.
There is a nice code snippet on hacking with swift which does exactly what you need. I use this snippet in my own projects too.
So in your case it would look like this:
let backgroundView = UIView()
backgroundView.backgroundColor = UIColor(red:0.94, green:0.94, blue:0.94, alpha:1.0)
cell.selectedBackgroundView = backgroundView
Replace your code with this and I think it will be ok. Hope this helps.
EDIT:
To change only the color of the swiped cell you could try to insert an else statement after your if statement. You could try this:
let selectedCell = self.Nomination2TableView.cellForRow(at: indexPath) as! Nomination2TableViewCell
if filteredMessageIDforSwipe.count == 0 {
selectedCell.contentView.backgroundColor = UIColor(hexString: "#ffffff")
} else {
selectedCell.contentView.backgroundColor = UIColor.white
//or whatever the color of the background is by default
}
Let me know if this doesn't solve it!
EDIT 2:
I recreated the core of your problem in an (overly simplified) little test app. So to recap, this is what I understood to be the core of your problem: you would like to set the color of the cells you swiped in a table view. You would also like them to stay that color when you reopen the app and also prevent other cells from getting colored because of cell reusing.
In the solution below I have not used the exact same objects and variables that you use since that would be difficult to recreate without your complete code. What this is instead is the logic that you could implement in your own solution. So it seems that you keep the selected cell indexPaths in filteredMessageIDforSwipe
- so you should iterate over that array and set the color for the cells with the corresponding index paths. It's important to set the default cell background color before you call the for loop.
Here is my sample ViewController:
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
@IBOutlet weak var tableView: UITableView!
let userDefaults = UserDefaults.standard
let testArray = ["Test1", "Test2", "Test3", "Test4", "Test5", "Test6", "Test7", "Test8", "Test9", "Test10", "Test11", "Test12", "Test13", "Test14", "Test15", "Test16", "Test17", "Test18", "Test19", "Test20"]
var selectedCellIndexPaths = [Int]()
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
if let values = userDefaults.value(forKey: "IndexPathsArray") as? Array<Int> {
selectedCellIndexPaths = values
}
print("selected", selectedCellIndexPaths)
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return testArray.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
cell.textLabel?.text = testArray[indexPath.row]
cell.backgroundColor = UIColor.white
for indx in selectedCellIndexPaths {
if indexPath.row == indx {
cell.backgroundColor = UIColor.red
}
}
return cell
}
func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]? {
let cell = tableView.cellForRow(at: indexPath)
let action = UITableViewRowAction(style: .default, title: "Mark Read") { (action, indexPath) in
cell?.backgroundColor = UIColor.red
self.selectedCellIndexPaths.append(indexPath.row)
self.userDefaults.set(self.selectedCellIndexPaths, forKey: "IndexPathsArray")
print(self.selectedCellIndexPaths)
}
return [action]
}
}
Let me know if you need further help!
Upvotes: 1
Reputation: 298
When you scroll down, your tableview reuse previously loaded cells, keeping the background color, you need to reset the color to default in:
override func prepareForReuse() {
// set the color to default
}
Upvotes: 0
Reputation: 131
Try this:
var selectedIndexPath: [IndexPath]?
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
selectedIndexPath.append(indexPath)
tableView.reloadData()}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
cell.contentView.backgroundColor = selectedIndexPath?.contains(indexPath) ? UIColor(hexString: "#efefef") : UIColor.clear}
Upvotes: 0