Exception… Attempted to access the table view's visibleCells while they were in the process of being updated, which is not allowed

On iPadOS and iOS13 on a call to visibleCells on a UITableView ( on code which works fine on iOS9 to iOS12 ) I get an exception with the message


Attempted to access the table view's visibleCells while they were in the process of being updated, which is not allowed.


Searching on the web gives me no hits for this exact message, so I suspect the message itself is new or changed.



Now the general gist of the message is fairly clear, but the specifics and but how one is expected to avoid this in a multithreaded application are far from clear.


For example ...what sort of changes qualify,


For example ... the user may rotate the device, a redisplay may be caused by incoming data. Are these now to be expected to crash your application?


Maybe there is a new threading model / usage requirement that I don't know about, yet.


Synchronising on the UITableView itself doesn't help.


What am I missing



And Yes, I can avoid the problem by catching the error and backing off until I succeed. But the hit on performance is eyewatering. And it's horrible.

Accepted Reply

This is a new exception in iOS 13 that UITableView will raise in order to prevent and proactively alert you of a situation that would previously cause undefined behavior and a variety of strange, seemingly unrelated, and hard-to-debug issues (including crashes).


What is happening here is that UITableView is in the middle of asking its dataSource to return a cell for each visible row and is configuring the properties of the returned cells so they can be displayed. And in the middle of this updating -- most likely inside a callback from the table view itself about a specific row such as tableView(_:cellForRowAt:) tableView(_:canEditRowAt:), etc -- your code is asking the table view to return the visibleCells. This is obviously problematic, because UITableView is right in the middle of preparing those cells, so it cannot possibly return a meaningful answer.


The fix for this is to look at where you are calling visibleCells in the backtrace when this exception is raised, and then do one of two things:


Option 1: Move the usage of visibleCells to a better place, so that you aren't asking for the visibleCells from some place that is called during the process of creating/configuring/updating those same cells. A great place to ask for the visible cells is after the table view lays out, so for example if the table view is the view of a view controller you can use viewDidLayoutSubviews(), or in a subclass of UITableView do it after calling super.layoutSubviews().


Option 2: Depending on what you're actually trying to do, you might be able to skip using visibleCells altogether. For example, you might be able to leverage the callbacks tableView(_:willDisplay:forRowAt:) and tableView(_:didEndDisplaying:forRowAt:) to track when cells are visible instead.


Another place you may hit this is if you are using Key-Value Observation to observe the table view's contentOffset directly, and are asking for the visibleCells every time that changes. You should use the UIScrollView delegate method scrollViewDidScroll(_:) instead, or possibly a different approach altogether (such as the suggestion in Option 2 above).


If you are hitting this exception and you think you are requesting the visibleCells from a location that should be valid/allowed, please share the backtrace when you hit this exception and details about what you're trying to do.

Replies

I have the same problem with IOS 13 but no solution.

UITableView beginUpdates / end updates is not documented as a locking mechanism but one of grouping updates for smooth animation.


Despite that, it appears that it does some locking.


Although my code is making no changes, accessing the value of visibleCells, wrapping the code in a beginUpdates / endUpdates pair allows it to complete without the "Attempted to access the table view's visibleCells while they were in the process of being updated" error.

I have posted a reply above but am replying separately to your "same problem" because I would be interested to know whether my solution solves the problem for you, since it relies on a reasonable but undocumented side effect.

This is a new exception in iOS 13 that UITableView will raise in order to prevent and proactively alert you of a situation that would previously cause undefined behavior and a variety of strange, seemingly unrelated, and hard-to-debug issues (including crashes).


What is happening here is that UITableView is in the middle of asking its dataSource to return a cell for each visible row and is configuring the properties of the returned cells so they can be displayed. And in the middle of this updating -- most likely inside a callback from the table view itself about a specific row such as tableView(_:cellForRowAt:) tableView(_:canEditRowAt:), etc -- your code is asking the table view to return the visibleCells. This is obviously problematic, because UITableView is right in the middle of preparing those cells, so it cannot possibly return a meaningful answer.


The fix for this is to look at where you are calling visibleCells in the backtrace when this exception is raised, and then do one of two things:


