UITableViewCell with UICollectionView - core-data

I'm following this tutorial Creating a Scrolling Filmstrip Within a UITableView
but instead of using static array i'm using core data with nsfetchresultscontroller i have a problem with delegate
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return 1;
}
i want to return only one item per cell which contains all items inside:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
ContainerCell *cell = [tableView dequeueReusableCellWithIdentifier:#"ContainerCell"];
NSArray *items = [[[self.frc sections] objectAtIndex:indexPath.section] objects];
[cell setCollectionData:items];
return cell;
}
but i'm getting this error:
CoreData: error: Serious application error. An exception was caught from the delegate of NSFetchedResultsController during a call to -controllerDidChangeContent:. attempt to delete row 1 from section 9 which only contains 1 rows before the update with userInfo (null)
if i'm using
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
NSInteger rows = [super tableView:tableView numberOfRowsInSection:section];
return rows;
}
the app is not crashing but i'm getting duplicates per cell
any ideas how to fix this
Thanks

You are using non-standard numbers of rows in your sections, so you have to make sure that when the underlying data changes, the appropriate number of rows is deleted and added. In your case, none.
Look for the delegate method in your controller where the rows are erroneously deleted or added. It is mentioned in your error message and should be
- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject
atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type
newIndexPath:(NSIndexPath *)newIndexPath
As you can see, in the delete case, it does this:
case NSFetchedResultsChangeDelete:
[tableView deleteRowsAtIndexPaths:#[indexPath]
withRowAnimation:UITableViewRowAnimationFade];
break;
But the indexPath is not describing the position of your managed object in the table view because you are displaying all rows of a section in a single row. So you can simply delete this line and replace it with the appropriate reload function for the first row in the section. Maybe you have a configureCell method as in the Xcode boilerplate code:
NSIndexPath *row = [NSIndexPath indexPathForRow:0 inSection:indexPath.section];
[tableView configureCell:[tableView cellForRowAtIndexPath:row] atIndexPath:row];

Related

Show UIImageView from image url in a table view cell

