My Core Data model looks like this:
article <--->> category
Is it even remotely possible to use NSFetchedResultsController to produce a UITableView that looks something like this?
Category 1
- Article A
- Article B
- Article C
Category 2
- Article A
- Article D
- Article E
- Article F
Category 3
- Article B
- Article C
Specifically, I'm interested in the (edge?) case where each UITableView section has a unique title (eg, "Category 1", "Category 2"), but the same object can exist in multiple sections (eg, Article A exists in both Category 1 and Category 2).
I have scoured Apple's Core Data docs and have spent two days perusing questions here, but alas, no luck even finding out whether this is possible, let alone how to implement it. Thanks for any help or pointers to a previously answered question. I certainly couldn't find it.
Yes, it's easy, though there are a million ways to do it.
Your view controller should be the "data source" of the UITableView, and returns information about the number of rows there are and then the contents of each individual row.
There is a concept of a "section" in a tableview, you might choose to have one for each category.
For example, you could create an NSFetchedResultsController to find the categories you want to display, and use that to populate the table view sections, and then each category would have an articles many-to-many relationship, to populate the rows in each section.
Something like this should get you started (assuming your category and article entities both contain a title property):
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
// return the number of categories
[[self.categoryResultsController fetchedObjects] count];
}
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
{
// return the title of an individual category
[[self.categoryResultsController.fetchedObjects objectAtIndex:section] valueForKey:#"title"];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
// return the number of articles in a category
MyCategory *category = [self.categoryResultsController.fetchedObjects objectAtIndex:section];
return category.articles.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
// fetch a cached cell object (since every row is the same, we re-use the same object over and over)
static NSString *identifier = #"ArticleCellIdentifier";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:identifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identifier] autorelease];
}
// find the category and article, and set the text of the cell
MyCategory *category = [self.categoryResultsController.fetchedObjects objectAtIndex:indexPath.section];
cell.textLabel.text = [[category.articles objectAtIndex:indexPath.row] valueForKey:#"title"];
return cell;
}
You can read the documentation on these methods to figure out how to customise it further.
I'd be tempted to ditch NSFetchResultsController because I don't see much benefit here but I've not put too much thought into it so I could be wrong.
What you could do is:
Perform a fetch request for all category's and place them into an NSArray. These will be your sections.
For section count return the count of category's
For row count return the count of self.category.articles
Here's some example code for step 2 + 3
// 2
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView;
{
return [self.categories count];
}
// 3
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section;
{
return [[[self.categories objectAtIndex:section] articles] count];
}
Related
I have 2 UITableViewControllers in my project.
The problem I am having is that I am getting blank cell entries in the tableView opposite to the tableView where the data is entered.
I can't seem to figure out why this is the case.
It's creating blank rows in this tableView even though the information is from the other UITableViewController.
Here's the main tableView part from the one of the 2 UITableViewControllers:
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
NSLog(#"number of addedSpaceObjects %lu",(unsigned long)[self.diaryoptions count]);
// Return the number of sections.
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
NSLog(#"number of sections %ld",(long)section);
// Return the number of rows in the section.
return [self.diaryoptions count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentification = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentification
forIndexPath:indexPath];
Data2 *diary = [self.diaryoptions objectAtIndex:indexPath.row];
cell.textLabel.text = diary.diaryname;
cell.detailTextLabel.text = diary.diaryWeight;
return cell;
}
And from other UITableViewController:
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
NSLog(#"number of addedSpaceObjects %lu",(unsigned long)[self.addedSpaceObjects count]);
// Return the number of sections.
if ([self.addedSpaceObjects count]) {
return 2;
}
else {
return 1;
}
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
NSLog(#"number of sections %ld",(long)section);
// Return the number of rows in the section.
if (section == 1) {
return [self.addedSpaceObjects count];
}
else {
return [self.recipes count];
}
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentification = #"Josh";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentification
forIndexPath:indexPath];
if (indexPath.section == 1) {
Data *recipe = [self.addedSpaceObjects objectAtIndex:indexPath.row];
cell.textLabel.text = recipe.name;
}
else {
// Configure the cell...
Data *recipe = [self.recipes objectAtIndex:indexPath.row];
cell.textLabel.text = recipe.name;
}
return cell;
}
Here is the full project on GitHub. https://github.com/josher32/Plant-Diet
Appreciate any help anyone can offer!
Ok, so I checked out the app and I'll try my best to explain the problem as precisely as I can to cover it adequately.
Firstly, the classes in question are:
RecipesTableTableViewController
AddRecipeViewController
Data
DiaryTableViewController
AddDiaryViewController
Data2
Secondly, we'll need to look into your
#define ADDED_SPACE_OBJECTS2 #"Added Space Objects Array"
AddRecipeViewController
So... AddRecipeViewController basically creates a Data object that is kept in an array and eventually stored in NSUserDefaults under the key name Added Space Objects Array.
Great!! So you now have got recipe related stuff in some Data object.
AddDiaryViewController
Same thing here.
AddDiaryViewController creates a Data2 object that is eventually stored in NSUserDefaults under the same key name Added Space Objects Array.
But before storing this, you're taking the old value of the key Added Space Objects Array, which is an array, and adding a new object to it before placing it back into NSUserDefaults.
But now... this array will now have a combination of Data as well as Data2 objects!
RecipesTableTableViewController
When we come here, things get real.
- (void)viewDidLoad
{
//...
NSArray *myRecipeAsPropertyLists = [[NSUserDefaults standardUserDefaults] arrayForKey:ADDED_SPACE_OBJECTS_KEY];
for (NSDictionary *dictionary in myRecipeAsPropertyLists) {
Data *spaceObject = [self spaceObjectForDictionary:dictionary];
[self.addedSpaceObjects addObject:spaceObject];
}
}
Since we already realized that self.addedSpaceObjects can contain Data as well as Data2 objects, in the case whendictionary is containing stuff specific to type Data2, spaceObjectForDictionary will not be able to translate it properly to the required Data object.
We're expecting name, title, ingredients, directions but we're getting diaryentry, diaryname,diaryWeight.
So (in this scenario):
The values of name, title, ingredients, directions will be nil
The section-row count will be incorrect because it will give count of both Data as well as Data2 objects (and we don't care about Data2 objects in the RecipesTableTableViewController class... right?... well anyways, I assumed)
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
//...
if (indexPath.section == 1) {
Data *recipe = [self.addedSpaceObjects objectAtIndex:indexPath.row];
cell.textLabel.text = recipe.name;
}
//...
}
We see recipe.name is nil, for some indexPaths, ergo blank rows and vice versa in DiaryTableViewController.
Solution:
Firstly, I wouldn't recommend NSUserDefaults for your purposes but anyways...
Basically, don't use a single #"Added Space Objects Array" key for your NSUserDefaults stuff.
I'd suggest you use 2 separate keys.
//replace
//#define ADDED_SPACE_OBJECTS2 #"Added Space Objects Array"
//with
#define ADDED_SPACE_OBJECTS2 #"RecipeEntries" //in RecipesTableTableViewController
//and
#define ADDED_SPACE_OBJECTS2 #"DiaryEntries" //in DiaryTableViewController
Basically, segregate the entries instead of mixing them up under a single key name.
This seems like the quickest way to solve your problem without changing your logic.
I am creating an app where users can store an object in Core Data. I have the Objects being pulled into a UITableView and everything is working correctly there. I now want to separate the objects into a possible of 1-3 different sections based on choices in a UISegmentedControl.
Currently I have this to create the 1 section and populate the cells of that section with my objects
#pragma mark - Table view data source
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
// Return the number of sections.
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
// Return the number of rows in the section.
return self.fetchedDiscs.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"DiscCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
//Configure Cell
Disc *currentDisc = [self.fetchedDiscs objectAtIndex:indexPath.row];
cell.textLabel.text = currentDisc.name;
cell.detailTextLabel.text = currentDisc.brand;
return cell;
}
So the main question is how to dynamically change the number of sections and number of rows in section?
The Segmented control returns values such as Option 1, Option 2, Option 3. The only way I can think of is to loop through my fetchedDiscs array and separate that into an array for each section. Then I can return the number of arrays if they exists and I can get the count of each array to get the number of rows in each section. But then I get to the problem of How to get the CellForRowAtIndextPath to work correctly with three arrays.
Basically there has to be a better more logical way to do this. I am just not sure how.
Let, you have a dictionary named dataSource that contains 'x' number of array as value for key "key1", "key2", .. "keyX". You can do this:
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return [[dataSource allKeys] count];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
//Get the array object form key. I am considering 'keySection' as an example
return [[dataSource objectForKey:#"keySection"] count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"DiscCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
cell.textLabel.text = [[dataSource objectForKey:keySection] objectAtIndex:indexPath.row]
return cell;
}
Note: I wrote the code in this editor, excuse any mistakes, I just tried to share the idea. Hope this helps.. :)
I'm developing a contact list for studying purposes and this question came while studying.
Here's the problem: I'm configuring a cell for my TableView and one of my friends said that I should use (self.nsmutablearray)[indexPath.row], however I don't understand why I should use (self.nsmutablearray) before [indexPath.row]. I know it's a dumb question, but I just can't understand how exactly the programming paradigm works.
Here's the code (Contact is a NSObject and contactList is a NSMutablArray):
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"ContactCell" forIndexPath:indexPath];
Contact *contact = (self.contactList)[indexPath.row];
cell.textLabel.text = contato.name;
return cell;
}
It's index subscripting.
self.contactList represents an array of Contact objects. You need to fetch a contact at a certain row in the table so you can populate the cell with data. indexPath represents a section and row in a table, and indexPath.row represents just the row. Therefore, you can write self.contactList[indexPath.row] which retrieves the element at that index from the array.
Alternatively, you could write [self.contactList objectAtIndex:indexPath.row]
I have app in which I want to show the data in table view sections problem is that I do not know the total sections names as it depends on array. So how to show data for each section in cell for row at index path.
NSArray *test=[NSArray arrayWithObjects:#"June 20",#"July 20","May 1",#"May 10",nil];
So I want to show the data in the tableView sections date wise like if June 20 all the records of June 20 should be shown in one sections and in May 1 all the records for May same goes for all. Any idea how to solve this. Thanks
It's best to create a standard structure like (personally, i'd suggest the following):
Array that contains multiple dictionaries
Each dictionary contains 2 keys
Day is a key and, say, June 20, is it's value
Events is a key and, it's value is an array object
This array is a collection of strings that will basically be the content for these days
Example:
Possible UITableViewController subclass methods
- (void)viewDidLoad
{
[super viewDidLoad];
arrTest = #[
#{#"Day":#"June 20",
#"Events":#[#"Repair window pane", #"Buy food for Elephant", #"Pay credit card dues"]},
#{#"Day":#"July 20",
#"Events":#[#"Repair window frame", #"Buy alot more food for Elephant", #"Pay credit card dues dues"]},
#{#"Day":#"May 1",
#"Events":#[#"Replace entire window", #"Sell Elephant, Get Cat", #"Return credit card"]},
#{#"Day":#"May 10",
#"Events":#[#"Take bath", #"Shave", #"Get new credit card"]}
];
}
#pragma mark - Table view data source
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
//get count of main array (basically how many days)
return arrTest.count;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
//get count of number of events in a particular day
//old style
//return [[[arrTest objectAtIndex:section] objectForKey:#"Events"] count];
return [arrTest[section][#"Events"] count];
}
-(NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
{
//get value of the "Day" key (June 20 / July 20 ...)
//old style
//return [[arrTest objectAtIndex:section] objectForKey:#"Day"];
return arrTest[section][#"Day"];
}
- (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];
}
//old style
//NSString *strDayEvent = [[[arrTest objectAtIndex:indexPath.section] objectForKey:#"Events"] objectAtIndex:indexPath.row];
NSString *strDayEvent = arrTest[indexPath.section][#"Events"][indexPath.row];
[cell.textLabel setText:strDayEvent];
return cell;
}
To decide number of section and header title for particular section you need to do implement UITableView delegates methods.
-(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView{
return [test count];
}
-(NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section{
return [test objectAtIndex:section];
}
The section name and the row contents have a relationship...and therefore the most suitable way is to manage it with objects displaying relationship like the requirement
For your case an object/structured array will do the job for you with object has a string content to store the date text and the array storing the cell content data
Or use an NSDictionary with key as date and value as the cell content data as array
I am new to ios programming so bear with me if the question is simple. I have a core data table mapped to a table view controller. The data in it currently looks as follows - there is one prototype cell:
I need to sum up the data by dates and show the details of each date in a different section with the summed up total coming up as the first row. Something like:
My question is is this doable? I am thinking I need to create sections and two prototype cells within each table cell. Would appreciate quick feedback.
Thanks all!
The easy way to do this is using section headers. You can either use a single string (#"%#: %#", date, total) or a wrapper view with a label on the left for the date and on the right for the total.
-(NSString *) tableView:(UITableView *)tv titleForHeaderInSection:(NSInteger)s
{
NSString *dateString = [self dateStringForSection:s];
float total = [self totalForSection:s];
return [NSString stringWithFormat:#"%#: %0.2f", dateString, total];
}
Or
-(UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
{
return [self wrappedHeaderForSection:s];
}
You'll have to implement dateStringForSection: or wrappedHeaderForSection: appropriately, of course.
The easiest way is to style your UITableView to 'UITableViewStyleGrouped'.
UITableView *tab = [[UITableView alloc] initWithFrame:rect style:UITableViewStyleGrouped];
Or you can go to interface builder and in Table View change style from plain to grouped.
The style 'Grouped' divides your table into multiple sections.
The using UITableViewDelegate methods specify all the parameters.
// Tell the number of section in table
-(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return numberOfSections;
}
//Tell the number of rows in each section
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
if (section == 0)
{
return 2;
} else if(section == 1)...
}
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
if (indexPath.section == 0 && indexPath.row == 0)
{
//Show Amount for Jul 02, 2013
cell.textLabel.text = #"Jul 02, 2013";
cell.detailTextLabel = #"20.35";
}
// Do the same for all rows and section in table.
}
For further reference -
http://mobisoftinfotech.com/iphone-uitableview-tutorial-grouped-table/
You should also definitely check out the Sensible TableView framework. Saves me tons of time when working with Core Data.