Running through the codelabs Firebase tutorial covering FriendlyChat. Addressed constant issue (answered elsewhere) but when I go to upload the selected image, my app crashes. I redid all steps and tested the "complete" version of the tutorial source code to ensure it wasn't something I was doing. No luck. Anyone else seeing this issue?
Here is the exception...
2016-05-23 17:25:13.119 FriendlyChatSwift[61549:15581893] *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Invalid update: invalid number of rows in section 0. The number of rows contained in an existing section after the update (1) must be equal to the number of rows contained in that section before the update (6), plus or minus the number of rows inserted or deleted from that section (1 inserted, 0 deleted) and plus or minus the number of rows moved into or out of that section (0 moved in, 0 moved out).'
When I check the message count on initial load, it's fine. When I go to the imagePicker and come back, suddenly message count is 1 but the row count is still 6.
The problem appears to be here:
override func viewWillAppear(animated: Bool) {
self.messages.removeAll()
// Listen for new messages in the Firebase database
_refHandle = self.ref.child("messages").observeEventType(.ChildAdded, withBlock: { (snapshot) -> Void in
self.messages.append(snapshot)
self.clientTable.insertRowsAtIndexPaths([NSIndexPath(forRow: self.messages.count-1, inSection: 0)], withRowAnimation: .Automatic)
})
}
If you remove all the messages then the index is off in the call to insertRowsAtIndexPaths.
I got this to work by moving all the code from viewWillAppear to the end of viewDidLoad. Because viewWillAppear is called every time the view shows up again, it ends up being called after you get out of the photos view and return back to the table view. viewDidLoad, on the other hand, is only called once, at the beginning when the view is loaded. It works also when the user goes back to the home screen and returns to the app.
Figured out the real solution. The discussion on this twitter thread explains what you have to do and why. The code firebase provides should go in viewDidAppear, as they had. However, you need to reload your table after you removeAll messages.
self.messages.removeAll()
self.clientTable.reloadData()
And then your viewWillDisappear needs to correctly remove the observer, as outlined by Ibrahim Ulukaya above.
self.ref.child("messages").removeObserverWithHandle(_refHandle)
The reason it should be in viewDidAppear is so you can re-start observing the table when the view comes back to the top. And the observer needs to be removed in viewWillDisappear so that you do not have a view responding to an observer/notification when it is not on screen as this violates the MVC rules.
Hope this helps. I struggled with this for a while. Not sure how the Firebase team didn't catch this.
The main error was that in viewWillDisappear removed the observer in wrong reference.
It should be
self.ref.child("messages").removeObserverWithHandle(_refHandle)
instead. Also you'd like to reloadData after removeAll in your viewWillAppear.
You wouldn't need to removeobjects and reloaddata everytime if you move them to viewDidLoad and deAlloc.
(I'll be updating the sourcecode shortly.)
Related
I have developed a UITableViewController screen. It's working fine on Xcode 10.2 but. When I run on Xcode 11 beta 1 it's crashing like below.
I didn't find what was happening.
In ViewDidLoad
override func viewDidLoad() {
super.viewDidLoad()
tableView.tableFooterView = UIView()
plateNoPrefix.becomeFirstResponder() // static cell textfield in tableViewcell
}
Exception… Attempted to access the table view's visibleCells while they were in the process of being updated, which is not allowed
I have faced the same issue when providing support for iOS 13.
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 behaviour 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 visible cells. 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 someplace 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 visible cells 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.
If you are hitting this exception and you think you are requesting the
visible cells 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.
Update:
I am sure but plateNoPrefix.becomeFirstResponder() causing the crash. As of now, you can check by pasting this code in viewDidAppear method
OR
Execute this code after delay (Worked for me)
DispatchQueue.main.asyncAfter(deadline: .now()+0.1) {
// Your code
}
For details clerification you can refer Apple Developer Forum
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
Please have a look at Apple Developer Forum
My app has two CollectionViewControllers. Only one is visible at a given time.
I have created the following structure on storyboard: two container views on top of each other. Every container view has a CollectionViewController embedded. The visibility of a particular container view determines which collectionViewController is visible.
This is the problem. Both CollectionViewControllers are receiving data in parallel but iOS has a bug that will make the app crash if one CollectionViewController tries to execute an insert using performBatchUpdates while it is invisible.
Trying to prevent that, I have created a BOOL flag on both CollectionViewControllers so they can know if they are visible and execute or not the performBatchUpdates. Something like:
if (self.isThisCollectionViewVisible == NO) return;
[self.collectionView performBatchUpdates:^{
// bla bla... perform insert,m remove...
This solves part of the problem. But the app continues to crash on the following condition: if I tap the button to switch to the invisible CollectionViewController making it visible while it is receiving updates.
I mean this: lets call A the first CollectionViewController and B the second one. A is visible and B is invisible at this point. B starts receiving data and is trying to do a performBatchUpdates but as it is invisible, the if (self.isThisCollectionViewVisible == NO) return; is preventing performBatchUpdates to run, what is fine. Now I make A invisible and B visible. At this point the flag self.isThisCollectionViewVisible is set to YES and performBatchUpdates makes the app crash with this error:
* Assertion failure in -[CollectionView _endItemAnimationsWithInvalidationContext:tentativelyForReordering:], /BuildRoot/Library/Caches/com.apple.xbs/Sources/UIKit/UIKit-3512.60.7/UICollectionView.m:4625
* Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Invalid update: invalid
number of items in section 0. The number of items contained in an
existing section after the update (76) must be equal to the number of
items contained in that section before the update (70), plus or minus
the number of items inserted or deleted from that section (5 inserted,
2 deleted) and plus or minus the number of items moved into or out of
that section (0 moved in, 0 moved out).'
I think the CollectionViewController is really not yet ready and updated to be able to do a performBatchUpdates... and this is not a matter of not updating the data source previously because it is being updated.
What checks can I do to prevent that from happening?
NOTE: I noticed something strange about this crash in particular. It says that 5 elements are being inserted and 2 deleted but in fact 3 elements are being inserted, 0 deleted and 2 changed when the crashes happen.
For me adding self.collectionView.numberOfItemsInSection(0) fixed the crash.
The collectionView has issues while inserting items when it is not visible.
Seems like I'm not alone with my solution: http://www.openradar.me/15262692
This crash told you that you didn't updated your datasource for collection. You need to update your dataSource (array or dictionary) and reload collection view data after you perform performBatchUpdates.
Invalid update: invalid number of items in section 0. The number of items contained in an existing section after the update (76) must be equal to the number of items contained in that section before the update (70), plus or minus the number of items inserted or deleted from that section (5 inserted, 2 deleted) and plus or minus the number of items moved into or out of that section (0 moved in, 0 moved out).'
As written in apple docs
Deletes are processed before inserts in batch operations. This means
the indexes for the deletions are processed relative to the indexes of
the collection view’s state before the batch operation, and the
indexes for the insertions are processed relative to the indexes of
the state after all the deletions in the batch operation.
So, move the changes before the inserts and it will the trick!
Encountered the same error today, for me, in performBatchUpdates block replace this:
NSArray *selectedItemsIndexPaths = [self.collectionView indexPathsForSelectedItems];
with this:
NSIndexPath *selectedIndexPath = [NSIndexPath indexPathForRow:self.selectIndex inSection:0];
NSArray *selectedItemsIndexPaths = #[selectedIndexPath];
Maintain the index by myself, it's OK now. The error should not be associated with data source, if you have had update the data source. It maybe related to the reuse of cells.
I have a paging tableView that loads a new page every time the user over scrolls, sort of like the instagram feed. The reloading of the tableView happens in a callback after the new page was fetched. The code looks something like this:
func fetchNewPage() {
ws.fetchNewPage(completion: { () -> Void in {
updateDataSource()
self.tableView.reloadData()
})
}
At the same time, the cells in the tableView should be updated when the user taps on them (they change their background color). This happens after a call to the WS. The code looks something like this:
func markAsSeen(indexPath: NSIndexPath) {
ws.markAsSeen(completion: { () -> Void in {
self.tableView.reloadRowsAtIndexPaths([indexPath], withRowAnimation: .None)
})
}
My problem is that the dataSource can change the number of elements while the markAsSeen completion block is executing. Then the app crashes with this exception:
Fatal Exception: NSInternalInconsistencyException
Invalid update: invalid number of rows in section 0. The number of rows contained in an existing section after the update (50) must be equal to the number of rows contained in that section before the update (25), plus or minus the number of rows inserted or deleted from that section (1 inserted, 1 deleted) and plus or minus the number of rows moved into or out of that section (0 moved in, 0 moved out).
So, my question is if you have any idea how to synchronize these two blocks so that the app does not crash anymore but also the row gets updated.
Thanks a lot
Your idea about synchronization is correct. As an option you can perform updates of data source on main queue. In such case data source and UI updates will be queued and executed serially, as result change of data will be predictable and consistent.
Also it worth to mention that all UI calls is mandatory should be executed on main thread. From your code it is not really clear that it is true.
I am using what I believe is a common pattern for adding items to a table view -
main controller creates modal controller and registers itself as delegate
modal view controller is presented
user provides some data and hits save button in the modal's navigation bar
modal view controller send its delegate a message containing the details entered
original controller receives the message and dismisses the modal
original controller updates the data model and inserts a new row into its tableview
This is working well except in one specific scenario.
If the device is rotated while the modal is presented, the app crashes after dismissing the modal. The new row is inserted correctly but then immediately afterwards fails:
*** Assertion failure in -[UITableView _endCellAnimationsWithContext:],
/SourceCache/UIKit_Sim/UIKit-2380.17/UITableView.m:1070
2013-07-28 17:28:36.404 NoHitterAlerts[36541:c07] *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Invalid update: invalid number of rows in section 0. The number of rows contained in an existing section after the update (8) must be equal to the number of rows contained in that section before the update (8), plus or minus the number of rows inserted or deleted from that section (1 inserted, 0 deleted) and plus or minus the number of rows moved into or out of that section (0 moved in, 0 moved out).'
I can't for the life of my figure out why this occurs. Test cases that work consistently fail if I rotate with the modal presented. I've noticed that simply reloading the tableview instead of inserting the row with animation works fine.
Here is a bare bones project that demonstrates the same issue:
demo project
Run the project in the iPhone sim
Add an item to the list - works fine
Back on first screen, rotate to landscape
Run the same test again. Still works.
Back on first screen, launch modal. Rotate simulator while modal is still presented. Hit 'Add Item'. Crashes.
Any ideas on what might be happening here?
I see what your problem is. In your MainController's -modalController:didAddItem: method, you are first adding the object to the self.arrayOfStrings, and not inserting the row into the tableView until after the -dismissViewControllerAnimated method has completed.
This appears to work when the orientation is not changed when the modalViewController is open, however if you do change the orientation, the mainController's orientation isn't changed until it is being closed. Once this happens, it seems that the tableView's data is automatically reloaded due to the frame being changed.
Thus, because the arrayOfStrings is having the object added before the animation starts and the -insertRowsAtIndexPaths:withRowAnimation: is not called until after the animation is completed, the table view thinks it has already gotten the rows by the time the insert method is reached.
In order to fix this, all you have to do is move your method that adds the string to the array into the completion block right before you call the insertRows method on your tableView.
So your method would end up looking something like the following with whatever changes you need for your actual project:
- (void)modalController:(ModalController *)controller didAddItem:(NSString *)string
{
//dismiss the modal and add a row at the correct location
[self dismissViewControllerAnimated:YES completion:^{
[self.arrayOfStrings addObject:string];
[self.tableView insertRowsAtIndexPaths:#[[NSIndexPath indexPathForRow:self.arrayOfStrings.count - 1 inSection:0]] withRowAnimation:UITableViewRowAnimationAutomatic];
}];
}
I have an iPad app with ARC, storyboards, supporting ios5+.
I have a VC that is working with NSFetchedResultsController on its own.
The VC has two tableviews pointing at two different entities.
I have single record add, change, delete, and moving of rows working fine on this VC.
I've now introduced a new feature to enable the user to quick add multiple rows to these tableviews.
I do this by providing a segue (modal) from this VC to a new VC (lets call it quickAddVC).
The user enters the data on quickAddVC, and then I segue back (modal) to the original VC.
I save the new records (via an alternate call) before (I believe) setting up the NSFetchedResultsControllers for the VC again (I set them to nil in ViewDidUnload).
However I keep getting this mismatch error.
Somehow it keeps registering the number of inserts that occur from the quickAddVC as 1 insert no matter how many records I add, and it somehow remembers how many records were in the NSFetchedResultsControllers before my segues to do this comparison.
Error:
* Assertion failure in -[UITableView _endCellAnimationsWithContext:], /SourceCache/UIKit/UIKit-2372/UITableView.m:1070
Failure in didChangeObject, name=NSInternalInconsistencyException reason=Invalid update: invalid number of rows in section 0. The number of rows contained in an existing section after the update (5) must be equal to the number of rows contained in that section before the update (2), plus or minus the number of rows inserted or deleted from that section (1 inserted, 0 deleted) and plus or minus the number of rows moved into or out of that section (0 moved in, 0 moved out).
Any ideas on what I am doing wrong or should try would be useful.
I can't figure out how to get the NSFetchedResultsControllers to simply forget its original state and restart from scratch which would I think resolve this issue for me.
I haven't posted any of the code, I guess I could post a lot if needed but hoping there is enough here to outline the design issue and get some ideas.
Just reposting the answer to mark question as closed.
Ok solved the issue. Basically I was clearing the NSFetchedResultsControllers in the wrong place. By setting them to nil just prior to the segue all works fine. I'd made an incorrect assumption that given the ViewDidLoad method was being called when I segued back again, that the ViewDidUnload method was also being called but that was not the case.