skip to Main Content

I have an array of items and TableView to display them. Item consists of 4 properties. And there is a method which randomly generate items. Initialy, array is empty but in viewDidLoad i have method, which append 100 items to array with delay about 1 second. Array appends until its count about 1_000_000 items. When I start app it freeze.

It’s my tableView methods:

func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return model.count
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: DealCell.reuseIidentifier, for: indexPath) as! DealCell
        guard model.count != 0 else {
            cell.instrumentNameLabel.text = "no data"
            cell.priceLabel.text = "no data"
            cell.amountLabel.text = "no data"
            cell.sideLabel.text = "no data"
            return cell
        }
        cell.instrumentNameLabel.text = "(model[indexPath.row].instrumentName)"
        cell.priceLabel.text = "(model[indexPath.row].price)"
        cell.amountLabel.text = "(model[indexPath.row].amount)"
        cell.sideLabel.text = "(model[indexPath.row].side)"
        return cell
    }

Function to append array:

server.subscribeToUpdate { updates in
            self.model.append(contentsOf: updates)
            self.tableView.reloadData()
        }

How to solve this problem? May be to make some counter for "numberOfRowsInSection" equal to 100 and then when scroll to 100th item increase it to 200 etc. Or is there a more concise solution?

Tried to use ReusableCell, but nothing happened.

2

