14

I want to scroll to a given index (self.boldRowPath), but when I debug scrollToRow is performed before reloadData().

How to know reloadData has finished ?

func getAllTimeEvent()  {
    self.arrAllTimeEvent = ModelManager.getInstance().getAllTimeEvent(from: self.apportmentDateFrom, to: self.apportmentDateTo)
    self.tblTimeEvent.reloadData()
    self.tblTimeEvent.scrollToRow(at: self.boldRowPath ?? [0,0], at: .top, animated: true)
 }

Any help will be much appreciated.

Rajesh Loganathan
  • 11,129
  • 4
  • 78
  • 90
Harshil Kotecha
  • 2,846
  • 4
  • 27
  • 41
  • Make sure you see [How to tell when UITableVIew has completed ReloadData?](https://stackoverflow.com/questions/16071503/how-to-tell-when-uitableview-has-completed-reloaddata). It's in objective-c but the answers provided there are more complete... – mfaani Dec 31 '17 at 01:07
  • 1
    @Honey this is ask before 8 month ago and i did't find this link bcoz there is no tag for swift so i did't reach this link throw google . – Harshil Kotecha Dec 31 '17 at 04:40
  • Understood, exactly because this question is marked as Swift...is the reason I didn't mark it as duplicate :) – mfaani Dec 31 '17 at 08:50

6 Answers6

29

Try doing this, it should work:

self.tblTimeEvent.reloadData()
DispatchQueue.main.async(execute: {
    self.tblTimeEvent.scrollToRow(at: self.boldRowPath ?? [0,0], at: .top, animated: true)
})

This will execute the scrollToRow on the main thread, that means after the reloadData is done (because it is on the main thread)

Farid Al Haddad
  • 833
  • 9
  • 19
  • ummm. shouldn't the tableview reload itself on main thread ALL the time? Isn't it a UI update?! – mfaani Dec 25 '17 at 00:18
  • 3
    When you call reloadData(), it reloads itself on the main thread so you cannot scroll the tableview while it is reloading. You call it on the main thread so it is queued to begin right after the reloadData is done. @Honey – Farid Al Haddad Dec 26 '17 at 15:37
5

As explained in this answer, the reload of the UITableView happens on the next layout run (usually, when you return control to the run loop).

So, you can schedule your code after the next layout by using the main dispatch queue. In your case:

    func getAllTimeEvent()  {
        self.arrAllTimeEvent = ModelManager.getInstance().getAllTimeEvent(from: self.apportmentDateFrom, to: self.apportmentDateTo)
        self.tblTimeEvent.reloadData()
        DispatchQueue.main.async {
            self.tblTimeEvent.scrollToRow(at: self.boldRowPath ?? [0,0], at: .top, animated: true)
        }
    }

You can also force the layout by manually calling layoutIfNeeded. But this is generally not a good idea (the previous option is the best):

    func getAllTimeEvent()  {
        self.arrAllTimeEvent = ModelManager.getInstance().getAllTimeEvent(from: self.apportmentDateFrom, to: self.apportmentDateTo)
        self.tblTimeEvent.reloadData()
        self.tblTimeEvent.layoutIfNeeded()
        self.tblTimeEvent.scrollToRow(at: self.boldRowPath ?? [0,0], at: .top, animated: true)
    }
Community
  • 1
  • 1
Julien Quere
  • 2,407
  • 16
  • 21
2

you can make sure reload is done using...

In Swift 3.0 + we can create a an extension for UITableView with a escaped Closure like below :

extension UITableView {
    func reloadData(completion: @escaping () -> ()) {
        UIView.animate(withDuration: 0, animations: { self.reloadData()})
        {_ in completion() }
    }
}

And Use it like Below where ever you want :

Your_Table_View.reloadData {
   print("reload done")
 }

hope this will help to someone. cheers!

caldera.sac
  • 4,918
  • 7
  • 37
  • 69
1

You can make a pretty solid assumption that a UITableView is done reloading when the last time tableView(_:willDisplay:forRowAt:) is called for the visible sections on the screen.

So try something like this (for a tableView where the rows in section 0 take up the available space on the screen):

override func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
  let lastRowIndex = tableView.numberOfRows(inSection: 0)
  if indexPath.row == lastRowIndex - 1 {
    // tableView done reloading
    self.tblTimeEvent.scrollToRow(at: self.boldRowPath ?? [0,0], at: .top, animated: true)
  }
}
Andy Obusek
  • 12,614
  • 4
  • 41
  • 62
  • 2
    tableview:willdisplay:forrowat: does not get called for all items in the tableview.. but only on the visible rows.. so if tableview is scrolled up, this will not work... – Dany Balian Jul 03 '17 at 23:32
0

You can use CATransaction for that

CATransaction.begin()
CATransaction.setCompletionBlock {
    // Completion
}
tableView.reloadData()
CATransaction.commit()
-3
tableView.reloadData { [weak self] in
    self?.doSomething()
}
Giang
  • 3,553
  • 30
  • 28