TableView gets laggy with data

So I am facing a problem

A table view gets slower (responses slower to scrolling, tapping) after reloading/adding rows for a couple of times


So after the user logs in, the app downloads 10 "WorldMessages". It is loaded into this table view.

If the user scrolls down, it calls a function which loads more 10: loadOlderOwnWorldMessages()

Each cell has a tapGestureRecognizer + longPressGestureRecognizer

And I have to mention that If the user reloads the tableView, then it clears the data and loads only the first 10 WorldMessages again


The problem

I don't know why, but for example, if I reload the tableView 50 times and every time I do scroll down a bit or more, then the tableView gets slow.

Maybe because of the tap/long press gesture recognizers, or constraints?

The app looks like this:

enter image description here

Video after it gets laggy:

https://www.youtube.com/watch?v=65NkjS-Kz3M

(If I kill the app and I open it again, then it works smoothly again until I reload it a couple of times)

Code:

// It's not the whole code, I deleted lots of lines of it that were not important (like the reload functions, and etc

class ProfileViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, UITabBarDelegate {

    // Classes
    let handleData = HandleData()
    let handleResponses = HandleResponses()
    let worldMessagesFunctions = WorldMessagesFunctions()
    let profileFunctions = ProfileFunctions()

    // View Objects
    @IBOutlet var tableView : UITableView!

    // Variables
    var userWorldMessages = [WorldMessage]()
    var lastContentOffsetY : CGFloat?

    var currentSelectedWorldMessageIndexPath : IndexPath?

