NSInternalInconsistencyException with UITableViewController and a Storyboard-Pop - objective-c

I integrated GrabKit in my iPhone-App, which is a Library to Grab Photos from social Networks. Now I have the following situation/problem:
I have a UITableViewController - AlbumListViewController.
Then there's a UITableViewCell - AlbumCellViewController.
When you tap on one of these cells - albums with photos
There's a UITableViewController - PhotoListViewController
and a UITableViewCell - PhotoCellViewController.
Everything here works just finde, I can browse albums, choose one, browse the photos in it and then when I choose one of the single photos, there is a PushViewController to my SetPhotoViewController, which displays the selected image in Fullscreen.
Now when I want to use the back-Button of my navigation bar in the SetPhotoViewController, the crashes with the following message:
*** Assertion failure in -[UISectionRowData refreshWithSection:tableView:tableViewRowData:], /SourceCache/UIKit_Sim/UIKit-2372/UITableViewRowData.m:400
2012-10-22 22:49:32.814 Amis[1820:c07] *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Failed to allocate data stores for 1073741821 rows in section 1. Consider using fewer rows'
*** First throw call stack:
(0x23de012 0x1b63e7e 0x23dde78 0x15f9f35 0xc8f83b 0xc923c4 0xb56fa2 0xb5692c 0x1690f 0x1cd653f 0x1ce8014 0x1cd87d5 0x2384af5 0x2383f44 0x2383e1b 0x2a9a7e3 0x2a9a668 0xaab65c 0x42fd 0x2b75)
libc++abi.dylib: terminate called throwing an exception
DataSource and Delegate of my two UITableViewControllers are both set to File's Owner.
When I debug trough the code, I can't find any special things, all photos can be loaded, all the arrays are filled with data, there's nothing strange.
Here are the two functions of the PhotoListViewController which get called, as soon as I press the back button on my NavigationController:
(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = nil;
NSLog(#"%i", indexPath.row);
NSLog(#"%ii", indexPath.section);
// Extra Cell
if ( indexPath.section > _lastLoadedPageIndex ){
static NSString *extraCellIdentifier = #"ExtraCell";
cell = [tableView dequeueReusableCellWithIdentifier:extraCellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:extraCellIdentifier];
}
cell.textLabel.text = #"load more";
cell.textLabel.font = [UIFont fontWithName:#"System" size:8];
} else {
static NSString *photoCellIdentifier = #"photoCell";
cell = [tableView dequeueReusableCellWithIdentifier:photoCellIdentifier];
if (cell == nil) {
cell = [[[NSBundle mainBundle] loadNibNamed:#"PhotoRowViewController" owner:self options:nil] objectAtIndex:0];
}
}
// setting of the cell is done in method [tableView:willDisplayCell:forRowAtIndexPath:]
return cell;
}
and..
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
{
// extra cell
if ( [cell.reuseIdentifier isEqualToString:#"ExtraCell"] ){
if ( state == GRKDemoPhotosListStateAllPhotosGrabbed ) {
cell.textLabel.text = [NSString stringWithFormat:#" %d photos", [[_album photos] count] ];
}else {
cell.textLabel.text = [NSString stringWithFormat:#"Loading page %d", _lastLoadedPageIndex+1];
[self fillAlbumWithMorePhotos];
}
} else // Photo cell
{
NSArray * photosAtIndexPath = [self photosForCellAtIndexPath:indexPath];
[(PhotoRowViewController*)cell setPhotos:photosAtIndexPath];
}
}
What am I missing?
Edit: The code of the numberOfRowsInSection-Method:
If I debug it, the first time res is equal 0, the second time the method gets called, res is equal 322121535.
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
// Return the number of rows in the section.
NSUInteger res = 0;
if ( state == GRKDemoPhotosListStateAllPhotosGrabbed && section == _lastLoadedPageIndex ) {
NSUInteger photosCount = [_album count];
// Number of cells with kNumberOfPhotosPerCell photos
NSUInteger numberOfCompleteCell = (photosCount - section*kNumberOfRowsPerSection*kNumberOfPhotosPerCell) / kNumberOfPhotosPerCell;
// The last cell can contain less than kNumberOfPhotosPerCell photos
NSUInteger thereIsALastCellWithLessThenFourPhotos = (photosCount % kNumberOfPhotosPerCell)?1:0;
// always add an extra cell
res = numberOfCompleteCell + thereIsALastCellWithLessThenFourPhotos +1 ;
} else if ( section > _lastLoadedPageIndex) {
// extra cell
res = 1;
} else res = kNumberOfRowsPerSection;
return res;
}

I'm the developer of GrabKit. Thanks for using it :)
Actually, the controllers in GrabKit are there for demonstration purpose only, they are not designed to be used "as is" in a project, and more specifically : The GRKDemoPhotosList controller was not made to have another controller pushed in the controllers hierarchy.
So what you need is just a little fix to make grabKit demo's controllers fit to your project :)
I guess the problem is the following :
_ in [GRKDemoPhotosList viewWillAppear], the method fillAlbumWithMorePhotos is called.
_ when you pop back from your controller to GRKDemoPhotosList, this method is called one more time, and it generates this bug.
Try to add a flag in the viewWillAppear method, to avoid calling fillAlbumWithMorePhotos twice, I guess it'll work fine.
Feel free to contact me by mail ( pierre.olivier.simonard at gmail.com ) or on twitter ( #pierrotsmnrd ) if you need more informations or help :)

Related

View based NSTableView text field loses focus

After selecting a row, pressing TAB will normally move focus to the next editable text field(s), or in my case, column. However, I have noticed that only sometimes after pressing TAB, the row will turn gray and focus is lost. I must then click on the text field to continue. After days of observations, I still cannot detect a repeatable pattern, and internet searches have not given me enough information. I wonder if my delegate might be the culprit. Some questions come to mind.
Can anyone shed light on why or how focus might be lost? (indirectly answered by Willeke's comment to remove some code)
Is there a proper or deliberate way to force focus on the next text field? (now irrelevant)
The unpredictable behavior prompting the first two questions seemed to be interference between two methods. Resolving one has at least led to predictable behavior and this related question. I included more code below with another method. One column contains a pop-up button. Ordinarily, choosing a value does not select the row (ie, highlighted). I would like the row to be selected and focused (ie, highlighted blue) so that I can immediately TAB to the next field and begin editing. In editPopUps:, selectRowIndexes: will select the row, but the row is gray instead of blue and hence TAB has no effect. How do I select and focus the row?
Update: Here are some pertinent methods. I setup each view based on column because only some columns are editable.
- (NSView *)tableView:(NSTableView *)tableView viewForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row {
NSString *column = tableColumn.identifier;
NSTableCellView *value = [tableView makeViewWithIdentifier:column owner:self];
Foo *foo = self.foos[row];
[self setupView:value column:column foo:foo];
[self populateCellView:value column:column foo:foo object:[foo valueForKey:column]];
return value;
}
- (void)setupCellView:(NSTableCellView *)view column:(NSString *)column foo:(Foo *)foo {
view.textField.alignment = NSRightTextAlignment;
BOOL editable = YES;
if ([attribute isEqualToString:column] || ![foo.x isEqualToNumber:bar.x]) {
editable = NO;
} else {
view.textField.target = self;
view.textField.action = #selector(editTextField:);
}
[view.textField setEditable:editable];
}
- (void)editTextField:(NSTextField *)sender {
if ([self.fooTableView numberOfSelectedRows]) {
NSTableCellView *cellView = (NSTableCellView *)[sender superview];
NSString *column = cellView.identifier;
NSInteger row = [self.fooTableView rowForView:cellView];
Foo *foo = self.foos[row];
NSNumber *amountNumber = [NSNumber numberWithShort:[sender intValue]];
if ([attribute isEqualToString:column]) {
foo.y = amountNumber;
} else {
foo.z = amountNumber;
}
[self.fooTableView reloadDataForRowIndexes:[NSIndexSet indexSetWithIndex:row] columnIndexes:[NSIndexSet indexSetWithIndex:[self.fooTableView columnWithIdentifier:column]]];
[self refreshSelection];
}
}
- (void)editPopUps:(NSPopUpButton *)sender {
GlorpView *glorpView = (GlorpView *)[sender superview];
NSString *column = pokeCellView.identifier;
NSInteger row = [self.fooTableView rowForView:glorpView];
Foo *foo = self.foos[row];
NSNumber *indexValue = [NSNumber numberWithShort:[sender indexOfSelectedItem]];
NSMutableIndexSet *columnIndexes = [NSMutableIndexSet indexSetWithIndex:[self.fooTableView columnWithIdentifier:column]];
if ([attribute isEqualToString:column]) {
foo.Z = indexValue;
}
[self.fooTableView selectRowIndexes:[NSIndexSet indexSetWithIndex:row] byExtendingSelection:NO];
[self refreshSelection];
}

How to display selected consumables in a table in Objective C

I am having an issue selecting appropriate consumables that I have set up in iTunes Connect to appear in my table in a nib file. (Using Ray Wenderlich's tutorial)
I have set up 11 consumables which are in two distinct sets; coins and categories.
What I want to do is have two buttons, each taking the user to a different table. Depending on the table a different set is displayed.
I have tried for hours to get this to work and I cannot do it; so I bow to the powers of the StackOverflow community to help.
Currently my set is as follows:
NSSet * productIdentifiers = [NSSet setWithObjects:
#"com.ac.scramble.coins1",
#"com.ac.scramble.coins2",
#"com.ac.scramble.coins3",
#"com.ac.scramble.coins4",
#"com.ac.scramble.coins5",
#"com.ac.scramble.category1",
#"com.ac.scramble.category3",
#"com.ac.scramble.category5",
#"com.ac.scramble.category8",
#"com.ac.scramble.category15",
#"com.ac.scramble.category30",
nil];
sharedInstance = [[self alloc] initWithProductIdentifiers:productIdentifiers
which is set up through iTunes Connect with no problem.
The evident problem here is that in my first nib which wants to show 6 rows, it is showing the final six in the list above (not sure why the final six rather than the first six...). This is fine, but in the second nib, which wants to show 5 rows, it is showing the final five from the list above. I don't want that - I want it to display the top five (the coins).
I've tried splitting the NSSet up into two and passing through but I cannot get that to work. I don't know if I can in the code somewhere specify which rows of the set I want to display. The pertinent code (I believe) which is probably where I need to do some trickery is:
- (void)reload {
_products = nil;
[self.table2 reloadData];
[[RageIAPHelper sharedInstance] requestProductsWithCompletionHandler:^(BOOL success, NSArray *products) {
if (success) {
_products = products;
[self.table2 reloadData];
}
//[self.refreshControl endRefreshing];
}];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *simpleTableIdentifier = #"SimpleTableCell";
SimpleTableCell *cell = (SimpleTableCell *)[tableView dequeueReusableCellWithIdentifier:simpleTableIdentifier];
if (cell == nil)
{
NSArray *nib = [[NSBundle mainBundle] loadNibNamed:#"SimpleTableCell" owner:self options:nil];
cell = [nib objectAtIndex:0];
}
SKProduct *product = (SKProduct *) _products[indexPath.row];
cell.nameLabel.text = product.localizedTitle;
NSLog(#"Localized title: %#", product.localizedTitle);
cell.thumbnailImageView.image = [UIImage imageNamed:[thumbnails3 objectAtIndex:indexPath.row]];
cell.descriptionLabel.text = [descriptions3 objectAtIndex:indexPath.row];
[_priceFormatter setLocale:product.priceLocale];
//cell.detailTextLabel.text = [_priceFormatter stringFromNumber:product.price];
cell.progressLabel.text = [_priceFormatter stringFromNumber:product.price];
cell.descriptionLabel.text = product.localizedDescription;
}
Thanks to all in advance.
I managed to work out the answer and it was indeed splitting the set up. This may not be the most efficient way of doing this but I took the Set of 11 elements and depending on which one I wanted I either used the removeLastObject or removeObjectAtIndex[0] methods to obtain what I wanted. Thanks to everyone's help!

NSInternalInconsistency Exception in UITableView

- (UITableViewCell*)tableView:(UITableView*)tableView cellForRowAtIndexPath:(NSIndexPath*)indexPath
{
static NSString* CellIdentifier = #"MessageCellIdentifier";
MessageTableViewCell* cell = (MessageTableViewCell*)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil)
cell = [[MessageTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
Message* message = [self.dataModel.messages objectAtIndex:indexPath.row];
[cell setMessage:message];
return cell;
}
I am developing an chat application in which i am getting exception when sending and receiving messages happening at the same time.The following is the exception message
Assertion failure in -[UITableView _endCellAnimationsWithContext:], /SourceCache/UIKit/UIKit-2380.17/UITableView.m:1070 2013-04-30
16:55:14.314 [2689:907] 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 (19) must be equal to the number of rows contained in that section before the update (17), 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).'
- (int)tableView:(UITableView*)tableView numberOfRowsInSection:(NSInteger)section
{
return [self.dataModel.messages count];
}
- (void)didSaveMsg:(NSMutableArray *)array1
{
self.dataModel.messages = array1;
[tableview insertRowsAtIndexPaths:[NSArray arrayWithObject:[NSIndexPath indexPathForRow:array1.count-1 inSection:0]] withRowAnimation:UITableViewRowAnimationFade];
[self scrollToNewestMessage];
}
- (void)scrollToNewestMessage
{
dispatch_after(DISPATCH_TIME_NOW, dispatch_get_main_queue(), ^(void){
NSIndexPath* indexPath = [NSIndexPath indexPathForRow:(self.dataModel.messages.count - 1) inSection:0];
[tableview scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionTop animated:YES];
});
[tableview reloadData];
[[DBModel database]updateMessageCount1:mobileNo cnt:#"0"];
}
The error message is fairly self explanatory - you are telling the table view you want to insert a single cell, but in the table's datasource are then saying it has two extra cells to display (19 instead of 17).
The problem almost certainly isn't in the code you've posted, it will be either in the numberOfRows method of your datasource, or in the code at the point at which you insert the extra cell.

TitleForHeaderInSection throwing EXC_BAD_ACCESS

I am trying to create a generic UITableView in my iPhone app.
I have a UITableView which populates the data using an array via a SELECT query loop.
I add the data into my array and populate the array in cellForRowAtIndexPath:.
I get the section header using that array and by using a sort method, I put the section headers in Array1.
I would like to have titleForHeaderInSection: work by having section 0 be a static header name and sections 1 and later become generic, meaning the header name will come from Array1.
I am not sure how can I create that logic since the app always throws EXC_BAD_ACCESS with the code below.
My logic: I keep the count of the array in an int and see if the value is greater than 0. If it is, I add the section header for and objectAtIndex:0, otherwise I use the static one. But when the count gets to 2, for section 2 and objectAtIndex:1, it breaks and throws EXC_BAD_ACCESS.
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
{
int value = [[self Array1] count];
if(section == 0)
return #"Countries";
if (value > 0) {
if (section == value){
return [[self Array1] objectAtIndex:section - 1];
}
}
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
int count = [[self Array1] count];
return count + 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
int value = [[self Array1] count];
if (section == 0) {
return [self.Array count];
}
if (value > 0) {
if (section == [[self Array1] count]) {
NSString *initialLetter = [[self Array1] objectAtIndex:section - 1];
// get the array of elements that begin with that letter
NSArray *elementsWithInitialLetter = [self elementsWithInitialLetter:initialLetter];
// return the count
return [elementsWithInitialLetter count];
}
}
}
Looks like you're just missing a retain on the iVar backing the Array1 method. declare the array as a property:
#property (nonatomic, retain) NSArray* datastore;
Then cache the value you're referring to in the Array1 method in this method (probably in viewDidLoad).
self.datastore = [self Array1];
Then replace all remaining references to [self Array1] with self.datastore. Build run and see if it still crashes. (Don't forget to set self.datastore = nil in your dealloc!

UITableView deleteRowsAtIndexPath when to update the model?

I'm implementing a method to remove a row from my tableview based on a domain object but I'm getting a error after call deleteRowsAtIndexPath:animated
Blockquote 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 (2) 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 (0 inserted, 1 deleted).'
So I thought "ok, I'm updating the model too soon". The code:
- (void)deleteItem:(Item *)item {
NSInteger index = [items indexOfObject:item]; // items is the model a NSArray
if (index != NSNotFound) {
NSMutableArray *itemEditable = [items mutableCopy];
[itemEditable removeObjectAtIndex:index];
self.items = itemEditable;
NSIndexPath *indexToDelete = [NSIndexPath indexPathForRow:index inSection:0];
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexToDelete] withRowAnimation:UITableViewRowAnimationMiddle];
}
}
And then I change the code to update the model after I call deleteRowsAtIndexPath:animated
- (void)deleteItem:(Item *)item {
NSInteger index = [items indexOfObject:item]; // items is the model a NSArray
if (index != NSNotFound) {
NSIndexPath *indexToDelete = [NSIndexPath indexPathForRow:index inSection:0];
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexToDelete] withRowAnimation:UITableViewRowAnimationMiddle];
NSMutableArray *itemEditable = [items mutableCopy];
[itemEditable removeObjectAtIndex:index];
self.items = itemEditable;
}
}
But it didn't solve, the only thing that changes is the number of rows in the error message from 2 to 3.
Blockquote 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 (3) must be equal to the number of rows contained in that section before the update (3), plus or minus the number of rows inserted or deleted from that section (0 inserted, 1 deleted).'
Where should I update the model?
Added numberOfRowsInSection method
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return [items count];
}
I've found the problem. The setter of items property is implemented to call reloadData and this was causing this confusion.
-(void) setItens:(NSMutableArray *)newItens{
if (itens != nil) {
[itens release];
itens = nil;
}
itens = [newItens copy];
// [tableView reloadData]; // WRONG don't do this
}
This is the second time I found what I was doing wrong just after publishing the question here. I hope this help someone one day. Thanks aBitObvious, I'm aBitShy now :)
I think after deleting the row you need to reload it just below the deleted line:
I mean,
- (void)deleteItem:(Item *)item
{
.......................your code..............
[tableView reloadData];
}
Try this, it should work.