Option 1: Move the usage of visibleCells to a better place, so that you aren't asking for the visibleCells from some place that is called during the process of creating/configuring/updating those same cells. A great place to ask for the visible cells is after the table view lays out, so for example if the table view is the view of a view controller you can use viewDidLayoutSubviews(), or in a subclass of UITableView do it after calling super.layoutSubviews().


Option 2: Depending on what you're actually trying to do, you might be able to skip using visibleCells altogether. For example, you might be able to leverage the callbacks tableView(_:willDisplay:forRowAt:) and tableView(_:didEndDisplaying:forRowAt:) to track when cells are visible instead.


Another place you may hit this is if you are using Key-Value Observation to observe the table view's contentOffset directly, and are asking for the visibleCells every time that changes. You should use the UIScrollView delegate method scrollViewDidScroll(_:) instead, or possibly a different approach altogether (such as the suggestion in Option 2 above).


If you are hitting this exception and you think you are requesting the visibleCells from a location that should be valid/allowed, please share the backtrace when you hit this exception and details about what you're trying to do.

I'm getting this is a consequence of making a UITextField inside one of the cells first responder in viewDidAppear.

Looking at the stack trace it seems to be UIKit that calls visibleCells.


Thread 1 Queue : com.apple.main-thread (serial)
#0 0x000000010e8eaf27 in objc_exception_throw ()
#1 0x000000010f55d8e8 in +[NSException raise:format:arguments:] ()
#2 0x000000010e253c29 in -[NSAssertionHandler handleFailureInMethod:object:file:lineNumber:description:] ()
#3 0x0000000113f863a8 in -[UITableView _visibleCellsUsingPresentationValues:] ()
#4 0x0000000113f93cc4 in -[UITableView _scrollFirstResponderCellToVisible:] ()
#5 0x0000000113fab37c in -[UITableView _adjustForAutomaticKeyboardInfo:animated:lastAdjustment:] ()
#6 0x00000001135af9fb in -[UIAutoRespondingScrollViewControllerKeyboardSupport _adjustScrollViewForKeyboardInfo:] ()
#7 0x00000001135afa77 in -[UIAutoRespondingScrollViewControllerKeyboardSupport _keyboardWillShow:] ()
#8 0x000000010f4886fc in __CFNOTIFICATIONCENTER_IS_CALLING_OUT_TO_AN_OBSERVER__ ()
#9 0x000000010f487b79 in _CFXRegistrationPost1 ()
#10 0x000000010f4878bf in ___CFXNotificationPost_block_invoke ()
#11 0x000000010f584973 in -[_CFXNotificationRegistrar find:object:observer:enumerator:] ()
#12 0x000000010f487226 in _CFXNotificationPost ()
#13 0x000000010e2b5108 in -[NSNotificationCenter postNotificationName:object:userInfo:] ()
#14 0x0000000113b8b044 in __59-[UIInputWindowController postStartNotifications:withInfo:]_block_invoke ()
#15 0x0000000113b8ab94 in -[UIInputWindowController postStartNotifications:withInfo:] ()
#16 0x0000000113b8e03e in __77-[UIInputWindowController moveFromPlacement:toPlacement:starting:completion:]_block_invoke.839 ()
#17 0x00000001142674a7 in +[UIView(UIViewAnimationWithBlocks) _setupAnimationWithDuration:delay:view:options:factory:animations:start:animationStateGenerator:completion:] ()
#18 0x00000001142678f7 in +[UIView(UIViewAnimationWithBlocks) _animateWithDuration:delay:options:animations:start:completion:] ()
#19 0x0000000113b8daae in -[UIInputWindowController moveFromPlacement:toPlacement:starting:completion:] ()
#20 0x0000000113b9324d in -[UIInputWindowController setInputViewSet:] ()
#21 0x0000000113b8d122 in -[UIInputWindowController performOperations:withAnimationStyle:] ()
#22 0x0000000113899b12 in -[UIInputResponderController setKeyWindowSceneInputViews:animationStyle:] ()
#23 0x000000011389926e in -[UIInputResponderController setInputViews:animationStyle:] ()
#24 0x000000011389a392 in -[UIInputResponderController setInputViews:animated:] ()
#25 0x000000011389a3f9 in -[UIInputResponderController setInputViews:] ()
#26 0x0000000113897e50 in -[UIInputResponderController _reloadInputViewsForKeyWindowSceneResponder:] ()
#27 0x0000000113897163 in -[UIInputResponderController _reloadInputViewsForResponder:] ()
#28 0x0000000113de334d in -[UIResponder(UIResponderInputViewAdditions) reloadInputViews] ()
#29 0x0000000113ddea3c in -[UIResponder becomeFirstResponder] ()
#30 0x000000011425d3ea in -[UIView(Hierarchy) becomeFirstResponder] ()
#31 0x0000000114088a50 in -[UITextField becomeFirstResponder] ()
#32 0x000000011425d42c in -[UIView(Hierarchy) deferredBecomeFirstResponder] ()
#33 0x000000011425d4d0 in -[UIView(Hierarchy) _promoteSelfOrDescendantToFirstResponderIfNecessary] ()
#34 0x000000011425d947 in __45-[UIView(Hierarchy) _postMovedFromSuperview:]_block_invoke ()
#35 0x000000011425d7d4 in -[UIView(Hierarchy) _postMovedFromSuperview:] ()
#36 0x000000011426cd8a in -[UIView(Internal) _addSubview:positioned:relativeTo:] ()
#37 0x0000000113f7329e in -[UITableView _addSubview:positioned:relativeTo:] ()
#38 0x0000000114201aea in -[UIScrollView _addContentSubview:atBack:] ()
#39 0x0000000113f72fec in -[UITableView _addContentSubview:atBack:] ()
#40 0x0000000113f96096 in __53-[UITableView _configureCellForDisplay:forIndexPath:]_block_invoke ()
#41 0x0000000114265d65 in +[UIView(Animation) performWithoutAnimation:] ()
#42 0x0000000113f95816 in -[UITableView _configureCellForDisplay:forIndexPath:] ()
#43 0x0000000113fa7986 in -[UITableView _createPreparedCellForGlobalRow:withIndexPath:willDisplay:] ()
#44 0x0000000113fa7e0b in -[UITableView _createPreparedCellForGlobalRow:willDisplay:] ()
#45 0x0000000113f7145d in -[UITableView _updateVisibleCellsNow:] ()
#46 0x0000000113f90f63 in -[UITableView layoutSubviews] ()
#47 0x0000000114273821 in -[UIView(CALayerDelegate) layoutSublayersOfLayer:] ()
#48 0x0000000110b57b11 in -[CALayer layoutSublayers] ()
#49 0x0000000110b5c51b in CA::Layer::layout_if_needed(CA::Transaction*) ()
#50 0x0000000110b68c44 in CA::Layer::layout_and_display_if_needed(CA::Transaction*) ()
#51 0x0000000110ab1a23 in CA::Context::commit_transaction(CA::Transaction*, double) ()
#52 0x0000000110ae6ecd in CA::Transaction::commit() ()
#53 0x0000000113ddaf20 in _afterCACommitHandler ()
#54 0x000000010f4c07c7 in __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__ ()
#55 0x000000010f4bb26e in __CFRunLoopDoObservers ()
#56 0x000000010f4bb8ea in __CFRunLoopRun ()
#57 0x000000010f4bafd6 in CFRunLoopRunSpecific ()
#58 0x00000001191b99f8 in GSEventRunModal ()
#59 0x0000000113db1722 in UIApplicationMain ()
#60 0x000000010d6ccb6b in main at /Users/ander/opgaver/ShellFish/ShellFish/AppDelegate.swift:19
#61 0x0000000111f3c6ad in start ()

