Weird Header Error when using NSFetchedResultsController - iphone

Since I can't really describe this error, I recorded my screen with the error.
http://www.youtube.com/watch?v=w2FqKKcL2Ck&feature=youtu.be
Basically I am not sure what to do. When the user finishes all the tasks in the Tasks To Complete section, I would like the section header to remain there...just with no objects in it (simply because its needed in order to add more tasks).
Luckily when the user doesn't have any Completed Tasks, the header there disappears which is what should happen. I would like the top header to not disappear though...
Also, same thing with the delete. I don't really want the Tasks To Complete section to disappear completely when the last object is deleted.
I've heard this is not possible unless you use a custom subclass like TAFetchResultsController, but I tried using that and it was just too complex for me to implement (and kind of broke my application rather than fix it). Maybe you guys have some suggestions?
Here is some relevant code:
Tasks core data properties
#interface Tasks : NSManagedObject
#property (nonatomic, retain) NSString *sectionString;
#end
#implementation Tasks
#dynamic sectionString;
#end
adding tasks to different sections
NSManagedObjectContext *context = self.managedObjectContext;
NSManagedObject *startingTask = [NSEntityDescription insertNewObjectForEntityForName:#"Tasks" inManagedObjectContext:context];
[startingTask setValue:#"Eat Dinner" forKey:#"taskName"];
[startingTask setValue:[NSNumber numberWithDouble:400] forKey:#"timeInterval"];
[startingTask setValue:#"Tasks To Complete" forKey:#"sectionString"];
NSManagedObject *finishedTask = [NSEntityDescription insertNewObjectForEntityForName:#"Tasks" inManagedObjectContext:context];
[finishedTask setValue:#"Do Laundry" forKey:#"taskName"];
[finishedTask setValue:[NSNumber numberWithDouble:400] forKey:#"timeInterval"];
[finishedTask setValue:#"Completed Tasks" forKey:#"sectionString"];
NSError *error;
if (![context save:&error]) {
NSLog(#"couldn't save: %#", [error localizedDescription]);
}
TableViewController.m:
-(void) viewDidLoad{
// ---Start Core Data With NSFetchedResultsController---
[super viewDidLoad];
NSError *error;
if (![[self fetchedResultsController] performFetch:&error]){
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
exit(-1);
}
// ---End Core Data w/ NSFetchedResultsController---
[self.tableView setDelegate:self];
[self setModalTransitionStyle:UIModalTransitionStyleCrossDissolve];
holdViewsArray = [[NSMutableArray alloc]init];
UIView *seperatorView;
UIView *seperatorView2;
NSString *sectionTitle = #"Tasks To Complete";
NSString *section2Title = #"Completed Tasks";
UILabel *label = [[UILabel alloc]init];
UILabel *label2 = [[UILabel alloc]init];
label.frame = CGRectMake(10.0, 5.0, 320.0, 50.0);
label.text = sectionTitle;
label2.frame = CGRectMake(10.0, 0.0, 320.0, 40.0);
label2.text = section2Title;
headerView = [[UIView alloc]initWithFrame:label.frame];
headerView2 = [[UIView alloc]initWithFrame:label2.frame];
CGRect sepFrame = CGRectMake(0, headerView.frame.size.height-2, 320, 1);
CGRect sep2Frame =CGRectMake(0, headerView2.frame.size.height-2, 320, 1);
seperatorView = [[UIView alloc] initWithFrame:sepFrame];
seperatorView2 = [[UIView alloc]initWithFrame:sep2Frame];
[headerView addSubview:seperatorView];
[headerView2 addSubview:seperatorView2];
[headerView addSubview:label];
[headerView addSubview:button];
[headerView2 addSubview:label2];
[holdViewsArray addObject:headerView];
[holdViewsArray addObject:headerView2];
- (NSFetchedResultsController *)fetchedResultsController {
if (_fetchedResultsController != nil) {
return _fetchedResultsController;
}
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription
entityForName:#"Tasks" inManagedObjectContext:managedObjectContext];
[fetchRequest setEntity:entity];
NSSortDescriptor *isCompleted = [[NSSortDescriptor alloc]initWithKey:#"sectionString" ascending:NO];
NSSortDescriptor *sort = [[NSSortDescriptor alloc]
initWithKey:#"dateCreated" ascending:YES];
[fetchRequest setSortDescriptors:#[isCompleted, sort]];
[fetchRequest setFetchBatchSize:20];
NSFetchedResultsController *theFetchedResultsController =
[[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
managedObjectContext:managedObjectContext sectionNameKeyPath:#"sectionString"
cacheName:nil];
self.fetchedResultsController = theFetchedResultsController;
_fetchedResultsController.delegate = self;
return _fetchedResultsController;
}-(void)configureCell:(UITableViewCell *)cell atIndexPath:(NSIndexPath *)indexPath {
Tasks *task = [_fetchedResultsController objectAtIndexPath:indexPath];
cell.textLabel.text = task.taskName.uppercaseString;
cell.detailTextLabel.text = [NSString stringWithFormat:#"%.0f", task.timeInterval];
cell.imageView.image = [UIImage imageNamed:#"unchecked.png"];
cell.imageView.highlightedImage = [UIImage imageNamed:#"uncheckedhighlighted.png"];
[cell setAccessoryType:UITableViewCellAccessoryDisclosureIndicator];
if (indexPath.section == 1)
[cell.contentView setAlpha:0.5];
else {
[cell.contentView setAlpha:1];
}
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc]initWithTarget:self action:#selector(handlechecking:)];
[cell.imageView addGestureRecognizer:tap];
cell.imageView.userInteractionEnabled = YES;
}
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
cellSubclassCell *cell = [tableView dequeueReusableCellWithIdentifier:#"UITableViewCell"];
if (!cell)
cell = [[cellSubclassCell alloc]initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:#"UITableViewCell"];
[self configureCell:cell atIndexPath:indexPath];
return cell;
}
-(void)handlechecking:(UITapGestureRecognizer *)t{
CGPoint tapLocation = [t locationInView:self.tableView];
NSIndexPath *tappedIndexPath = [self.tableView indexPathForRowAtPoint:tapLocation];
Tasks *task = [_fetchedResultsController objectAtIndexPath:tappedIndexPath];
if ([task.sectionString isEqual: #"Tasks To Complete"]){
task.sectionString = #"Completed Tasks";
} else if ([task.sectionString isEqualToString:#"Completed Tasks"]){
task.sectionString = #"Tasks To Complete";
}
[self.tableView reloadData];
NSTimeInterval time = [[NSDate date] timeIntervalSinceReferenceDate];
[task setDateCreated:time];
}
-(UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section{
switch (section) {
case 0:
return [holdViewsArray objectAtIndex:0];
break;
case 1:
return [holdViewsArray objectAtIndex:1];
break;
}
return 0;
}

NSFetchedResultsController infers sections from the objects returned by the fetch. So if the number of items a section goes to zero, the section vanishes. That's just how NSFetchedResultsController works.
Your simplest option is probably a 3rd-party framework. Take a look at TLIndexPathTools. It provides an alternative to NSFetchedResultsController that does not require Core Data or an NSFetchRequest, meaning you can use it in more scenarios. Take a look at some of the example projects (the Core Data one is called "Core Data")- some cool things are done with few lines of code.
TLIndexPathTools supports empty sections, but not explicitly in the way you need. However, you can accomplish it with a few lines of code by overriding TLIndexPathController. The code would look something like this:
#interface MyIndexPathController : TLIndexPathController
#end
#import "MyIndexPathController.h"
#implementation MyIndexPathController
- (void)setDataModel:(TLIndexPathDataModel *)dataModel
{
if ([dataModel sectionForSectionName:#"MyFirstSectionName"] == NSNotFound) {
TLIndexPathSectionInfo *firstSection = [[TLIndexPathSectionInfo alloc] initWithItems:nil andName:#"MyFirstSectionName"];
NSMutableArray *sectionInfos = [NSMutableArray arrayWithArray:dataModel.sections];
[sectionInfos insertObject:firstSection atIndex:0];
dataModel = [[TLIndexPathDataModel alloc] initWithSectionInfos:sectionInfos andIdentifierKeyPath:dataModel.identifierKeyPath andCellIdentifierKeyPath:dataModel.cellIdentifierKeyPath];
}
super.dataModel = dataModel;
}
#end
What is happening here is we intercept the data model generated by the controller (based on the results of our fetch request) and check if the section of interest is present. If not, we create an empty section info object and combine it with the other section infos into a new data model and pass it along to the super class.
It should not be too difficult to extend generically to work with any set of required sections if needed.

Related

not sure why NSFetchedResultsController isn't showing sections

I previously had a TableView which would get its 2 header views (and separator views) from an array called holdViewsArray in viewDidLoad, like so:
-(void) viewDidLoad{
// ---Start Core Data With NSFetchedResultsController---
[super viewDidLoad];
NSError *error;
if (![[self fetchedResultsController] performFetch:&error]){
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
exit(-1);
}
// ---End Core Data w/ NSFetchedResultsController---
[self.tableView setDelegate:self];
[self setModalTransitionStyle:UIModalTransitionStyleCrossDissolve];
holdViewsArray = [[NSMutableArray alloc]init];
UIView *seperatorView;
UIView *seperatorView2;
NSString *sectionTitle = #"Tasks To Complete";
NSString *section2Title = #"Completed Tasks";
UILabel *label = [[UILabel alloc]init];
UILabel *label2 = [[UILabel alloc]init];
label.frame = CGRectMake(10.0, 5.0, 320.0, 50.0);
label.text = sectionTitle;
label2.frame = CGRectMake(10.0, 0.0, 320.0, 40.0);
label2.text = section2Title;
headerView = [[UIView alloc]initWithFrame:label.frame];
headerView2 = [[UIView alloc]initWithFrame:label2.frame];
CGRect sepFrame = CGRectMake(0, headerView.frame.size.height-2, 320, 1);
CGRect sep2Frame =CGRectMake(0, headerView2.frame.size.height-2, 320, 1);
seperatorView = [[UIView alloc] initWithFrame:sepFrame];
seperatorView2 = [[UIView alloc]initWithFrame:sep2Frame];
[headerView addSubview:seperatorView];
[headerView2 addSubview:seperatorView2];
[headerView addSubview:label];
[headerView addSubview:button];
[headerView2 addSubview:label2];
[holdViewsArray addObject:headerView];
[holdViewsArray addObject:headerView2];
}
After implementing NSFetchedResultsController, I decided that I wanted to use NSFetchedResultsController's properties and methods to make the header views and split the table view into 2 sections. I've tried to do so, but its not giving me any titles in my sections and it just looks awful. How would I keep the same look of the headers that I have now but use NSFetchedResultsController to split the table view into 2 sections (complete and incomplete tasks)? Here is what I tried:
Tasks Core Data property (optional property):
#interface Tasks : NSManagedObject
#property (nonatomic, retain) NSString *sectionString;
#end
#implementation Tasks
#dynamic sectionString;
#end
Adding two tasks in different sections in the App Delegate
NSManagedObjectContext *context = self.managedObjectContext;
NSManagedObject *startingTask = [NSEntityDescription insertNewObjectForEntityForName:#"Tasks" inManagedObjectContext:context];
[startingTask setValue:#"Eat Dinner" forKey:#"taskName"];
[startingTask setValue:[NSNumber numberWithDouble:400] forKey:#"timeInterval"];
[startingTask setValue:#"Tasks To Complete" forKey:#"sectionString"];
NSManagedObject *finishedTask = [NSEntityDescription insertNewObjectForEntityForName:#"Tasks" inManagedObjectContext:context];
[finishedTask setValue:#"Do Laundry" forKey:#"taskName"];
[finishedTask setValue:[NSNumber numberWithDouble:400] forKey:#"timeInterval"];
[finishedTask setValue:#"Completed Tasks" forKey:#"sectionString"];
NSError *error;
if (![context save:&error]) {
NSLog(#"couldn't save: %#", [error localizedDescription]);
}
Splitting into two sections
- (NSFetchedResultsController *)fetchedResultsController {
if (_fetchedResultsController != nil) {
return _fetchedResultsController;
}
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription
entityForName:#"Tasks" inManagedObjectContext:managedObjectContext];
[fetchRequest setEntity:entity];
NSSortDescriptor *isCompleted = [[NSSortDescriptor alloc]initWithKey:#"sectionString" ascending:NO];
NSSortDescriptor *sort = [[NSSortDescriptor alloc]
initWithKey:#"dateCreated" ascending:NO];
[fetchRequest setSortDescriptors:#[isCompleted, sort]];
[fetchRequest setFetchBatchSize:20];
NSFetchedResultsController *theFetchedResultsController =
[[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
managedObjectContext:managedObjectContext sectionNameKeyPath:#"sectionString"
cacheName:#"Root"];
self.fetchedResultsController = theFetchedResultsController;
_fetchedResultsController.delegate = self;
return _fetchedResultsController;
}
What am I doing wrong that's causing it to not split into 2 sections with titles of "Tasks To Complete" and "Completed Tasks"?

Not Able to Retrieve data using core data

I am using coredata in my application to store data.i have to add the data in one view controller and retrieve it in another view controller.i tried the following code but it is not working.
//addViewController.m
-(IBAction)save:(id)sender
{
CoreDataOneAppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];
NSManagedObjectContext *context = [appDelegate managedObjectContext];
NSManagedObject *newContact;
newContact = [NSEntityDescription insertNewObjectForEntityForName:#"Employee"
inManagedObjectContext:context];
[newContact setValue:name.text forKey:#"name"];
[newContact setValue:amount.text forKey:#"amount"];
name.text = #"";
amount.text = #"";
//label
status.text = #"saved";
NSError *error;
[context save:&error];
}
I want to retrieve the values and display them in a tableView
//retrieveViewController.m
- (void)viewDidLoad
{
objects = [[NSArray alloc]init];
CoreDataOneAppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];
NSManagedObjectContext *context = [appDelegate managedObjectContext];
NSEntityDescription *entityDesc = [NSEntityDescription entityForName:#"Employee"
inManagedObjectContext:context];
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:entityDesc];
NSError *error;
objects = [context executeFetchRequest:request
error:&error];
[request release];
[super viewDidLoad];
}
//tableView
-(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [objects count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
labelOne = [[UILabel alloc]initWithFrame:CGRectMake(5, 11, 110, 21)];
labelTwo = [[UILabel alloc]initWithFrame:CGRectMake(230, 11, 70, 21)];
static NSString *cellIdentifier = #"CellIdentifier";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithFrame:CGRectZero reuseIdentifier:cellIdentifier]autorelease];
}
[cell.contentView addSubview:labelTwo];
[cell.contentView addSubview:labelOne];
NSManagedObject *matches = nil;
matches = [objects objectAtIndex:indexPath.row];
NSString *str1=[NSString stringWithFormat:#"%#",[matches valueForKey:#"name"]];
labelOne.text = str1;
NSString *str2=[NSString stringWithFormat:#"%#",[matches valueForKey:#"amount"]];
labelTwo.text = str2;
return cell;
}
I am getting EXC_BAD_ACCESS error.i tried using NSZombieEnabled and i got the following error.
2012-04-27 11:59:18.153 CoreDataOne[4370:207] *** -[_PFArray objectAtIndex:]: message sent to deallocated instance 0x5931e40
i am able to retrieve the values if write the code what i have written in viewDidLoad in cellForRowAtIndexPath but how to declare the numberOfRows.
It looks like you are not using ARC. I think you need to retain the result of your fetch request in viewDidLoad (don't forget to release it in dealloc). Also, you are leaking by alloc/initing an array and then overwriting it.
Store the values in MutableArray in view didload where you are retrieving values. And then use it in your table view. Declare the noof Rows as array count.

FetchedResultsController not being called

I have the standard fetchedResultsController method and delegate methods in my viewController but they are not being called. I tested this by putting an NSLog in this method and the console never shows this.
I made sure to add FetchedResultsController delegate to .h and .m.
Any ideas why?
Interface
#interface LogViewController : UIViewController <NSFetchedResultsControllerDelegate, UITableViewDelegate, UITableViewDataSource>
#property (nonatomic, retain) NSFetchedResultsController *fetchedResultsController;
#property (nonatomic, retain) NSManagedObjectContext *managedObjectContext;
#property (nonatomic, retain) NSArray *logArray;
#property (nonatomic, retain) UIImageView *imageView;
#property (nonatomic, retain) Session *session;
#property (nonatomic, retain) IBOutlet UITableView *logTableView;
#end
Implementation
#import "LogViewController.h"
#implementation LogViewController
#synthesize fetchedResultsController = __fetchedResultsController;
#synthesize managedObjectContext;
#synthesize logArray;
#synthesize logTableView;
#synthesize imageView;
#synthesize session;
- (void)dealloc
{
[logArray release];
[logTableView release];
[session release];
[__fetchedResultsController release];
[managedObjectContext release];
[super dealloc];
}
#pragma mark - View lifecycle
- (void)viewDidLoad
{
[super viewDidLoad];
self.navigationItem.title = #"Log";
logTableView.backgroundColor = [UIColor clearColor];
logTableView.separatorColor = [UIColor blackColor];
self.navigationController.navigationBar.tintColor = [UIColor colorWithRed:24/255.0 green:83/255.0 blue:170/255.0 alpha:1.0];
self.logArray = [[NSArray alloc]initWithObjects:#"Today's Workout", #"Last Workout", #"Past Week", #"Past Month", #"All Workouts", nil];
if (managedObjectContext == nil)
{
self.managedObjectContext = [(CurlAppDelegate *)[[UIApplication sharedApplication] delegate] managedObjectContext];
}
}
- (void)viewDidUnload
{
self.logArray = nil;
self.logTableView = nil;
[super viewDidUnload];
}
#pragma mark - Table view data source
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [self.logArray count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
TDBadgedCell *cell = [[[TDBadgedCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier] autorelease];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:CellIdentifier] autorelease];
}
cell.textLabel.textColor = [UIColor blackColor];
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
cell.textLabel.text = [logArray objectAtIndex:indexPath.row];
cell.backgroundColor = [UIColor clearColor];
UIImageView *myImageView = nil;
if (indexPath.row == 0)
{
myImageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"customcell_background_top.png"]];
}
else if (indexPath.row == 4)
{
myImageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"customcell_background_bottom.png"]];
}
else
{
myImageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"customcell_background_middle.png"]];
}
[cell setBackgroundView:myImageView];
[myImageView release];
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateFormat:#"MMM d, y"];
NSDate *date = nil;
if (indexPath.row == 0)
{
date = [NSDate date];
NSString *dateString = [dateFormatter stringFromDate:date];
cell.badgeString = dateString;
}
else if (indexPath.row == 1)
{
self.session = [[__fetchedResultsController fetchedObjects]lastObject];
NSDate *date = self.session.timeStamp;
NSString *dateString = [dateFormatter stringFromDate:date];
cell.badgeString = dateString;
}
else if (indexPath.row > 1)
{
cell.badgeString = [NSString stringWithFormat:#"%i", [[__fetchedResultsController fetchedObjects]count]];
}
cell.badgeColor = [UIColor colorWithRed:24/255.0 green:83/255.0 blue:170/255.0 alpha:1.0];
[dateFormatter release];
return cell;
}
-(void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
{
// [cell setBackgroundColor:[UIColor colorWithRed:209/255.0 green:209/255.0 blue:209/255.0 alpha:1.0]];
}
#pragma mark - Table view delegate
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
[tableView deselectRowAtIndexPath:indexPath animated:YES];
if (indexPath.row == 0 || indexPath.row == 1)
{
SessionViewController *detailViewController = [[SessionViewController alloc] initWithNibName:#"SessionViewController" bundle:nil];
detailViewController.title = [logArray objectAtIndex: indexPath.row];
[self.navigationController pushViewController:detailViewController animated:YES];
[detailViewController release];
}
else
{
LogResultsViewController *detailViewController = [[LogResultsViewController alloc] initWithNibName:#"LogResultsTableViewController" bundle:nil];
detailViewController.title = [logArray objectAtIndex: indexPath.row];
[self.navigationController pushViewController:detailViewController animated:YES];
[detailViewController release];
}
}
#pragma mark - Fetched results controller
- (NSFetchedResultsController *)fetchedResultsController
{
if (__fetchedResultsController != nil)
{
return __fetchedResultsController;
}
// Create the fetch request for the entity.
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
// Edit the entity name as appropriate.
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Session" inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];
// Set the batch size to a suitable number.
[fetchRequest setFetchBatchSize:20];
// Edit the sort key as appropriate.
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"timeStamp" ascending:NO];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];
[fetchRequest setSortDescriptors:sortDescriptors];
// Edit the section name key path and cache name if appropriate.
// nil for section name key path means "no sections".
NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:self.managedObjectContext sectionNameKeyPath:nil cacheName:nil];
aFetchedResultsController.delegate = self;
self.fetchedResultsController = aFetchedResultsController;
[aFetchedResultsController release];
[fetchRequest release];
[sortDescriptor release];
[sortDescriptors release];
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. If it is not possible to recover from the error, display an alert panel that instructs the user to quit the application by pressing the Home button.
*/
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
return __fetchedResultsController;
NSLog(#"Number of Objects = %i",
[[__fetchedResultsController fetchedObjects] count]);
}
#pragma mark - Fetched results controller delegate
- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller
{
[self.logTableView beginUpdates];
}
- (void)controller:(NSFetchedResultsController *)controller didChangeSection:(id <NSFetchedResultsSectionInfo>)sectionInfo
atIndex:(NSUInteger)sectionIndex forChangeType:(NSFetchedResultsChangeType)type
{
switch(type)
{
case NSFetchedResultsChangeInsert:
[self.logTableView insertSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeDelete:
[self.logTableView deleteSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade];
break;
}
}
- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject
atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type
newIndexPath:(NSIndexPath *)newIndexPath
{
UITableView *tableView = self.logTableView;
switch(type)
{
case NSFetchedResultsChangeInsert:
[tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeDelete:
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeUpdate:
[self configureCell:[tableView cellForRowAtIndexPath:indexPath] atIndexPath:indexPath];
break;
case NSFetchedResultsChangeMove:
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
[tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath]withRowAnimation:UITableViewRowAnimationFade];
break;
}
}
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller
{
[self.logTableView endUpdates];
}
#end
replace calls like this
self.session = [[__fetchedResultsController fetchedObjects]lastObject];
with this
self.session = [[self.fetchedResultsController fetchedObjects]lastObject];
I.E. Use the getter that lazily loads the NSFetchedResultsController.
If you use lazy loading you have to use the getter, because the instance variable will stay nil if you never invoke the getter that creates the object.

iphone slow navigation due to data load

I'm newbie to objective-c . My problem is slow navigation between controllers.
When I jump from one controller to another it's really slow, because it gets data from the service, parse it and then display it. All this takes around 4-5 secs.
Now I've put all this data fetching in loadView of my DetailController. Is there anyway that I show the controller first and then display the data. Because as of now, when I select a row on my root controller, it shows activity and stays on root controller and then it navigates to the DetailController.
So it loads the data first and then displays it. Is there anyway that I can reverse this process, show the controller first and then display it..
this is what I'm doing
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
DataCollection *collection = [DataCollection sharedObject];
Product *tempProduct = [[collection popularProducts] objectAtIndex:row] ;
ProductDetailViewController *tempProductDetail = [[ProductDetailViewController alloc] init];
tempProductDetail.homeViewProduct = tempProduct;
[tempProductDetail release];
}
and then the Detail Controller
- (void)loadView {
[super loadView];
// here's all the data loading and parsing ... which takes time
}
//Here's the method for downloading the data, I'm using a REST service to get all the data.
- (void) getProductData:(NSString*)productId{
NSMutableString *specificURL = [[NSMutableString alloc] initWithString:#"GetProductPage?id="];
[specificURL appendFormat:#"%#",productId];
[specificURL appendFormat:#"&dataSources=&accountId=&companyId=&version=1&cultureCode=sv&currencyId=2&format=json"];
NSDictionary *serverCategories = [[NSDictionary alloc] initWithDictionary:[appRequestController proceesRequest:specificURL]];
NSArray *categories = [[[serverCategories objectForKey:#"DataSources"] objectAtIndex:0] objectForKey:#"Items"];
[serverCategories release];
if ([[productsArray objectAtIndex:j] objectForKey:#"Name"] != [NSNull null]) {
[product setProductName:[[productsArray objectAtIndex:j] objectForKey:#"Name"]];
}
else {
[product setProductName:#""];
}
if ([[productsArray objectAtIndex:j] objectForKey:#"ThumbnailImage"] != [NSNull null]) {
[product setProductImage:[[productsArray objectAtIndex:j] objectForKey:#"ThumbnailImage"]];
}
else {
[product setProductImage:#""];
}
if ([[productsArray objectAtIndex:j] objectForKey:#"Price"] != [NSNull null]) {
[product setProductPrice:[[productsArray objectAtIndex:j] objectForKey:#"Price"]];
}
else {
[product setProductPrice:#""];
}
[[collection popularProducts] addObject:product];
[product release];
}
I've copy pasted junks of the code so that you can get an idea how I'm getting the data. Below is the code for cellForRowAtIndexPath:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
NSString *MyIdentifier = #"mainMenuIdentifier";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:MyIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:MyIdentifier] autorelease];
[cell setSelectedBackgroundView:[[UIImageView alloc] initWithImage:[UIImage imageNamed:#"highlightstrip.png"]]];
}
UILabel *productNameLbl = [[UILabel alloc] initWithFrame:CGRectMake(90, 8, 200,10)];
[productNameLbl setText:[NSString stringWithString:[[[[DataCollection sharedObject] popularProducts]objectAtIndex:indexPath.row] productName]]];
[productNameLbl setFont:[UIFont boldSystemFontOfSize:12.0]];
[cell addSubview:productNameLbl];
[productNameLbl release];
UILabel *productSubLbl = [[UILabel alloc] initWithFrame:CGRectMake(90, 22, 190,40)];
[productSubLbl setText:[NSString stringWithString:[[[[DataCollection sharedObject] popularProducts]objectAtIndex:indexPath.row] productSubHeader]]];
[productSubLbl setTextColor:[UIColor colorWithRed:102.0/255.0 green:102.0/255.0 blue:102/255.0 alpha:1.0]];
productSubLbl.lineBreakMode = UILineBreakModeWordWrap;
productSubLbl.numberOfLines = 3;
[productSubLbl setFont:[UIFont fontWithName:#"Arial" size:10]];
[cell addSubview:productSubLbl];
[productSubLbl release];
NSMutableString *url = [[NSMutableString alloc] initWithString:#""];
NSString *baseImageURL = #"http://samplesite.com/";
NSString *imagePath = [NSString stringWithString:[[[[DataCollection sharedObject] popularProducts]objectAtIndex:indexPath.row] productImage]];
[url appendString:baseImageURL];
[url appendString:imagePath];
NSURL *imageURL = [NSURL URLWithString:url];
NSData *data = [NSData dataWithContentsOfURL:imageURL];
UIImage *img = [[UIImage alloc] initWithData:data cache:NO];
UIImageView *imageView = [[UIImageView alloc] initWithImage:img];
[imageView setFrame:CGRectMake(10, 10, 70, 70)];
[cell addSubview:imageView];
[imageView release];
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
return cell;
}
Pass the url or product id to the detail view. Inside the viewDidLoad of the ProductDetail, create a background thread to retrieve your data. When you background thread finish, it will notify your ProductDetail controller with the detail. At that time, you update your ProductDetailView.
To get your data on a background thread you can use
NSThread
NSOperation
Pass the row into your ProductDetailViewController, and do the fetch in it's viewDidLoad method. You might also want to show a spinner (UIActivityIndicator), and you definitely should read up on background threads.
To make view available to user and loading data after some time you can use NSTimer.
Just have a NSTimer method which will be called in viewDidLoad method as
[NSTimer scheduledTimerWithTimeInterval:<#(NSTimeInterval)ti#> target:<#(id)aTarget#> selector:<#(SEL)aSelector#> userInfo:<#(id)userInfo#> repeats:<#(BOOL)yesOrNo#>]
put some time interval which will be calling your loading method after that.
Hope this helps.

CoreData leaks when view is destroyed

I'm trying to finish an app but I'm having some memory leaks with CoreData when I delete the view from the navigation stack even though I released everything I created.
Basically the following method is called by the view below it.
+ (NSMutableArray *)getStoriesForSubscription:(Subscriptions *)s {
NSFetchRequest *request = [[NSFetchRequest alloc] init];
NSEntityDescription *storiesEntity = [NSEntityDescription entityForName:#"Articles" inManagedObjectContext:ikub.context];
[request setEntity:storiesEntity];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"(belongsTo == %#)", s];
[request setPredicate:predicate];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"pubDate" ascending:NO selector:nil];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];
[request setSortDescriptors:sortDescriptors];
NSError *error = nil;
NSMutableArray *stories = (NSMutableArray*)[ikub.context executeFetchRequest:request error:&error];
if (![ikub.context save:&error]) { NSLog(#"Cannot fetch the folders from the fetch request."); }
[sortDescriptors release];
[sortDescriptor release];
[request release];
return stories;
}
#implementation SubscriptionStories
#synthesize storiesTable, stories, subscription;
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return [stories count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"cell"];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:#"cell"];
}
int index = [indexPath row];
cell.textLabel.text = [[stories objectAtIndex:index] title];
cell.detailTextLabel.text = [[stories objectAtIndex:index] desc];
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
if ([[[stories objectAtIndex:index] read] isEqualToNumber:[NSNumber numberWithBool:NO]]) {
cell.textLabel.textColor = [UIColor blackColor];
} else {
cell.textLabel.textColor = [UIColor grayColor];
}
return cell;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
StoryDetails *details = [[StoryDetails alloc] init];
details.title = #"Detaje";
details.t = [[stories objectAtIndex:[indexPath row]] title];
details.d = [[stories objectAtIndex:[indexPath row]] desc];
details.l = [[stories objectAtIndex:[indexPath row]] link];
details.g = [[stories objectAtIndex:[indexPath row]] guid];
details.p = (NSString *)[[stories objectAtIndex:[indexPath row]] pubDate];
[SubscriptionsController setStoryAsRead:[[stories objectAtIndex:[indexPath row]] link] forSubscription:subscription];
[self.navigationController pushViewController:details animated:YES];
[details release];
}
- (void)viewDidLoad {
[super viewDidLoad];
}
- (void)viewWillAppear:(BOOL)animated {
stories = [[SubscriptionsController getStoriesForSubscription:subscription] retain];
[storiesTable reloadData];
}
- (void)viewWillDisappear:(BOOL)animated {
[stories release];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
}
- (void)viewDidUnload {
[super viewDidUnload];
}
- (void)dealloc {
[subscription release];
[super dealloc];
}
Instruments says that the leak happens in this line:
stories = [[SubscriptionsController getStoriesForSubscription:subscription] retain];
If you have declared the property stories with retain then the extra retain is not necessary.
self.stories = [SubscriptionsController getStoriesForSubscription:subscription];
my suggestions:
remove (another?) leak by changing your UITableViewCell creation to return an autoreleased cell
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
...
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:#"cell"] autorelease];
...
}
if that didn't help. (I had leaks were instruments was miles away from the actual leak). change your viewWillDisappear method to something like this
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
[stories release];
stories = nil;
}
and add another release for stories in dealloc
- (void)dealloc {
[subscription release];
[stories release];
[super dealloc];
}
Maybe there are obscure ways that a dealloc happens without calling the viewWillDisappear: method.
I usually release everything in dealloc. As long as you make sure that you set an object to nil when you have released it in another method nothing bad will happen.
The leak was in a totally different place. Instruments isn't always right.