Multiple sections in UITableView - objective-c

Actually i'm using only one section. I sort my data stored in core data by date.
I want to have two sections (latest and history). In my first section "latest" I want to put my latest date and in the other section "history" i want to put other dates sorted by date.
My table is editable and I'm using NSFetchedResultsController.
Here is my sample code for numberOfRowsInSection:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc]init];
[fetchRequest setEntity:[NSEntityDescription entityForName:#"Info"
inManagedObjectContext:self.managedObjectContext]];
// Define how we want our entities to be sorted
NSSortDescriptor* sortDescriptor = [[[NSSortDescriptor alloc]
initWithKey:#"date" ascending:NO] autorelease];
NSArray* sortDescriptors = [[[NSArray alloc] initWithObjects:sortDescriptor, nil] autorelease];
[fetchRequest setSortDescriptors:sortDescriptors];
NSString *lower = [mxData.name lowercaseString];
NSPredicate *predicate = [NSPredicate predicateWithFormat: #"(name = %#)", lower];
[fetchRequest setPredicate:predicate];
NSError *errorTotal = nil;
NSArray *results = [self.managedObjectContext executeFetchRequest:fetchRequest error:&errorTotal];
if (errorTotal) {
NSLog(#"fetch board error. error:%#", errorTotal);
}
return [results count];
[fetchRequest release];
[results release];
}

You need to modify your designated "UITableViewDataSource" object to return "2" for the "numberOfSectionsInTableView:" method.
Then you need to return the right thing in your "tableView:cellForRowAtIndexPath:" method, depending on the section designated in the index path.
If you want an optional section title (e.g. "History" or "Latest"), you can also return an array of section titles via sectionIndexTitlesForTableView:.

Implement - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 2;
}
This way the tableviewController know's how many sections to create. If you don't implement this method it will create the default number of sections which is 1.
This method is asked to the data source to return the number of sections in the table view.
The default value is 1.
The full method description can be found here
Update:
When the tableview is asking you which cell to display for a certain index path you can give the cell with the right data. Presuming you have 2 NSArray's containing the titles for the latest and history rows you could do the following:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
//create cell
static NSString *CellIdentifier = #"MyCellIdentifier";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if(indexPath.section == 0){
//set title for latest
NSString *title = [[self latestTitles] objectAtIndex:indexPath.row];
[[cell textLabel] setText:title];
}else{
//set title for history
NSString *title = [[self historyTitles] objectAtIndex:indexPath.row];
[[cell textLabel] setText:title];
}
//Update: add NSLog here to check if the cell is not nil..
NSLog(#"cell = %#", cell);
return cell;
}

Related

Populating table with 2 arrays

I'm trying to populate a tableview controller with objects from 2 different arrays but it crashes and gives this error
"Terminating app due to uncaught exception 'NSRangeException', reason: ' -[__NSArrayM objectAtIndex:]: index 1 beyond bounds [0 .. 0]"***
how do i fix this? below is my code:
(void)viewDidAppear:(BOOL)animated{
//[super viewDidAppear:<#animated#>];
NSManagedObjectContext *bookmanagedObjectContext = [self managedObjectContext];
NSFetchRequest *bookfetchRequest = [[NSFetchRequest alloc] initWithEntityName:#"Book"];
NSPredicate *bookpredicate =[NSPredicate predicateWithFormat:#" bookref.toproject contains[cd]%#",self.projectdb];
[bookfetchRequest setPredicate:bookpredicate];
NSSortDescriptor *booksortDescriptor = [[NSSortDescriptor alloc]initWithKey:#"authorSurname" ascending:YES];
NSArray *booksortDescriptors = [[NSArray alloc]initWithObjects:booksortDescriptor, nil];
[bookfetchRequest setSortDescriptors:booksortDescriptors];
self.BookrefArray = [[bookmanagedObjectContext executeFetchRequest:bookfetchRequest error:nil] mutableCopy];
NSManagedObjectContext *journalmanagedObjectContext = [self managedObjectContext];
NSFetchRequest *journalfetchRequest = [[NSFetchRequest alloc] initWithEntityName:#"Journal"];
NSPredicate *journalpredicate =[NSPredicate predicateWithFormat:#" journalref.toproj contains[cd]%#",self.projectdb];
[journalfetchRequest setPredicate:journalpredicate];
NSSortDescriptor *journalsortDescriptor = [[NSSortDescriptor alloc]initWithKey:#"surname" ascending:YES];
NSArray *journalsortDescriptors = [[NSArray alloc]initWithObjects:journalsortDescriptor, nil];
[journalfetchRequest setSortDescriptors:journalsortDescriptors];
self.JournalrefArray = [[journalmanagedObjectContext executeFetchRequest:journalfetchRequest error:nil] mutableCopy];
[self.tableView reloadData];}
(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
#warning Potentially incomplete method implementation.
// Return the number of sections.
return 1;
}
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
#warning Incomplete method implementation.
// Return the number of rows in the section.
return (self.BookrefArray.count + self.journalrefArray.count);
}
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"Cells";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
Journal *myjournal =[self.journalrefArray objectAtIndex:indexPath.row];
[cell.detailTextLabel setText:[myjournal valueForKey:#"journalname"]];
[cell.textLabel setText:[NSString stringWithFormat:#"%#, %#",[myjournal valueForKey:#"surname"],[myjournal valueForKey:#"firstname"]]];
Book *mybook =[self.BookrefArray objectAtIndex:indexPath.row];
// Configure the cell...
[cell.detailTextLabel setText:[mybook valueForKey:#"bookTitle"]];
[cell.textLabel setText:[NSString stringWithFormat:#"%#, %#",[mybook valueForKey:#"authorSurname"],[mybook valueForKey:#"authorOthernames"]]];
return cell;
}
You have this error because you try to access of an element of your array that is not exist.
In fact you says that your table has "table1.count + table2.count" elements and for each cell you try to get an element in your 2 tables with the current row.
For example, if table1 has 2 items and table2 has 5 items
, your tableView will have 7 rows and "cellForRowAtIndexPath" will be called 7 times. So for index 3 you will obtains this error because your table1 has only 2 items.
To solve the problem, you should get a book OR a journal in function of this row.
For example, you can try anything like this :
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"Cells";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
if (indexPath.row < self.BookrefArray.count)
{
Book *mybook =[self.BookrefArray objectAtIndex:indexPath.row];
// Configure your cell with a book
}
else
{
Journal *myjournal =[self.journalrefArray objectAtIndex:indexPath.row - self.BookrefArray.count];
// Configure your cell with a journal
}
return cell;
}
You can also change the order to display journals before books.
Answer :
You need to differentiate Book and Journal in -cellForRowAtIndexPath.
Explanation :
Your call for tableview's -cellForRowAtIndexPath will be dependent upon the number you're returning from tableview's -numberOfRowsInSection method.
So, for example, you've 3 objects in BookrefArray and 2 objects in journalrefArray, your tableview's -numberOfRowsInSection method will return 5 which means -cellForRowAtIndexPath will be called for 5 times.
Let's go through this line :
Journal *myjournal =[self.journalrefArray objectAtIndex:indexPath.row];
Here, it'll get indexPath.row = 5 for one case, and your journalrefArray contains only 2 objects. So, you're getting "index .. beyond bounds" error.
Update :
You can simply merge both the arrays into one.
[documentsArray addObjectsFromArray:self.BookrefArray];
[documentsArray addObjectsFromArray:self.journalrefArray];
and then in -cellForRowAtIndexPath, you can do something like this :
id document = [documentsArray objectAtIndex:indexPath.row];
if ([document isKindOfClass:Book])
{
Book *mybook = (Book *)document;
// Do something with mybook.
}
else
{
Journal *myjournal = (Journal *)document;
// Do something with myjournal.
}

update UITableView based on array

I have JSON file who's information I put in my table. I have one button for sorting my array
I can sort my array and print it with NSLog. How can I update my table based on my sorted array?
This is my code:
-(void)sortArry:(UIButton *)sender
{
NSSortDescriptor *ageDescriptor = [[NSSortDescriptor alloc] initWithKey:#"name" ascending:YES];
NSArray *sortDescriptors = #[ageDescriptor];
NSArray *sortedArray = [tableData sortedArrayUsingDescriptors:sortDescriptors];
//here i have my data in nslog
NSLog(#"%# sort test",sortedArray);
}
How can I show my sortedArray in the table?
I also used
[self.tableView reloadData];
after sorting but it didn't show the sorted table
Update:
Here is my cellForRowAtIndexPath
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath
*)indexPath
{
static NSString *CellIdentifier = #"Cell";
CustomCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
NSArray *objects = [[NSBundle mainBundle] loadNibNamed:#"CustomCell" owner:nil options:nil];
for (id currentObject in objects)
{
if([currentObject isKindOfClass:[UITableViewCell class]])
{
cell = (CustomCell *) currentObject;
break;
}
}
}
id keyValuePair;
if (tableView == self.searchDisplayController.searchResultsTableView)
{
keyValuePair = [self.filteredTableData objectAtIndex:indexPath.row];
}
else
{
keyValuePair = [self.tableData objectAtIndex:indexPath.row];
}
cell.name.text = keyValuePair[#"name"];
return cell;
}
sortedArray is deleted when it goes out of scope at the end of your sortArry: method. You need to set the property or instance variable that your UITableViewDelegate methods inspect.
Call [tableView reloadData]; after the array is sorted.
sortedArray needs to be a property of your viewController not an instance variable of sortArry , so when you reload all UITableViewdelegate method can access its values and update.
// The array You use to populate the table
#property NSArray ArrayDataSource;
Change the method signature to return the sorted array
-(NSArray *)sortArry
{
NSSortDescriptor *ageDescriptor =
[[NSSortDescriptor alloc] initWithKey:#"name" ascending:YES];
NSArray *sortDescriptors = #[ageDescriptor];
NSArray *sortedArray =
[tableData sortedArrayUsingDescriptors:sortDescriptors];
//here i have my data in nslog
NSLog(#"%# sort test",sortedArray);
return sortedArray;
}
Stored the return value in the datasource:
ArrayDataSource = [self.ArrayDataSource];
Finally reload the table
[self.tableView reloadData];

NSFetchedResultsController -> How to implement sections?

I am having trouble getting sections with a NSFetchedResultsController working. I have an entity, say 'Employee' and a string attribute, let's say 'name'. Now I want to show all employee's names in a UITableView using a NSFetchedResultsController... no problem, heres my Code:
if (_fetchedResultsController == nil) {
NSManagedObjectContext *moc = [appDelegate managedObjectContext];
NSFetchRequest *request = [NSFetchRequest new];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Employee" inManagedObjectContext:moc];
NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:#"name" ascending:YES];
[request setEntity:entity];
[request setSortDescriptors:[NSArray arrayWithObject:sortDescriptor]];
_fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:request managedObjectContext:moc sectionNameKeyPath:#"name" cacheName:#"root"];
NSError *error;
[_fetchedResultsController performFetch:&error];
if (error != nil) {
NSLog(#"Error: %#", error.localizedDescription);
}
}
But the NSFetchedResultsController creates a section for each entity. So when I have 200 employees, it creates 200 sections. Why?
And how do I implement those methords properly:
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
// Return the number of sections.
return [[_fetchedResultsController sections] count];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
// Return the number of rows in the section.
return [[[_fetchedResultsController sections] objectAtIndex:section] numberOfObjects];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
// Configure the cell...
cell.textLabel.text = [[_fetchedResultsController objectAtIndexPath:indexPath] valueForKey:#"name"];
return cell;
}
May I do understand the whole concept wrong? Where is the difference between [_fetchedResultsController sections] and [_fetchedResultsController sectionIndexTitles]?
I hope you can help me and thanks in advance!
EDIT: I forget to tell you the most important thing: I want to have sections separated by the first letter of the 'name' attribute. (Like the Music-APP).
Nick
Pass nil as your sectionNameKeyPath when initializing your NSFetchedResultsController.
If you pass name, you basically say "please create one section for each name". When passing nil, you tell it to create only a single section.
You tableview method implementations look right to me.
[_fetchedResultsController sections] gives you an array with objects that you can ask for things like the number of objects in a section. In contrast, [_fetchedResultsController sectionIndexTitles] is mostly so you can tell the NSFetchedResultsController which section titles to use (i.e you would set this to an array with one string for each section). In your case, you can just ignore it.

CoreDataTableViewConView.troller is not scrolling

I've spent literally weeks trying to get sections and rows to work in my table and have finally did it!
Next I noticed that even though I had plenty of data to view, I could not scroll down past what is first displayed on screen. Additionally, the scroll bar seems to be fatter than usual and there is a number 2 displayed in the upper right hand corner.
Not sure what I'm doing wrong. Can someone lead me nudge me in the right direction?
I couldn't capture the fat scroll bar, but it is definitely wider than it should be.
- (void)setupFetchedResultsController {
NSString *entityName = #"Regster";
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:entityName];
request.sortDescriptors = [NSArray arrayWithObject:[NSSortDescriptor sortDescriptorWithKey:#"addDate" ascending:YES selector:#selector(localizedCaseInsensitiveCompare:)]];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Regster" inManagedObjectContext:self.managedObjectContext];
[request setEntity:entity];
[request setResultType:NSDictionaryResultType];
[request setReturnsDistinctResults:YES];
//[request setFetchBatchSize:2];
self.fetchedResultsController.delegate = nil;
[request setPropertiesToFetch:[NSArray arrayWithObjects:#"addDate", #"regType", nil]];
NSString *query = self.selectedAccounts.name;
request.predicate = [NSPredicate predicateWithFormat:#"inAccounts.name CONTAINS[cd] %#", query];
self.fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:request managedObjectContext:self.managedObjectContext sectionNameKeyPath:#"addDate" cacheName:nil];
[self performFetch];
NSError *error = nil;
NSUInteger count = [_managedObjectContext countForFetchRequest:request error:&error];
}
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
[self setupFetchedResultsController];
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return [[self.fetchedResultsController sections] count];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
id sectionInfo = [[self.fetchedResultsController sections] objectAtIndex:section];
return [sectionInfo numberOfObjects];
}
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
id <NSFetchedResultsSectionInfo> sectionInfo = [[self.fetchedResultsController sections] objectAtIndex:section];
return [sectionInfo name];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Account Register";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:CellIdentifier];
}
[self.tableView setScrollEnabled:YES];
NSDictionary *regtype = [self.fetchedResultsController objectAtIndexPath:indexPath];
cell.textLabel.text = [regtype objectForKey:#"regType"];
return cell;
}
EDIT1: Changing #"addDate" of the sectionNameKeyPath of the fetchedResultsController, results in the removing of the dates and sections, leaving one section and the scroll works fine. Leaving the #"addDate" does what I want with sections, but I don't understand why it doesn't scroll with that 2 and a "fat" scroll.
EDIT2: I found my problem... I had borrowed code from another instructional course to get my CoreDataTableViewController working and it had implemented sectionIndexTitlesForTableView. Commented out and is working!
It's difficult not seeing your code or understanding what the data is. But I can help a little.
That 2 in the corner is your table view's section index column (that also might be what you mean by fat scroll bar). You've got 2 sections both starting with the number 2. If the section titles were words, you'd see an alphabetical index.
The fact that the index is only showing 1 value though may mean that your table view does not show the complete data set that you're expecting, only the 2 sections you have on the screen.
Perhaps show some code? Even just the setup code might be helpful. Typically the CoreDataTableViewController needs an NSFetchedResultsController, a title key and so on. Showing some of that code might provide more clues.
I found my problem... I had borrowed code from another instructional course to get my CoreDataTableViewController working and it had implemented sectionIndexTitlesForTableView. Did some massive searching and found this brought up somewhere. Commented out the sectionIndexTitlesForTableView method and it is working perfectly!

NSDictionary to TableView

because i'm a newby at Stackoverflow i cannot comment someones anwser yet. (my reputation is 16..). I got a question about this anwser: How do I put this JSON data into my table view? Please help me, I'm living in a nightmare :)
Fulvio sais you have to use [eventNameList addObject:event]; and [eventNameList objectAtIndex:indexPath.row]; to store and get the event data but. addObject is an NSMutableSet method and objectAtIndex:indexPath.row is not. So i cannot use this method to get the data from the NSMutableSet.
Besides that, i can use the count methods neither.
Any Idea's ?
Assuming you have an NSDictionary, you could use the [dictionary allKeys] method to retrieve an array with all keys (lets call it keyArray for now). For the rowCount you could return the count of objects in this keyArray. To get the item that needs to be displayed in the cell you could use [dictionary objectForKey:[keyArray objectAtIndex:indexPath.row]]] to get the appropriate dictionary for the displayed cell.
In code:
// use the keyArray as a datasource ...
NSArray *keyArray = [jsonDictionary allKeys];
// ------------------------- //
// somewhere else in your code ...
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [keyArray count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = (UITableViewCell *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
// set some cell defaults here (mainly design) ...
}
NSString *key = [keyArray objectAtIndex:indexPath.row];
NSDictionary *dictionary = [jsonDictionary objectForKey:key];
// get values from the dictionary and set the values for the displayed cell ...
return cell;
}
#Tieme: apparantly the URL you use already returns an array, you don't really need to process a dictionary (you could just use the array as the dataSource), check out the following:
SBJSON *json = [[[SBJSON alloc] init] autorelease];
NSURL *url = [NSURL URLWithString:#"http://www.my-bjoeks.nl/competitions/fetchRoutes/25.json"];
NSString *string = [[[NSString alloc] initWithContentsOfURL:url] autorelease];
NSError *jsonError = nil;
id object = [json objectWithString:string error:&jsonError];
if (!jsonError) {
NSLog(#"%#", object);
NSLog(#"%#", [object class]); // seems an array is returned, NOT a dictionary ...
}
// if you need a mutableArray for the tableView, you can convert it.
NSMutableArray *dataArray = [NSMutableArray arrayWithArray:object]
eventNameList should be defined as an NSMutableArray, not an NSMutableSet. NSMutableArray responds to both -addObject (it puts the new object at the end of the array) and -objectAtIndex: and when you think about it, a table view is essentially an ordered list and so is an array whereas a set is not.
LUCKY:)
Assuming that you might be having nsmutablearray of nsdictionary.
In such case you can get data using:
[dictionary objectforkey:#"key"] objectAtIndex:indexpath.row]