1
votes

In our app we populate a table view dynamically from a server.

When scrolling it the table lags.

So I created a Dispatch queue:

    var queue = DispatchQueue(label: "myqueue")

Then in tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell method I did:

    queue.async {
            DispatchQueue.main.async {
                cell.placeIconView.layer.cornerRadius = cell.placeIconView.frame.width / 2
                cell.placeIconView.clipsToBounds = true
            }
            
            let poiImgs = HttpResponseHolder.shared.POIS[indexPath.row].imagesUrls
            
            if poiImgs!.count > 0 {
                let url = URL(string: poiImgs![0])
                
                let data = try? Data(contentsOf: url!)
                DispatchQueue.main.async {
                    cell.placeIconView.image = UIImage(data: data!)
                }
            }
            
            if poiImgs?.count == 0 && HttpResponseHolder.shared.POIS[indexPath.row].detailsOnWeb == false {
                
                DispatchQueue.main.async {
                    cell.placeIconView.image = UIImage(named: "img_poi_placeholder")
                }
            }
            
            if poiImgs?.count == 0 && HttpResponseHolder.shared.POIS[indexPath.row].detailsOnWeb == true {
                
                DispatchQueue.main.async {
                    cell.placeIconView.image = UIImage(named: "url_img_placeholder")
                }
            }
            
            DispatchQueue.main.async {
                cell.dotImgView.isHidden = true
            }
            
            if self.showCrowdingState == true {
                let crowdIndex = HttpResponseHolder.shared.POIS[indexPath.row].crowdingLevel
                
                DispatchQueue.main.async {
                    cell.dotImgView.isHidden = false
                }
                
                switch crowdIndex {
                case 1:
                    // green
                    DispatchQueue.main.async {
                        cell.dotImgView.image = UIImage(named: "greenDot")
                    }
                case 2:
                    // yellow
                    DispatchQueue.main.async {
                        cell.dotImgView.image = UIImage(named: "yellowDot")
                    }
                case 3:
                    // green
                    DispatchQueue.main.async {
                        cell.dotImgView.image = UIImage(named: "redDot")
                    }
                default:
                    break
                }
            } else {
                DispatchQueue.main.async {
                    cell.dotImgView.image = nil
                }
            }
        }

As you can see I put my cell configuration within a background thread (and pushed back the UI updates in the Main thread).

This fixed the lag but when I scroll the table view load its content with a delay.

Did I mistake something?

1
Some general remarks: cellForRow will execute on the main-queue. You are switching queues a lot inside of it, which will also have an affect on speed. As a general approach, you should either have the models to populate the cells available before you display cells (and display a loading indicator until enough models are ready to populate the view port) or you should allow cells to load their own models asynchronously after they have already been displayed. There are many frameworks and architectures or libraries that allow such an approach, such as RxSwift + MVVM or IGListKit.erik_m_martens

1 Answers

0
votes

You can buffer ui update task and at the end you can run on main queue with single dispatch statement.

queue.async {
    typealias Task = () -> Void
    var operations:[Task] = []

    operations.append {
        cell.placeIconView.layer.cornerRadius = cell.placeIconView.frame.width / 2
        cell.placeIconView.clipsToBounds = true
    }
    operations.append {
        cell.placeIconView.image = UIImage(data: data!)
    }
    operations.append {
        ...
    }
    
    DispatchQueue.main.async {
        operations.forEach({ $0() })
        operations.removeAll()
    }
}