1

I have a TabelViewController. The data inside the TableViewCells is being updated with a high frequency (lets assume 10 Hz) via the tableview.reloadData(). This works so far. But when I scroll the TableView, the update is paused, until the user interaction ends. Also all other processes inside my app are paused. How can I fix this?

Here is an example TableViewController. If you run this on your emulator and check the output inside the debug area, you will notice, that not only the update of the graphics (here a label) is paused, but also the notifications, when you scroll and hold the tableview. This is also the case, if you have a process in another class.

It's interesting, that this blocking of processes is not the case if you interact with a MapView. What am I missing here?

import UIKit

class TableViewController: UITableViewController {
    
    var text = 0
    var timer = Timer()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        NotificationCenter.default.addObserver(self, selector: #selector(self.recieveNotification), name: NSNotification.Name(rawValue: "testNotificatiion"), object: nil)
        
        scheduledTimerWithTimeInterval()
    }
    
    func scheduledTimerWithTimeInterval(){
        timer = Timer.scheduledTimer(timeInterval: 1/10, target: self, selector: #selector(self.updateCounting), userInfo: nil, repeats: true)
    }
    
    @objc func updateCounting(){
        text += 1
        
        let userInfo = ["test": text]
        
        NotificationCenter.default.post(name: NSNotification.Name(rawValue: "testNotificatiion"), object: nil, userInfo: userInfo)
        
        tableView.reloadData()
    }
    
    @objc func recieveNotification(notification: Notification){
        
        if let userInfo = notification.userInfo! as? [String: Int]
        {
            if let recieved = userInfo["test"] {
                print("Notification recieved with userInfo: \(recieved)")
            }
        }
    }
    
    // MARK: - Table view data source
    
    override func numberOfSections(in tableView: UITableView) -> Int {
        return 1
    }
    
    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return 10
    }
    
    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        var cell: UITableViewCell?
        cell = tableView.dequeueReusableCell(withIdentifier: "rightDetail", for: indexPath)
        cell?.detailTextLabel?.text = String(text)

        return cell!
    }
}
Christian
  • 27
  • 4

1 Answers1

2

Timer's don't fire when a UITableView is being scrolled. Check out this answer here by Quinn The Eskimo.

Change your method to be like this and it'll work even while scrolling.

func scheduleMyTimer() {
    let t = Timer(timeInterval: 1.0, repeats: true) { _ in
        self.updateCounting()
    }
    RunLoop.current.add(t, forMode: RunLoop.Mode.common)
}

CoreLocation callbacks also aren't firing when a UITableView is being scrolled. Here's a fix for that.

From the documentation for CLLocationManagerDelegate:

Core Location calls the methods of your delegate object on the runloop from the thread on which you initialized CLLocationManager. That thread must itself have an active run loop, like the one found in your app’s main thread.

So I changed the CLLocationManager init from this:

class AppDelegate {
    var lm = CLLocationManager()
}

To this:

class AppDelegate {
    var lm: CLLocationManager!
    var thread: Thread!

    func didFinishLaunching() {
        thread = Thread {
            self.lm = CLLocationManager()

            Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { (_) in
                // empty on purpose
            }
        
            RunLoop.current.run()

            // If no input sources or timers are attached
            // to the run loop, the run() method exits immediately,
            // so we add a Timer just before the call.

        }
        thread.start()
    }
}

Then the delegate callbacks kept firing even while scrolling. This answer helped.

Peter Parker
  • 2,121
  • 1
  • 17
  • 23
  • Thanks, this works. In my real project the process is not a simple timer. How can I solve my issue, when the process is for example a motion update handler from CoreMotion? – Christian Jul 27 '21 at 07:09
  • Its also the same for location updates. I have a singleton locationManger and motionManger (because i don't want to start multiple instances of location manger and motion manager). When the app launches I start the managers in my appDelegate Class. In the tableViewControllers I implement the locationManagerDelegate and the corresponding methods. The delegate method is not excecuted, when I interact with my tableView. Its strange, because I thought, that the locationManager runs not on the main thread (which is, as I understand, blocked during the user interaction) – Christian Jul 27 '21 at 12:09
  • 1
    Hmm what API of CoreMotion are you using? Cuz I've tried `startAccelerometerUpdates(:_)` and it works fine for me, the callbacks keep firing even while scrolling. Both when `.main` is passed in as the queue parameter, and also when I pass in my own `OperationQueue` whose quality of service is set to background. – Peter Parker Jul 27 '21 at 16:29
  • 1
    I added a fix for `CoreLocation` – Peter Parker Jul 27 '21 at 17:17
  • Thank you very much for your effort. You helped me a lot. Have a nice day! – Christian Jul 27 '21 at 20:19
  • 1
    Oh you're welcome \\(^-^) Thanks, you too! – Peter Parker Jul 27 '21 at 20:30
  • @Christian Sry, I just noticed a major performance issue with the previous code that caused the CPU to run at 100%. I've tested the updated code and it works, and CPU remains low. My bad, never worked with `Thread` before, and didn't check performance the first time around. – Peter Parker Aug 02 '21 at 01:44