OK, this solved by problem but tylerf's post below is clearly pretty much definitive.

I've been experiencing this error recently too on a UITableView.

Interestingly I was only receiving the error on an actual device, no issues whilst using Simulator.


My UITableView uses CellForRowAt to display the cells depending on the section.

After trial and error, the offending line appeared to be:

cell.infoTextView.isEditable = tableView.isEditing


Changing to the following line has resolved the crash (at least for me):

cell.infoTextView.isUserInteractionEnabled = tableView.isEditing

Thanks enormously.


I am indeed watching changes in contentOffset. The pointer to scrollViewDidScroll() makes a lot of sense.

Unfortunately my case is different. I am not calling any visibleCells function. Instead, what I do is just having a UITextView inside a UITableViewCell, with top, bottom, leading and trailing constraint set to the cell, and scrollable disabled so that the cell is a self-resizing. To replicate the exception, I just need to update the text of the text view through data source update. Here's the sample code:


import UIKit

class DemoCell: UITableViewCell {
    @IBOutlet var demoTextView: UITextView!
}

let demoText = "This is a demo text"
let demoText2 = "This is a different demo text"

class ViewController: UIViewController {

    @IBOutlet var tableView: UITableView!
    var demoDatas = Array(repeating: demoText, count: 10)
    
    override func viewDidLoad() {
        super.viewDidLoad()
        tableView.dataSource = self
        
        DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
            self.demoDatas = Array(repeating: demoText2, count: 10)
            self.tableView.reloadData()
        }
    }
}

