Search code examples
swiftxcodeuitableviewuikit

Hidden TableViews cells behind visible cells that are not represented by models


I'm currently building an chatbot application for a client. The chat is implemented using a UITableView with custom cells. A text message from the bot is build as a custom UITableViewCell with a UITextView( because it needs to be clickable for links, otherwise I would have used a UILabel) wrapped inside a UIView container to draw a bubble around it. Also this container view has a shadow to please the designer :P

Everytime a new message from the bot is received this message is delayed for a fraction of time so that the user can read the message before. In that time a so called WaitingMessageTableViewCell is shown at the index the new cell will be arriving. After the time has expired the WaitingMessageTableViewCell is removed from the model and then from the tableView and the new cell is inserted.

            tableView.beginUpdates()
            if let insertRows = insertRows {
                tableView.insertRows(at: insertRows, with: .none)
            }
            if let deleteRows = deleteRows {
                tableView.deleteRows(at: deleteRows, with: .none)
            }
            if let reloadRows = reloadRows {
                tableView.reloadRows(at: reloadRows, with: .none)
            }
            tableView.endUpdates()

The problem was found by the designer saying that when the WaitingMessageTableViewCell is about to appear the shaddow of the cell before is getting stronger for a fraction of time. I debugged into it and found that before the WaitingMessageTableViewCell is inserted it is appearing behind the cell before, which makes no sense to me.(is it the reuse mechanism?!) Now comes the very strange part: I used the UI Analyzer build into Xcode and saw that there are hidden cells behind the visible cells which are not represented by the models.

(lldb) po tableView.subviews
▿ 5 elements
  ▿ 0 : <CHAT.WaitingMessageTableViewCell: 0x7fdef286a600; baseClass = UITableViewCell; frame = (0 395.5; 375 40); hidden = YES; opaque = NO; autoresize = W; layer = <CALayer: 0x600003952d80>>
  ▿ 1 : <CHAT.AgentMessageTableViewCell: 0x7fdef2861e00; baseClass = UITableViewCell; frame = (0 294.5; 375 101); hidden = YES; opaque = NO; autoresize = W; layer = <CALayer: 0x6000039bf160>>
  ▿ 2 : <CHAT.AgentMessageTableViewCell: 0x7fdef3897600; baseClass = UITableViewCell; frame = (0 395.5; 375 101.5); opaque = NO; autoresize = W; layer = <CALayer: 0x6000039aba20>>
  ▿ 3 : <CHAT.AgentMessageTableViewCell: 0x7fdef382e200; baseClass = UITableViewCell; frame = (0 294.5; 375 101); opaque = NO; autoresize = W; layer = <CALayer: 0x600003955a00>>
  ▿ 4 : <CHAT.WelcomeTableViewCell: 0x7fdef2882c00; baseClass = UITableViewCell; frame = (0 0; 375 294.5); clipsToBounds = YES; autoresize = W; layer = <CALayer: 0x600003951160>>
(lldb) po tableView.visibleCells
▿ 3 elements
  ▿ 0 : <CHAT.WelcomeTableViewCell: 0x7fdef2882c00; baseClass = UITableViewCell; frame = (0 0; 375 294.5); clipsToBounds = YES; autoresize = W; layer = <CALayer: 0x600003951160>>
  ▿ 1 : <CHAT.AgentMessageTableViewCell: 0x7fdef382e200; baseClass = UITableViewCell; frame = (0 294.5; 375 101); opaque = NO; autoresize = W; layer = <CALayer: 0x600003955a00>>
  ▿ 2 : <CHAT.AgentMessageTableViewCell: 0x7fdef3897600; baseClass = UITableViewCell; frame = (0 395.5; 375 101.5); opaque = NO; autoresize = W; layer = <CALayer: 0x6000039aba20>>
(lldb) po AssistantDataManager.shared.cellModels
▿ 3 elements
  ▿ 0 : <WelcomeCellModel: 0x600002c80740>
  ▿ 1 : <SimpleAgentTextCellModel: 0x600001af0050>
  ▿ 2 : <AskQuestionCellModel: 0x600002c80400>

Also here is my cellForRows method:

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

        guard indexPath.row < AssistantDataManager.shared.cellModels.count else {
            return UITableViewCell()
        }
        let cellModel = AssistantDataManager.shared.cellModels[indexPath.row]

        if let cell = tableView.dequeueReusableCell(withIdentifier: cellModel.cellReuseIdentifier) as? AssistantActionBaseTableViewCell {
            //Quickfix for stronger shadow
            cell.backgroundColor = Colors.tableViewBackgroundColor

            cell.configure(with: cellModel)
            if let topContraint = cell.topContraint {
                topContraint.constant = calculateTopContraintSpacing(for: indexPath)
            }

            if let bottomContraint = cell.bottomContraint {
                bottomContraint.constant = 0
            }

            cell.selectionStyle = .none
            return cell
        }
        return UITableViewCell()
    }

Can anybody tell me why the new inserted WaitingMessageTableViewCell is first behind the cell above and then moves down and why is my tableView populated with much more cells then it needs to be?

Thank you very much in advance.


Solution

  • I've found out why the new cell is shown first behind the previous cell and then moves down. It's a combination of implicit animations and life cycle.

    Previously I inserted deleted and reloaded the cells all in one batch update. Because of the implicit animations the tableView decided to animate that change. But when I first insert and delete and after that reload the cells everything works as expected.

    tableView.performBatchUpdates({
        if let insertRows = insertRows {
            tableView.insertRows(at: insertRows, with: .none)
        }
        if let deleteRows = deleteRows {
            tableView.deleteRows(at: deleteRows, with: .none)
        }
    
    }) { (finished) in
        self.tableView.performBatchUpdates({
            if let reloadRows = reloadRows {
                self.tableView.reloadRows(at: reloadRows, with: .none)
            }
        }, completion: nil)
    }