Answers


  1. The code below should solve your problem by unblocking the main thread using DispatchQueue

    server.subscribeToDeals { deals in
           self.model.append(contentsOf: deals)
           DispatchQueue.main.async {
              self.tableView.reloadData()
           } 
        }
    
    Login or Signup to reply.
  2. I don’t know what you are really trying to do here… your code seems like it’s just doing a "stress test" or something.

    However, to try and help you understand why your app is "freezing" —

    Your for i in 0...dealsCount { loop will be running very fast. As in maybe 1 or 2 thousandths of a second per 100 iterations. If you are calling .reloadData() every 100th time through the loop, your code is trying to update the UI pretty much constantly. That means your UI will appear "frozen."

    Here’s an example that you may find helpful…

    First, we’ll add a "status label" and a progress view to display the progress as we generate Deals. We’ll update those every 1000th new Deal created.

    Second, as we generate new Deals, we’ll append them directly to the controller’s var model: [Deal] = [] array (instead of building new arrays and appending them periodically).

    Third, we’ll only call .reloadData():

    • at the first 1,000 Deals
    • then at every 100,000 Deals
    • and finally after we’ve generated all 1-million+

    As I said, I don’t know what you’re really doing … but it is unlikely someone would scroll through the first 1,000 rows before we add the next 100,000 records.

    However, you’ll find that you can scroll the table while the records are being generated… and, after they’ve all been generated, selecting any cell will jump to the 900,000th row.

    Here’s how it looks:

    enter image description here enter image description here

    enter image description here enter image description here

    Deal Struct

    struct Deal {
        var id: Int64 = 0
        var dateModifier: Date = Date()
        var instrumentName: String = ""
        var price: Double = 0
        var amount: Double = 0
    }
    

    Simple multi-line label cell class

    class DealCell: UITableViewCell {
        let theLabel = UILabel()
        override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
            super.init(style: style, reuseIdentifier: reuseIdentifier)
            commonInit()
        }
        required init?(coder: NSCoder) {
            super.init(coder: coder)
            commonInit()
        }
        func commonInit() {
            theLabel.numberOfLines = 0
            theLabel.backgroundColor = UIColor(white: 0.95, alpha: 1.0)
            theLabel.translatesAutoresizingMaskIntoConstraints = false
            contentView.addSubview(theLabel)
            let g = contentView.layoutMarginsGuide
            NSLayoutConstraint.activate([
                theLabel.topAnchor.constraint(equalTo: g.topAnchor),
                theLabel.leadingAnchor.constraint(equalTo: g.leadingAnchor),
                theLabel.trailingAnchor.constraint(equalTo: g.trailingAnchor),
                theLabel.bottomAnchor.constraint(equalTo: g.bottomAnchor),
            ])
        }
        func fillData(_ aDeal: Deal) {
            let i = aDeal.id
            let d = aDeal.dateModifier
            let nm = aDeal.instrumentName
            let p = String(format: "%0.2f", aDeal.price)
            let a = String(format: "%0.2f", aDeal.amount)
            theLabel.text = "(i): (nm)nPrice: (p) / Amount: (a)n(d)"
        }
    }
    

    Demo view controller

    class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
        
        var model: [Deal] = []
        
        let instrumentNames: [String] = [
            "accordion",
            "acoustic guitar",
            "bagpipes",
            "banjo",
            "bass guitar",
            "bongo drums",
            "bugle",
            "cello",
            "clarinet",
            "cymbals",
            "drums",
            "electric guitar",
            "flute",
            "French horn",
            "harmonica",
            "harp",
            "keyboard",
            "maracas",
            "organ",
            "pan flute (pan pipes)",
            "piano",
            "recorder",
            "saxophone",
            "sitar",
            "tambourine",
            "triangle",
            "trombone",
            "trumpet",
            "tuba",
            "ukulele",
            "violin",
            "xylophone",
        ]
        
        let tableView = UITableView()
        let progressView = UIProgressView()
        let statusLabel = UILabel()
        
        override func viewDidLoad() {
            super.viewDidLoad()
    
            [statusLabel, progressView, tableView].forEach { v in
                v.translatesAutoresizingMaskIntoConstraints = false
                view.addSubview(v)
            }
            
            let g = view.safeAreaLayoutGuide
            NSLayoutConstraint.activate([
    
                statusLabel.topAnchor.constraint(equalTo: g.topAnchor, constant: 8.0),
                statusLabel.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 8.0),
                statusLabel.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -8.0),
                
                progressView.topAnchor.constraint(equalTo: statusLabel.bottomAnchor, constant: 8.0),
                progressView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
                progressView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0),
                
                tableView.topAnchor.constraint(equalTo: progressView.bottomAnchor, constant: 8.0),
                tableView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 0.0),
                tableView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: 0.0),
                tableView.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: 0.0),
    
            ])
    
            tableView.register(DealCell.self, forCellReuseIdentifier: "c")
            tableView.dataSource = self
            tableView.delegate = self
            
            statusLabel.textAlignment = .center
            statusLabel.textColor = .systemRed
            
            subscribeToDeals()
        }
        
        func updateProgress(currentCount: Int64, futureCount: Int64) {
            
            // update the status Lable and progress bar
            statusLabel.text = "Generated (currentCount) of (futureCount)"
            progressView.progress = Float(currentCount) / Float(futureCount)
    
            // only reload the table if we're at
            //  the first 1_000, or
            //  every 100_000, or
            //  we're finished generating Deals
            
            // if it's the first update
            if currentCount == 1_000 {
                tableView.reloadData()
            }
            // else if we're at an even 100_000
            else if currentCount % 100_000 == 0 {
                tableView.reloadData()
            }
            // else if we've generated all
            else if currentCount == futureCount {
                tableView.reloadData()
            }
            
        }
    
        func subscribeToDeals() {
            
            let bkgQueue = DispatchQueue(label: "subscribing", qos: .background)
            
            bkgQueue.async{
                
                let dealsCount = Int64.random(in: 1_000_000..<1_001_000)
    
                for i in 0...dealsCount {
                    
                    let currentTimeStamp = Date().timeIntervalSince1970
                    let timeStampRandomizer = Double.random(in: 50_000...50_000_000)
                    let deal = Deal (
                        id: i,
                        dateModifier: Date(timeIntervalSince1970: Double.random(in: currentTimeStamp - timeStampRandomizer...currentTimeStamp)),
                        instrumentName: self.instrumentNames.shuffled().first!,
                        price: Double.random(in: 60...70),
                        amount: Double.random(in: 1_000_000...50_000_000)
                    )
                    
                    // append directly to data
                    self.model.append(deal)
                    
                    // if we're at a 1_000 point
                    if i % 1_000 == 0 {
                        DispatchQueue.main.async {
                            self.updateProgress(currentCount: i, futureCount: dealsCount)
                        }
                    }
    
                }
    
                // we've generated all deals
                DispatchQueue.main.async {
                    self.updateProgress(currentCount: dealsCount, futureCount: dealsCount)
                }
    
                print("Done generating (dealsCount) Deals!")
            }
            
        }
    
        func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
            return model.count
        }
        func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
            let c = tableView.dequeueReusableCell(withIdentifier: "c", for: indexPath) as! DealCell
            c.fillData(model[indexPath.row])
            return c
        }
        func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
            if model.count > 1_000_000 {
                tableView.scrollToRow(at: IndexPath(row: 900_000, section: 0), at: .middle, animated: false)
            }
        }
    }
    

    Note: this is Example Code Only!!! It is not intended to be, and should not be considered to be, "production ready."

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search