    // Main Code
    override func viewDidLoad() {
        super.viewDidLoad()

        userWorldMessages = ownWorldMessages.shared.worldMessages
    }

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
       return userWorldMessages.count
    }
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "ProfileWorldMessageCell", for: indexPath) as! ProfileWorldMessageCell

        let worldMessage = userWorldMessages[indexPath.row]

        cell.worldMessageData = worldMessage

        cell.messageLabel.text = worldMessage.message

        if let image = UIImage(named: "bubble") {
            let h = image.size.height / 2
            let w = image.size.width / 2
            cell.bubbleImageView.image = image
                .resizableImage(withCapInsets:
                    UIEdgeInsetsMake(h, w, h, w),
                                resizingMode: .stretch).withRenderingMode(.alwaysTemplate)

            cell.bubbleImageView.tintColor = appColors.worldMessageBubble

        }

        let calendar = NSCalendar.current
        let date = Date(timeIntervalSince1970: Double(worldMessage.leftTime))
        let dateFormatter = DateFormatter()
        dateFormatter.locale = Locale(identifier: "en_US")
        var dateFormat = "yyyy MMM dd"
        if calendar.isDateInToday(date) {
            // Today
            dateFormat = "HH:mm"
            dateFormatter.string(from: date)
        } else if (calendar.date(byAdding: .weekOfYear, value: -1, to: Date())! < date){
            // This last 7 days
            dateFormat = "EEEE HH:mm"
        } else if (calendar.date(byAdding: .month, value: -12, to: Date())! < date){
            // This year
            dateFormat = "MMM dd"
        } else {
            dateFormat = "yyyy MMM dd"
        }
        dateFormatter.dateFormat = dateFormat
        let strDate = dateFormatter.string(from: date)

        cell.timeLabel.text = strDate

        cell.commentsButton.setTitle("\(worldMessage.comments!) comments", for: .normal)
        cell.likesButton.setTitle("\(worldMessage.likes!) likes", for: .normal)

        // tapped
        let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(bubbleTappedHandler))
        cell.bubbleButton.addGestureRecognizer(tapGestureRecognizer)
        cell.bubbleButton.isUserInteractionEnabled = true

        // long pressed
        let longPressGestureRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(bubbleLongPressHandler))
        longPressGestureRecognizer.minimumPressDuration = 0.5
        cell.addGestureRecognizer(longPressGestureRecognizer)
        cell.isUserInteractionEnabled = true

        return cell
    }
    var cellHeights: [IndexPath : CGFloat] = [:]
    func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
        cellHeights[indexPath] = cell.frame.size.height

        if indexPath.row == UserWorldMessagesStore.shared.worldMessages.count - 1 && userWorldMessagesCanLoadMore == true {
            loadOlderOwnWorldMessages()
        }
    }
    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        return UITableViewAutomaticDimension
    }
    func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
        if cellHeights[indexPath] != nil {
            return CGFloat(Float(cellHeights[indexPath] ?? 0.0))
        }
        else {
            return UITableViewAutomaticDimension
        }
    }
    func scrollViewDidScroll(_ scrollView: UIScrollView) {
        if (lastContentOffsetY != nil){
            tableView.setContentOffset(CGPoint(x: 0, y: lastContentOffsetY!), animated: false)
        }
    }



    func loadOlderOwnWorldMessages(){
        profileFunctions.loadOlderOwnWorldMessages(startIndex: (userWorldMessages.count)) { response, newUserWorldMessages in
            if let response = response {
                if response.type == 1 {
                    DispatchQueue.main.async(execute: {() -> Void in
                        if newUserWorldMessages != nil {

                            var insertRows : [IndexPath] = []
                            var fromIndex = UserWorldMessagesStore.shared.worldMessages.count - 1
                            for worldMessage in newUserWorldMessages! {
                                UserWorldMessagesStore.shared.worldMessages.append(worldMessage)
                                insertRows.append(IndexPath(row: fromIndex, section: 1))
                                fromIndex += 1
                            }
                            self.userWorldMessages = UserWorldMessagesStore.shared.worldMessages

                            self.tableView.beginUpdates()
                            self.tableView.insertRows(at: insertRows, with: .automatic)
                            self.tableView.endUpdates()


                        }

                        })
                } else {
                    DispatchQueue.main.async(execute: {() -> Void in
                        self.handleResponses.displayError(title: response.title, message: response.message)
                    })
                }
            }
        }
    }

    @objc func bubbleTappedHandler(sender: UITapGestureRecognizer, should: Bool) {
        let touchPoint = sender.location(in: self.tableView)
        if let indexPath = tableView.indexPathForRow(at: touchPoint) {
            if indexPath == currentSelectedWorldMessageIndexPath {
                // it was already selected, so deselect it

                deselectCurrentSelectedWorldMessage()
            } else {
                // select new one, deselect old one if was selected

                if (currentSelectedWorldMessageIndexPath != nil){
                    deselectCurrentSelectedWorldMessage()
                }

                selectWorldMessage(indexPath: indexPath)
            }
        }
    }
    func selectWorldMessage(indexPath: IndexPath) {

        tableView.selectRow(at: indexPath, animated: true, scrollPosition: .none)
        currentSelectedWorldMessageIndexPath = indexPath

        if let cell = tableView.cellForRow(at: indexPath) as? ProfileWorldMessageCell {

            // Change some constraints
            cell.messageLabelTopConstraint.constant = 14
            cell.messageLabelBottomConstraint.constant = 14
            UIView.animate(withDuration: 0.15, animations: {
                self.view.layoutIfNeeded()
                self.tableView.beginUpdates()
                self.tableView.endUpdates()
            }, completion: nil)
        }
    }
    @objc func deselectCurrentSelectedWorldMessage(){
        UIMenuController.shared.setMenuVisible(false, animated: true)
        if (currentSelectedWorldMessageIndexPath == nil){
            return
        }
        let indexPath = currentSelectedWorldMessageIndexPath!

        if let cell = tableView.cellForRow(at: indexPath) as? ProfileWorldMessageCell {

            // Change back some constraints
            cell.messageLabelTopConstraint.constant = 10
            cell.messageLabelBottomConstraint.constant = 10
            UIView.animate(withDuration: 0.15, animations: {
                self.view.layoutIfNeeded()
                self.tableView.beginUpdates()
                self.tableView.endUpdates()
            }, completion: nil)
        }

        currentSelectedWorldMessageIndexPath = nil
    }

    @objc func bubbleLongPressHandler(sender: UILongPressGestureRecognizer, should: Bool) {
        // Show options like copy, delete etc.
    }



}

Upvotes: 0

Views: 2425

Answers (1)

mag_zbc
mag_zbc

Reputation: 6982

There are couple of things that are very wrong with your cellForRow function:

  • the cells are reused, and you are ignoring that fact. You are creating new instances of gesture recognizers each time cellForRow is called, and when cell is reused you are adding yet another one. That means that if your cell had been reused for 50th time, it will have 50 gesture recognizers added. You should create your gesture recognizers only once
  • You create a DateFormatter and set its dateFormat property each time, and that is actually really expensive process. Instead you should have a static instance of formatter for each date format

Upvotes: 6

Related Questions