I want to show images in my table view's cells. I have created custom cells for table and every cell has a UIImageView.
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return 5;
}
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *cellIdentifier = #"autocompletelistcell";
Data *data=[datas objectAtIndex:indexPath.row];
CustomCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
UIImage *image=[UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:#"http://domain.com/image.jpg"]]];
cell.imageview.image=image;
cell.name=data.name;
return cell;
}
But it takes long time to load all images (actually only 5 images but different sizes) and doesn't show UITableView until loads all images.
Is it possible to show table and cells before completing loading of images? I guess I need multithreading?
I would look into using the AFNetworking libraries and in particular UIImageView+AFNetworking.
Then you can just do something like this and have the images load async...
[cell.imageview setImageWithURL:[NSURL URLWithString:#"http://domain.com/image.jpg"
placeholderImage:[UIImage imageNamed:#"placeholder.png"]];

NSFetchedResultsController: problems when having two table rows for one object

I've got a tableView where each data set has two rows, therefore my datasource-methods look like the following:
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return self.fetchedResultsController.sections.count;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)sectionIndex {
id <NSFetchedResultsSectionInfo> section = self.fetchedResultsController.sections[sectionIndex];
return section.numberOfObjects * 2;
}
The NSFetchedResultsControllerDelegate method:
- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath {
if (NSFetchedResultsChangeDelete == type) {
NSIndexPath *cellIndexPath = indexPath;
NSIndexPath *separatorIndexPath = [NSIndexPath indexPathForRow:indexPath.row + 1 inSection:indexPath.section];
[self.transactionTableView deleteRowsAtIndexPaths:#[cellIndexPath, separatorIndexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
} else if (NSFetchedResultsChangeInsert == type) {
NSIndexPath *cellIndexPath = newIndexPath;
NSIndexPath *separatorIndexPath = [NSIndexPath indexPathForRow:newIndexPath.row + 1 inSection:newIndexPath.section];
[self.transactionTableView insertRowsAtIndexPaths:#[cellIndexPath, separatorIndexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
} else {
[self.transactionTableView reloadData];
}
}
If I only use one row for each dataset, everything works as expected. But as soon as I make it this way, I get NSAssertionErrors here:
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller {
// The fetch controller has sent all current change notifications, so tell the table view to process all updates.
[self.tableView endUpdates];
}
Isn't it possible to use two rows for one data set? What am I doing wrong? I would have expected to work it this way as I always insert two indexPaths whenever the underlying core data changes.
That should definitely be possible, but your mapping from the FRC index path to the
table view index path looks wrong. It should probably be
NSIndexPath *cellIndexPath = [NSIndexPath indexPathForRow:(2*indexPath.row) inSection:indexPath.section];
NSIndexPath *separatorIndexPath = [NSIndexPath indexPathForRow:(2*indexPath.row + 1) inSection:indexPath.section];

Implementing a Custom Table View Cell (with labels and a custom size) using NSFetchedResultsController

I am working on a simple app which has the following premise:
TableViewController with a plus button - the user presses and is presented modally with text fields to enter to represent the name, title, etc. When they press save, the view controller disappears and adds those entries to the Core Data database and the entries are displayed in the table View.
I had this working very well with standard cells like subtitle, etc. I am using NSFetchedResultsController and I want to now deploy custom cells.
I changed the style to be a custom cell in the Storyboard and added three labels which would represent the name, title and amount (added by the user).
I created a custom class for the UITableViewCell and ensured that this table view cell was that custom reference class. I added the properties for the labels: nameLabel, amountLabel, titleLabel.
I imported this class into the full TableViewController class. Following tutorials online, it states to essentially just use the cellForRowAtIndexPath method with something like this:
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *cellIdentifier = #"TableCellID";
TableCell *tablecell = (TableCell *)[tableView dequeueReusableCellWithIdentifier:cellIdentifier];
NSDictionary * dict = [self.tweetsArray objectAtIndex:indexPath.row];
tablecell.title.text = [dict objectForKey:#"tweet"];
UIImage *imageIcon = [UIImage imageNamed:[dict objectForKey:#"image"]];
[tablecell.cellImage setImage:imageIcon];
return tablecell;
}
That is not my code but referenced from: http://www.codigator.com/tutorials/ios-uitableview-tutorial-custom-cell-and-delegates/ but the concept is the same.
Because I am using NSFetchedResultsController, I actually have two methods for the cell:
- (void)configureCell:(UITableViewCell *)cell atIndexPath:(NSIndexPath *)indexPath {
Transaction *transaction = [self.fetchedResultsController objectAtIndexPath:indexPath];
cell.nameLabel.text =[NSString stringWithFormat:#"%#", transaction.name];
cell.amountLabel.text = [NSString stringWithFormat:#"%#", transaction.amount];
cell.eventLabel.text = [NSString stringWithFormat:#"%#", transaction.title];
}
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"People";
TransactionCell *cell = (TransactionCell *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
// Set up the cell...
[self configureCell:cell atIndexPath:indexPath];
return cell;
}
This code does not work because the nameLabel, amountLabel is not actually recognised.
I have (like the code at the top) put all of the information into the cellForRowAtIndexPath, but then I get an error from the
- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath {
method because it has the following entry:
case NSFetchedResultsChangeUpdate:
[self configureCell:[tableView cellForRowAtIndexPath:indexPath] atIndexPath:indexPath];
break;
I have tried changing the method signature of the
- (void)configureCell:(UITableViewCell *)cell atIndexPath:(NSIndexPath *)indexPath {
to be:
- (void)configureCell:(TransactionCell *)cell atIndexPath:(NSIndexPath *)indexPath {
but that just does not seem right to me and there are warnings.
So basically I'm not trying to achieve anything complicated. I am simply trying to create a custom cell with three labels - why is the "cell" from the cellForRowAtIndexPath not being recognised in the configureCell method?
Finally, the custom cell has a specific size, bigger than normal and that does not appear when the app is run; it remains normal. I did this in the inspector of the cell but also added the following code to the end of the table view class:
- (void)searchDisplayController:(UISearchDisplayController *)controller didLoadSearchResultsTableView:(UITableView *)tableView
{
tableView.rowHeight = 80;
}
But it remained the normal height.
Any assistance on this would be massively appreciated!
Thanks,
You have two options:
Change the signature of configureCell to
- (void)configureCell:(TransactionCell *)cell atIndexPath:(NSIndexPath *)indexPath
and add an additional cast in
case NSFetchedResultsChangeUpdate:
[self configureCell:(TransactionCell *)[tableView cellForRowAtIndexPath:indexPath] atIndexPath:indexPath];
break;
Or leave the signature of configureCell as it is and cast inside the method:
- (void)configureCell:(UITableViewCell *)cell atIndexPath:(NSIndexPath *)indexPath {
TransactionCell *tcell = (TransactionCell *)cell;
Transaction *transaction = [self.fetchedResultsController objectAtIndexPath:indexPath];
tcell.nameLabel.text = transaction.name;
tcell.amountLabel.text = transaction.amount;
tcell.eventLabel.text = transaction.title;
}
(Note that all the stringWithFormat: calls are unnecessary.)
I would use the second method, but that is just a matter of taste.
This is because you probably forgot to call
self.tableView registerClass:forCellReuseIdentifier:
Or
self.tableView registerNib:forCellReuseIdentifier:
in your viewDidLoad (assuming self.tableView point to your UITableView)
by the way do not implement both! If you made a xib for your cell, use registerNib alone. If you created a UITableViewCell subclass, use registerClass alone.

Core data inserting rows and saving issue

I´m having some problems when saving data in core data and also with the rows organization and for better understanding of what my problem is, i´m going to explain what i have:
I have a main tableview working with dynamic rows, in this tableview i have a + button, whenever the + button is pressed, a tableview appears inside a popover were the user can choose the "type of cell" to insert in the main tableview. The "type of cell" are custom cells and they have they´re one class and xib file. Each custom cell has various textfields...so the idea is:
choose a type of cell and insert in the main tableview.
fill the textfields with data.
the data saved corresponds to the number of rows inserted and the data in the textfields.
When calling the popover tableview i have this method in my main tableview:
- (IBAction)Add:(id)sender
{
SelectProduct *typeProduct=[[self.storyboard instantiateViewControllerWithIdentifier:#"selectTable"]initWithTableViewTag:self.tableView.tag];
self.popover=[[UIPopoverController alloc]initWithContentViewController:typeProduct];
[popover presentPopoverFromBarButtonItem:buttonAdd permittedArrowDirections:UIPopoverArrowDirectionAny animated:YES];
typeProduct.popView = self.popover;
typeProduct.cellSelected = self.cellSelected; //cellSelected is core data subclass.
typeProduct.delegate = self;
typeProduct.managedObjectContext = self.managedObjectContext;
}
then in my didSelectRow of my popover tableview, i have:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
row = indexPath.row;
if (row == 0)
{
cellSelected=[NSEntityDescription insertNewObjectForEntityForName:#"CellSave" inManagedObjectContext:self.managedObjectContext];
cellSelected.nameCellData = #"Olive";
cellSelected.amountData = myCostumCell.amount.text;
}
From here, a cell is inserted in my main tableview, here´s my main tableview relevant methods:
- (void)viewDidLoad
{
[self.tableView registerNib:[UINib nibWithNibName:#"myCostumCellXib" bundle:nil] forCellReuseIdentifier:#"myCostumCell"];
AppDelegate *appDelegate =[[UIApplication sharedApplication] delegate];
self.managedObjectContext=[appDelegate managedObjectContext];
NSError *error = nil;
if (![self.fetchedResultsController performFetch:&error])
{
// Replace this implementation with code to handle the error appropriately.
// abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
[self fetchedResultsController];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"myCostumCell";
cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
cell.cellSelected = self.cellSelected;
cell.managedObjectContext = self.managedObjectContext;
if (cell == nil)
{
cell = [[MyCostumCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
cellSelected = [self.fetchedResultsController objectAtIndexPath:indexPath];
cell.nameCell.text = cellSelected.nameCellData;
if ([cellSelected.nameCellData isEqualToString:#"Olive"])
{
cell.amount.text = cellSelected.amountData;
// i have more textfields to assign but i think you understand the rest..
}
}
My fetchedResultsController method: ( also have the others but they are the standard stuff)
- (NSFetchedResultsController *)fetchedResultsController
{
if (_fetchedResultsController != nil)
{
return _fetchedResultsController;
}
// Create and configure a fetch request.
NSFetchRequest *fetchRequestCellSave = [[NSFetchRequest alloc] init];
NSEntityDescription *entityCellSave=
[NSEntityDescription entityForName:#"CellSave" inManagedObjectContext:self.managedObjectContext];
[fetchRequestCellSave setEntity:entityCellSave];
// Create the sort descriptors array.
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"nameCellData" ascending:YES];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];
[fetchRequestCellSave setSortDescriptors:sortDescriptors];
_fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequestCellSave managedObjectContext:self.managedObjectContext sectionNameKeyPath:#"nameCellData" cacheName:nil];
_fetchedResultsController.delegate = self;
self.fetchedResultsController = _fetchedResultsController;
return _fetchedResultsController;
}
Now if i want to exit the main tableview and go to another tableview, i understand that i have to save the content of the textfields in my managedObject, so:
- (void)viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
cellSelected.amountData = cell.amount.text;
//i have more textfields to assign but for the example i think you understand
what i want.
[self.managedObjectContext save:nil];
}
From here, one row is "saved" and the text in the amount also...but the problems start when i add one more row:
Why the new row appears on top of the tableview, instead of after the previous row inserted?
When i fill the textField (amount) of the second row inserted...exit the tableview and come back...the textfield doesn´t appears filled..What am i doing wrong?
The previous issue happens if i insert 2 rows at once, but if i insert one...exit the tableview and then come back and insert another row...the textfield amount is saved...
Where´s my problem? is it in my custom cell class? Where?...I´m sorry for the long post, but this is driving me crazy...
thanks for your time
Regards
You need to save the data as soon as it is entered, not just when the view is removed from display. As soon as a cell is scrolled off screen it will be reused or killed so you have to save your text before that happens. Best place is in the text field delegate callback when the text is changed.
When you add 2 rows before saving you have corrupted your internal state (when the second row is added but you haven't yet saved the data from the first row).
Your rows are sorted by entered text so it depends on the text (or lack of) to determine where it appears on screen.
You probably shouldn't be giving the cell a reference to the managed object context (not MVC).
You should also think about the difference between local and instance variables as your code seems to confuse them...

Multiple index paths

I need to implement two different index paths on my tableview in cellForRow at index path... One with data from my fetched results controller and another for simple text.
What is the best way to go about this.
Im not exactly sure what your asking, but it sounds like you want a table with one section containing results from an NSFetchedResultController, and another section with data from some other source. This is easy to do. If your data can doesn't need to be in multiple sections then it's very easy, just get data from fetchedResultController for section 0, and something else for section 1.
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView{
return 2;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
if(section == 0){
id sectionInfo = [[fetchedResultsController sections] objectAtIndex:section];
return [sectionInfo numberOfObjects];
}else{
return self.someArray.Count;
}
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
if(indexpath.section == 0){
static NSString *CellIdentifier = #"data cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
NSObject *foo = [self.fetchedResultController objectAtIndexPath:indexPath];
//configure cell
return cell;
}else{
static NSString *CellIdentifier = #"some other cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
//configure cell
return cell;
}
}

Resources