0

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]()
    }

}
nm1213
  • 99
  • 9
  • in cellforrow have your default color for all cells and change the background color for the one selected. Since your view shows only 7 cell at start they look ok.. but once you start scrolling you will have a problem. – Yogesh Tandel Jan 29 '19 at 10:05
  • This may not be feasible because I’m changing the background colour to track read/unread posts in the tableview. I could have more than 7 read or unread posts. – nm1213 Jan 29 '19 at 10:07
  • I suppose I can flip the logic and change the colour only for unread posts, which then become “white” when marked as read... white being the default cell colour anyways. That would work. – nm1213 Jan 29 '19 at 10:09
  • yes.. so have the condition for read and unread in cellforrow and set the backgroundcolor color – Yogesh Tandel Jan 29 '19 at 10:09
  • [First Question](https://stackoverflow.com/questions/43487865/dont-reuse-cell-in-uitableview) Second qusetion IndexPath (row, section) – Bill Jan 29 '19 at 10:09
  • Thanks Bill. I don’t want to keep too many cells in memory for obvious reasons. You mention indexPath(row, section). How would I specify this dynamically, so that only one row ever changes colour? – nm1213 Jan 29 '19 at 10:21
  • Hi, I've edited my question to reflect the fact that I don't want the cell colour to change on select, but rather on swipe. Please have a look. – nm1213 Jan 29 '19 at 12:49

4 Answers4

1

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!

Community
  • 1
  • 1
lajosdeme
  • 2,189
  • 1
  • 11
  • 20
  • Thanks! Will try this within an hour and give feedback. – nm1213 Jan 29 '19 at 10:10
  • Would this work if I’m changing background colour for cells to track read/unread status? The full code allows a cell swipe, changes the colour, and writes the cell key value to a file. The next time the table is generated, the file is read, and the cell colours are changed if the key exists in the file. – nm1213 Jan 29 '19 at 10:13
  • Hi, I've edited my question to reflect the fact that I don't want the cell colour to change on select, but rather on swipe. Please have a look. – nm1213 Jan 29 '19 at 12:49
  • Idem... thanks. I tried this as you suggested. One difference is that I have another if statement where filteredMessageIDforSwipe.count > 0. I added your suggestion of the else statement after this second if statement. The rest is as you suggested. However, this did not work. – nm1213 Jan 29 '19 at 13:27
  • Also edited my question to show the entire function. – nm1213 Jan 29 '19 at 13:37
  • @nm1213 Yes, thank you. Will possibly have time to take a deeper look into it later in the day. Meanwhile, have you seen this post? I don't know whether this will help or not but seems relevant: https://stackoverflow.com/questions/18958883/change-background-color-of-cell-on-swipe-to-delete – lajosdeme Jan 29 '19 at 13:39
  • Thank you so much for your help! I’ll have a look at your post now. – nm1213 Jan 29 '19 at 13:40
  • @nm1213 I just updated my answer with a new solution. – lajosdeme Jan 29 '19 at 21:12
  • Thanks Idem - it’s 6am here now, so will try this in a couple of hours. – nm1213 Jan 29 '19 at 22:02
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/187532/discussion-between-nm1213-and-ldem). – nm1213 Jan 30 '19 at 00:38
0

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}
Mahak Mittal
  • 121
  • 1
  • 10
0

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
Ashvini
  • 342
  • 3
  • 11
  • Hi, I've edited my question to reflect the fact that I don't want the cell colour to change on select, but rather on swipe. Please have a look. – nm1213 Jan 29 '19 at 12:49
  • Hi, just copy the code of didSelectRow method and paste it into editActionsForRowAt method. – Ashvini Jan 29 '19 at 12:56
  • I tried that... didn’t work. Will show the code shortly. – nm1213 Jan 29 '19 at 12:58
  • Edited the question above to show you what I tried. – nm1213 Jan 29 '19 at 13:09
  • I have updated my answer, so can you please try this updated method? – Ashvini Jan 29 '19 at 13:13
  • Hi, for reference I've copied the entire contents for editActionsForRowAt. Action 2 and Action 5 are relevant. – nm1213 Jan 29 '19 at 13:16
  • Hi Ashvini, I tried the edited code, and I get "Expression resolves to an unused function" at: self.Nomination2TableView.beginUpdates and self.Nomination2TableView.endUpdates – nm1213 Jan 30 '19 at 00:56
  • I fixed this by adding () at the end of .beginUpdates and .endUpdates as follows: .beginUpdates() and .endUpdates() – nm1213 Jan 30 '19 at 00:59
0

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
}
zheck
  • 298
  • 3
  • 11
  • and of course, you have to specify the cell's color in cellForRow if needed – zheck Jan 29 '19 at 11:31
  • Hi, I've edited my question to reflect the fact that I don't want the cell colour to change on select, but rather on swipe. Please have a look. I'm guessing I need to page out the tableview to display a certain number of cells per page, and then reload and reset the colours for the next page before displaying the relevant data. Does this make sense? – nm1213 Jan 29 '19 at 12:50