UITableViewAlertForLayoutOutsideViewHierarchy error: Warning once only (iOS 13 GM)

It happened to me because I registered the device for change orientation notification in the viewWillAppear(:) method. I moved the registration in the viewDidAppear(:) and Xcode it's not stopping at the breakpoint anymore.

What I can say is that layout changes might be run when the view is already visible...


Like @joe-h, I was getting this error and was also surprised as the unwind approach he shows is one used by lots of developers + is in some significant Apple iOS sample code.

The triggering line in my code (@joe-h, I'm guessing likely in yours, too) is a tableView.reloadRows at the selectedIndexPath (which is an unwrapped tableView.indexPathForSelectedRow):

tableView.reloadRows(at: [selectedIndexPath], with: .automatic)

Unfortunately commenting out the row isn't an option if you are unwinding after updating the value in an existing tableView row (which is an approach in the Apple FoodTracker tutorial mentioned above, as well as one used in Apple's Everyone Can Code series). If you don't reload the row(s) then your change won't show in the tableView. After commenting out the reload in the unwind, I added a viewDidAppear with the following code and this seems to fix things:

override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)
    if let selectedIndexPath = tableView.indexPathForSelectedRow {
        tableView.reloadRows(at: [selectedIndexPath], with: .automatic)
    }
}

I'd welcome comments on whether this is a sound approach, but for now, this seems to be working.


This warning can happen du to updating table view or collection view while it is not visible, for example when it is on the parent view controller. To solve that, first, I created a property in the view controller, containing the table view to check if the view controller is visible or not, as bellow:

var isVisible: Bool = false

override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)
    self.isVisible = true
}

override func viewDidDisappear(_ animated: Bool) {
    super.viewDidAppear(animated)
    self.isVisible = false
}

Then in the data source delegate, before reacting to changes, first check if the view controller is visible. If it was not, do not do any updates. For example

func controllerWillChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
    guard isVisible else { return }
    tableView.beginUpdates()
}

You should check that visibility before doing any changes in the tableView. For example, in case of NSFetchedResultsController, it must be done in all delegate callbacks which we have implemented.

UPDATE

I recently found that if you update the table view with animation false, even when it is not visible, there won't be any warnings.