0

I'm stuck in a performance problem in my chat-like UITableview. I've got three types of cells:

  • Text
  • Image/video
  • Audio

Currently I'm handling the dynamic cells height caching the size in a dictionary:

func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
    if let estimatedHeight = self.estimatedMessagesHeights[indexPath] {
        return estimatedHeight
    }
    return UITableViewAutomaticDimension
}

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

func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
    self.estimatedMessagesHeights.updateValue(cell.bounds.size.height, forKey: indexPath)
}

It's not working very well, maybe scrolling is not that laggy but when I scroll to the bottom of the UITableView using

self.tblChat.scrollToRow(at: indexPath, at: .bottom, animated: false)

my app freeze for an instant. I tried setting a static height for the cells and the freeze is not happening.

In cellForRowAtIndexPath I dequeue the correct xib for the type of message, set the title label used for the "day" and the spacing from the previous message based on the date. After that I call the configuration method of the cell where I format and display the message text or I show the remote/local image using SDWebImage (after the download if necessary).

let message = self.model.getMessage(atIndex: indexPath.row)
var cell: ChatMessageTableViewCell?

if message.isSender {
    if message.type == .audio {
        cell = self.tblChat.dequeueReusableCell(withIdentifier: self.audioSenderIdentifier, for: indexPath) as! ChatAudioSenderTableViewCell
    } else if message.type == .text {
        cell = self.tblChat.dequeueReusableCell(withIdentifier: self.senderIdentifier, for: indexPath) as! ChatMessageSenderTableViewCell
    } else {
        cell = self.tblChat.dequeueReusableCell(withIdentifier: self.mediaSenderIdentifier, for: indexPath) as! ChatMediaSenderTableViewCell
    }
} else {
    if message.type == .audio {
        cell = self.tblChat.dequeueReusableCell(withIdentifier: self.audioReceiverIdentifier, for: indexPath) as! ChatAudioReceiverTableViewCell
    } else if message.type == .text {
        cell = self.tblChat.dequeueReusableCell(withIdentifier: self.receiverIdentifier, for: indexPath) as! ChatMessageReceiverTableViewCell
    } else {
        cell = self.tblChat.dequeueReusableCell(withIdentifier: self.mediaReceiverIdentifier, for: indexPath) as! ChatMediaReceiverTableViewCell
    }
}

let previousMessage = self.model.getMessage(atIndex: indexPath.row-1)
cell.topLabel.text = self.model.getMessageDayTitle(message: message, previousMessage: previousMessage)
cell.cntTopSpacing.constant = self.model.getSpacingBetweenMessages(previousMessage: previousMessage, message: message)
cell.configure(withMessage: message, chat: self.model.chat)

cell?.delegate = self

return cell

Any suggestions on how can I handle correctly the cells height calculation?

1 Answers1

0

Caching 'estimatedHeightForRowAt' is almost meaningless in your case, it's called only when UITableView reloads (because it is a feature to improve loading time for UITableView, not for cells). There are a few solutions I can see:

  • avoid using automatic cell heights
  • calculate height for each cell manually (and return in 'heightForRowAt'); then cache those values in the dictionary, like you do it in the code sample for 'estimated height'. I've improved the FPS from 14 to stable 48-60 in my case (there's an extremely complicated layout).
  • if your fps is still bad, try to configure cells asynchronously when scrolling, and synchronously when tableview reloads.

Note: pay attention to recalculate heights when your cell content is changed