IPAD vs Iphone TableView rows - ios

I have created a Universal Master Details application. I used Storyboard for the IPad and iPhone. I am actually making an HTTP request to retrieve the required data that will be presented in the master tableview. I reload the tableview data from the requestFinished method.
- (void)requestFinished:(ASIHTTPRequest *)request {
NSString *responseString = [request responseString];
ConversationDataController *dataControllerSingleton = [ConversationDataController sharedInstance];
conversationList = [responseString JSONValue];
[dataControllerSingleton setConversationList:conversationList];
[self.tableView reloadData];
}
I store the returned data in dataControllerSingleton which has an NSMutableArray property called conversationList.
In the numberOfRowsInSection, I print out the number of objects in the section based upon the fetchedResultsController. I also print out the number of values in my NSMutableArray conversationList. In numberOfSectionsInTableView I print out the number of sections.
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
id <NSFetchedResultsSectionInfo> sectionInfo = [self.fetchedResultsController sections][section];
NSLog(#"Number OF Objects in section %lu",(unsigned long)[sectionInfo numberOfObjects]);
ConversationDataController *dataControllerSingleton = [ConversationDataController sharedInstance];
NSLog(#"ConversationList count %lu",(unsigned long)[dataControllerSingleton.conversationList count]);
return [sectionInfo numberOfObjects];
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
NSLog(#"Number of sections: %d",[[self.fetchedResultsController sections] count]);
return [[self.fetchedResultsController sections] count];
}
So, when I use NSLog to see how many section and rows I have I get
Number of Objects in section 2
ConversationList count 46
Number of Sections 1
In my cellForRowAtIndexPath I am adding the cell text from my NSMutableArray
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView
dequeueReusableCellWithIdentifier:#"ConversationCell"];
[self configureCell:cell atIndexPath:indexPath];
ConversationDataController *dataControllerSingleton = [ConversationDataController sharedInstance];
cell.textLabel.text = [dataControllerSingleton.conversationList objectAtIndex:indexPath.row];
return cell;
}
So, with this setup, I see two values in the master view for both iPhone and iPad. When I select either one of these values in master view, I am taken to the detail view (which is just empty for now). This works without any errors for the two rows that are presented.
If I change numberOfRowsInSection to return the number of values in my NSMutableArray
return [self.conversationList count];
I run into errors.
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
id <NSFetchedResultsSectionInfo> sectionInfo = [self.fetchedResultsController sections][section];
NSLog(#"Number OF Objects in section %lu",(unsigned long)[sectionInfo numberOfObjects]);
ConversationDataController *dataControllerSingleton = [ConversationDataController sharedInstance];
NSLog(#"ConversationList count %lu",(unsigned long)[dataControllerSingleton.conversationList count]);
//return [sectionInfo numberOfObjects];
return [self.conversationList count];
}
On iPhone simulator, I am presented with the 46 values in the master view. I can select any one of them and I am presented with the (empty) detail view. On the iPad simulator, if I select either one of the first two cells, I am presented with the (empty) detail view as expected. However, when I select any value beyond the first two, I get the following error;
Terminating app due to uncaught exception 'NSRangeException', reason: '*** - [_PFBatchFaultingArray objectAtIndex:]: index (2) beyond bounds (2)'
*** First throw call stack:
(0x19a5012 0x17cae7e 0x19a4deb 0x156941a 0x15e9511 0x42fe9 0x4ae285 0x4ae4ed 0xeb85b3 0x1964376 0x1963e06 0x194ba82 0x194af44 0x194ae1b 0x249b7e3 0x249b668 0x3feffc 0x40e0d 0x2ac5)
libc++abi.dylib: terminate called throwing an exception
Any assistance would be greatly appreciated. Thanks in advance.
Tim

If you are using more than one section (which it looks like you are) then delegate method
tableView:(UITableView *)tableView cellForRowAtIndexPath:
should really be handling this.
You also seem to be mixing the use of NSFetchedResultsController to work out the number of sections and rows, but then using the property self.conversionList to return the number of rows. But for which section. If you want to use Core Data then instead of using a singleton with an array, cache the data from your web server into a core data table and use that in your
- (NSFetchedResultsController *)fetchedResultsController
method.
Also you are mixing the use of self.conversionList &
ConversationDataController *dataControllerSingleton = [ConversationDataController sharedInstance];
dataControllerSingleton.conversationList
are they both the same?

Please add libz.dylib into your project . It can be added in the Build phases tab. There you have link with Binarry libraries section . Click '+' symbol and add libz.dylib to your project

Related

Updating Core Data executes UITableView problems [duplicate]

I have a problem with UITableView sections. I'm using NSFetchedResultsController to populate table and I've provided sectionNameKeyPath: on initialization.
Basiclly I have a table view with one section but when user taps on cell it is changing one core data attribute and should create second section. I think I've accomplished this but...
When I tap on the first cell it creates new section above (this is how it should be) but when my first tap is on another cell it creates section at the botton of table view. You can preview this on screenshots below:
Here are some Table View delegate methods from my app:
-(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return [[_fetchedResultsController sections] count];
}
-(NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
{
return [[[_fetchedResultsController sections] objectAtIndex:section] name];
}
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
id sectionInfo = [[_fetchedResultsController sections] objectAtIndex:section];
return [sectionInfo numberOfObjects];
}
And my question is: How to manually provide section order?
For this example solution was pretty straight - adding one NSSortDescriptor to existing one in NSFetchedResultsController sort descriptors array.
So now, fetched data is firstly sorted by section and then it is sorted alphabetically.

Core data backed UITableView giving error when deleting cells

I've got a UITableView backed by an array of Core Data objects, made by appending two arrays of Core Data objects named folders and notes. Whenever I try to delete a row from the table view, it throws:
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) and plus or minus the number of rows moved into or out of that section (0 moved in, 0 moved out).'
Here's the code to delete (in my UITableViewCell subclass):
NSManagedObjectContext *context = [NSManagedObjectContext MR_contextForCurrentThread];
[object MR_deleteInContext:context];
[context MR_saveToPersistentStoreAndWait];
[tableViewController loadData];
[tableView deleteRowsAtIndexPaths:#[self.indexPath] withRowAnimation:UITableViewRowAnimationLeft];
Note: the MR_ methods are MagicalRecord methods, they do what they say.
Here's the loadData method in the UITableViewController (basically refetches the array):
NSPredicate *textParentFilter = [NSPredicate predicateWithFormat:#"parent == %#", self.parent];
self.notes = [Note MR_findAllWithPredicate:textParentFilter];
NSPredicate *folderParentFilter = [NSPredicate predicateWithFormat:#"parentFolder == %#", self.parent];
self.folders = [Folder MR_findAllWithPredicate:folderParentFilter];
Also:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
// Return the number of rows in the section.
return [self.notes count] + [self.folders count];
}
I can see why I'm getting an exception: I'm deleting the row in loadData and then the numberOfRowsInSection is out of sync from the tableview and the Core Data store. However, if I replace deleteRowsAtIndexPaths with [tableView reloadData], it deletes just fine (but there's no animation). I've tried beginUpdates and endUpdates, switching the order in which things are deleted, to no avail.
When working with UITableViews and Core Data, it is important that the objects in your storage (e.g., your notes and folders arrays) match the UITableView delegate/datasource calls at all times. When you start animating cells for addition, deletion and updating, you encounter synchronisation issues where occasionally, the values don't align. A good example is when you have to perform multiple cell additions/deletions in succession while showing or revealing a section of a table. If you use [tableView reloadData], you enforce synchronisation but won't be able to animate the cells.
NSFetchedResultsController can be used to synchronise multiple cell additions/deletions with animations. It will require a bit of rewiring on your end, but not as much as you'd think. With this, you can control animation styles, even bulk modify cells while keeping your table intact. NSFetchedResultsController will listen to changes in Core Data based on a predicate, and reflect these changes in the tableview. This means you can have multiple tableviews respond with their own NSFetchedResultsController without having to notify them, and everything will stay synchronised.
A good place to find skeleton code is to set up a new Core Data project in XCode. Methods that you'll need to add to your code (found in MasterViewController.m):
// this references your controller that listens to Core Data and communicates with the tableview
- (NSFetchedResultsController *)fetchedResultsController
// delegate methods:
- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller;
- (void)controller:(NSFetchedResultsController *)controller didChangeSection:(id <NSFetchedResultsSectionInfo>)sectionInfo
atIndex:(NSUInteger)sectionIndex forChangeType:(NSFetchedResultsChangeType)type;
- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject
atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type
newIndexPath:(NSIndexPath *)newIndexPath;
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller;
Your tableview will then need to consult the NSFetchedController instance for it's delegate/datasource methods. For example:
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return [[self.fetchedResultsController sections] count];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
id <NSFetchedResultsSectionInfo> sectionInfo = [self.fetchedResultsController sections][section];
return [sectionInfo numberOfObjects];
}
If you're feeling advanced enough, there's a great tutorial that shows how you can queue cells additions/deletions for improved performance:
http://www.fruitstandsoftware.com/blog/2013/02/uitableview-and-nsfetchedresultscontroller-updates-done-right/
NSFetchedResultsController is one of those things that you don't really hear about in a basic Core Data tutorial, but it proves to be a real asset when doing table animations and handling large amounts of data (through caching).
I use the following code whenever a user slides a cell to delete it:
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
{
if (editingStyle == UITableViewCellEditingStyleDelete)
{
if (tableView == self.tableView)
{
NSInteger row = [indexPath row];
Entry *e = self.entries[row];
[self.entries removeObjectAtIndex: indexPath.row];
[e MR_deleteEntity];
[self.tableView reloadData];
}
}
}
In your case you first need to figure out if 'row' corresponds to a Note or a Folder, but the idea is the same.
I've fixed it! Or rather, NSFetchedResultsController fixed it. I've implemented
- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath
in my view controller to deleteRowsAtIndexPaths:#[indexPath] withRowAnimation:. My delete method is now just
NSManagedObjectContext *context = [NSManagedObjectContext MR_defaultContext];
[object MR_deleteInContext:context];
[context MR_saveToPersistentStoreAndWait];
(thanks for the advice about MR_contextForCurrentThread).
Here's loadData:
NSManagedObjectContext *c = [NSManagedObjectContext MR_defaultContext];
NSPredicate *textParentFilter = [NSPredicate predicateWithFormat:#"parent == %#", self.parent];
self.notes = [Note MR_fetchAllSortedBy:#"text" ascending:YES withPredicate:textParentFilter groupBy:nil delegate:self inContext:c];
NSPredicate *folderParentFilter = [NSPredicate predicateWithFormat:#"parentFolder == %#", self.parent];
self.folders = [Folder MR_fetchAllSortedBy:#"title" ascending:YES withPredicate:folderParentFilter groupBy:nil delegate:self inContext:c];
And - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section:
if ([self.notes.sections count] + [self.folders.sections count] > 0) {
id <NSFetchedResultsSectionInfo> notesSectionInfo = [self.notes.sections objectAtIndex:section];
id <NSFetchedResultsSectionInfo> foldersSectionInfo = [self.folders.sections objectAtIndex:section];
return [notesSectionInfo numberOfObjects] + [foldersSectionInfo numberOfObjects];
} else
return 0;

Better way to count uncompleted reminders & update table view cell?

I'm using this to count all the uncompleted reminders in the defult list and put it in the detail of the table view cell:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
...
NSPredicate *predicate2 = [self.reminderStore predicateForIncompleteRemindersWithDueDateStarting:nil ending:nil calendars:#[[self.reminderStore defaultCalendarForNewReminders]]];
[self.reminderStore fetchRemindersMatchingPredicate:predicate2 completion:^(NSArray *reminders)
{
cell.detailTextLabel.text = [NSString stringWithFormat:#"%d", [reminders count]];
NSLog(#"count: %d", [reminders count]);
}];
...
return cell;
}
The problem is that because it's on another thread, it's not updating the table cell until it needs to reload it. (it does prints in the log). What's the best way of fixing that, so it would load automatically and won't need to load again every time? Also, is there another way to faster count reminders?

iOS - Add a cell to empty UITableView Section w/ Core Data and NSFetchedResultsController

I'm using Core Data and an NSFetchedResults controller to populate a UITableViewCell (plain style) in my app. The TableView has 3 sections, each with a viewForHeaderInSection and a viewForFooterInSection. The user will be entering items that will be put in each section.
When one of the sections has no items in it, it completely disappears. I'm wondering if it would be possible to make it so if a section is empty, it would display a new cell that would say "no entries" or something like that (maybe with a UIImageView or another view?), which would then go away if a new item was put into that section.
Does anyone know how to accomplish this?
Here's the code for my data source.
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return [[self.fetchedResultsController sections] count];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [[[self.fetchedResultsController sections] objectAtIndex:section] numberOfObjects];
}
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
{
return [[[self.fetchedResultsController sections] objectAtIndex:section] name];
}
Also, the section itself gets removed from the TableView when it has no rows, so how could I make it so the "no entries" cell gets added to the correct section?
If the section don't appear in the table view, it isn't returning from the fetch results, despite the fact you said you have 3 sections.
You can return it manually by:
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 3;
}
Then
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
if ([[[self.fetchedResultsController sections] objectAtIndex:section] numberOfObjects] == 0){
return 1;
} else {
return [[[self.fetchedResultsController sections] objectAtIndex:section] numberOfObjects];
}
}
In your cellForRowAtIndexPath: test for empty..
if ([[[self.fetchedResultsController sections] objectAtIndex:section] numberOfObjects] == 0){
cell.textLabel = #"No Entries";
}
Just a idea.
EDIT: Be careful, if you try to catch a index that isn't returned by the fetch, your app can crash.
You need to modify your numberOfRowsInSection function to check if there are no real rows for that section, and return 1 if that is the case (instead of 0).
Then, in the cellForRowAtIndexPath, check if you have data for that section, and just create your "no entries" cell when you see that you don't.

Adding a row in TableView iOS

I´m quite new to iOS development and I´m having a terrible time by trying something that should be easy; to add an extra row in a TableView everytime the user clicks on one of the existing rows. There is no real purpose on that action, I´m just wanting to understand the behaviour of TableView.
So I did the following:
I used a Split View-based template and changed the number of rows to 30 in the RootViewController.
- (NSInteger)tableView:(UITableView *)aTableView numberOfRowsInSection:(NSInteger)section {
// Return the number of rows in the section.
return 30;
}
The method tableView:didSelectRowAtIndexPath looks in the following manner:
- (void)tableView:(UITableView *)aTableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
/*
When a row is selected, set the detail view controller's detail item to the item associated with the selected row.
*/
NSMutableArray* paths = [[NSMutableArray alloc] init];
NSIndexPath *indice = [NSIndexPath indexPathForRow:30 inSection:0];
[paths addObject:indice];
detailViewController.detailItem = [NSString stringWithFormat:#"Second Story Element %d with all its information and bla bla bla", indexPath.row];
[[self tableView] beginUpdates];
[self.tableView insertRowsAtIndexPaths:(NSArray *) paths withRowAnimation:UITableViewRowAnimationNone];
[[self tableView] endUpdates];
}
When I execute the program and click on one of the elements, I receive the following error:
*** 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 (30) must be equal to the number of rows contained in that section before the update (30), plus or minus the number of rows inserted or deleted from that section (1 inserted, 0 deleted).'
I did not change any other part of the code that the template provides.
I read quite extensively the documentation from Apple and the responses to the following questions:
Add a row dynamically in TableView of iphone
and
how to properly use insertRowsAtIndexPaths?
The second question seems to address the same problem, but I´m not capable to understand what is happening. What do they mean with dataSource? The response that I understand better says the following:
It's a two step process:
First update your data source so numberOfRowsInSection and cellForRowAtIndexPath will return the correct values for your post-insert data. You must do this before you insert or delete rows or you will see the "invalid number of rows" error that you're getting.
What does this update of the data source implies?
Sample code would be HIGHLY appreciated, because I´m totally frustrated.
By the way, all that I´m trying has nothing to do with entering the editing mode, has it?
You need to keep the count returned by tableView:numberOfRowsInSection: in sync!
So when you have 30 rows and then tell the tableview to insert a new row you need to make sure tableView:numberOfRowsInSection: will now return 31.
- (NSInteger)tableView:(UITableView *)aTableView numberOfRowsInSection:(NSInteger)section
{
return self.rowCount;
}
- (void)tableView:(UITableView *)aTableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
self.rowCount++;
[self.tableView beginUpdates];
[self.tableView insertRowsAtIndexPaths:(NSArray *) paths withRowAnimation:UITableViewRowAnimationNone];
[self.tableView endUpdates];
}
In practice you would probably use an array to track your rows return [self.rows count]; etc
The answer is quite simple. When you want to modify a table view you need to perform two simple steps:
Deal with the model
Deal with table animation
You already perform the second step. But you have missed the first one. Usually when you deal with a table you pass it a data source. In other words some data to display within it.
A simple example is using a NSMutableArray (it's dynamic as the name suggests) that contains dummy data.
For example, create a property like the following in .h
#property (nonatomic, strong) NSMutableArray* myDataSource;
and in .m synthesize it as:
#synthesize myDataSource;
Now, you can alloc-init that array and populate it as the following (for example in viewDidLoad method of your controller).
self.myDataSource = [[NSMutableArray alloc] init];
[self.myDataSource addObject:#"First"];
[self.myDataSource addObject:#"Second"];
Then, instead of hardcoding the number of rows you will display (30 in your case), you can do the following:
- (NSInteger)tableView:(UITableView *)aTableView numberOfRowsInSection:(NSInteger)section {
return [self.myDataSource count];
}
Now, in you didSelectRowAtIndexPath delegate you can add a third element.
- (void)tableView:(UITableView *)aTableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
[self.myDataSource addObject:#"Third"];
[[self tableView] beginUpdates];
[self.tableView insertRowsAtIndexPaths:(NSArray *) paths withRowAnimation:UITableViewRowAnimationNone];
[[self tableView] endUpdates];
}
It looks like one big problem is with tableView:numberOfRowsInSection:. You need to return the correct number of rows in that method.
To do that, it's usually best to maintain an NSArray or NSMutableArray of items for the table view so in that function, you can say: return [arrayOfValues count];. Keep the array as a property of your view controller class so that it's readily accessible in all methods.
The array can also be used in cellForRowAtIndexPath:. If you have an array of NSString, you can say cell.text = [arrayOfValues objectAtRow:indexPath.row];.
Then, when you want to add an item to the table view, you can just add it to the array and reload the table, e.g. [tableView reloadData];.
Try implementing this concept and let me know how it goes.
You can Also do that for dayanamic table cell
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [arrayStationStore count];
}
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *cellIndentyfire;
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIndentyfire];
if (cell == nil)
{
cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIndentyfire];
}
cell.textLabel.text = [arrayStationStore objectAtIndex:indexPath.row];
return cell;
}
-(NSIndexPath *)tableView:(UITableView *)tableView willSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
// Check if current row is selected
BOOL isSelected = NO;
if([tblStationName cellForRowAtIndexPath:indexPath].accessoryType == UITableViewCellAccessoryCheckmark)
{
isSelected = YES;
}
if(isSelected)
{
[tblStationName cellForRowAtIndexPath:indexPath].accessoryType = UITableViewCellAccessoryNone;
[arrayReplace removeObject:indexPath];
NSLog(#"array replace remove is %# ",arrayReplace);
}
else
{
[tblStationName cellForRowAtIndexPath:indexPath].accessoryType = UITableViewCellAccessoryCheckmark;
[arrayReplace addObject:indexPath];
NSLog(#"array replace add is %# ",arrayReplace);
}
return indexPath;
}

Resources