extension ViewController: UITableViewDataSource {
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return demoDatas.count
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "DemoCellIdentifier", for: indexPath) as! DemoCell
        cell.demoTextView.text = demoDatas[indexPath.row]
        return cell
    }
}

I believe this kind of setup is quite common in messaging app. Any idea for the work around?

Thanks for reporting this case, this one looks like one of the known issues in Seed 1. If you can file a quick bug report using Feedback Assistant with this info, and also share the backtrace when you hit the exception (or a crash report), we can ensure this is tracked and you'll get notified when it's fixed!

I'm having the same issue with the exception being thrown due to a setText: on a UITextView – based on what I'm seeing with beta 2 this hasn't been resolved yet, is that correct?


Is there a work-around available in that case or do we have to wait for this to be resolved in a future beta release? I'm currently stuck being able to update and test my application due to this issue.

I'm also having this same issue on our application's basic tableview setup. Each cell has a uitextview and modifying the textview's text is causing the exception to arise.

Is there an update or a fix for this? We are having the same issue. We have a cell with a UITextField and we just update the text of the text field. We also make UITextField inside one of the cells first responder. The app crashes with exception 'NSInternalInconsistencyException', reason: 'Attempted to access the table view's visibleCells while they were in the process of being updated, which is not allowed.

This has been fixed for a few betas already. Please update to the latest beta of iOS 13 and try there.

(lldb) bt

* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 7.1

frame #0: 0x00000001ab675140 UIKitCore`UITableViewAlertForLayoutOutsideViewHierarchy

frame #1: 0x00000001ab674100 UIKitCore`-[UITableView _updateVisibleCellsNow:] + 284

frame #2: 0x00000001ab687ac0 UIKitCore`-[UITableView _visibleCellsUsingPresentationValues:] + 456

frame #3: 0x00000001ab67c294 UIKitCore`-[UITableView _updateAnimationDidStopWithOldVisibleViews:finished:context:] + 1608

frame #4: 0x00000001ab93eb28 UIKitCore`-[UIViewAnimationBlockDelegate _didEndBlockAnimation:finished:context:] + 588

frame #5: 0x00000001ab913034 UIKitCore`-[UIViewAnimationState sendDelegateAnimationDidStop:finished:] + 244

frame #6: 0x00000001ab91353c UIKitCore`-[UIViewAnimationState animationDidStop:finished:] + 240

frame #7: 0x00000001adefe28c QuartzCore`CA::Layer::run_animation_callbacks(void*) + 276

frame #8: 0x0000000101e4ac88 libdispatch.dylib`_dispatch_client_callout + 16

frame #9: 0x0000000101e58ce8 libdispatch.dylib`_dispatch_main_queue_callback_4CF + 1316

frame #10: 0x00000001a74697d4 CoreFoundation`__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__ + 12

frame #11: 0x00000001a74647c8 CoreFoundation`__CFRunLoopRun + 2004

frame #12: 0x00000001a7463ccc CoreFoundation`CFRunLoopRunSpecific + 464

frame #13: 0x00000001b1765328 GraphicsServices`GSEventRunModal + 104

frame #14: 0x00000001ab4c7c80 UIKitCore`UIApplicationMain + 1936

* frame #15: 0x000000010051c168 belweb`main at AppDelegate.swift:21:7

frame #16: 0x00000001a72ee424 libdyld.dylib`start + 4


no my code in a backtrace that has visibleCells . 13 beta 6