Tableview crashes when I scroll up to the top and hold - uitableview

I have a table view that is fed by core data. It works fine except when I scroll back to the top and hold it there. Then the app crashes.
Any suggestions?
Thanks
// The Model for this class.
//
// When it gets set, we create an NSFetchRequest to get all Photographers in the database associated with it.
// Then we hook that NSFetchRequest up to the table view using an NSFetchedResultsController.
- (void)setManagedObjectContext:(NSManagedObjectContext *)managedObjectContext
{
_managedObjectContext = managedObjectContext;
if (managedObjectContext) {
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:#"TestResults"];
request.sortDescriptors = #[[NSSortDescriptor sortDescriptorWithKey:#"date" ascending:YES selector:#selector(localizedCaseInsensitiveCompare:)]];
request.predicate = nil; // all records
self.fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:request managedObjectContext:managedObjectContext sectionNameKeyPath:nil cacheName:nil];
} else {
self.fetchedResultsController = nil;
}
}
#pragma mark - UITableViewDataSource
// Uses NSFetchedResultsController's objectAtIndexPath: to find the Photographer for this row in the table.
// Then uses that Photographer to set the cell up.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"TestResults"];
TestResults *testResult = [self.fetchedResultsController objectAtIndexPath:indexPath];
[self showTestResult:testResult];
// show traffic light color
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateFormat:#"MM-dd-yy hh:mm a"];
NSString *theDate = [dateFormatter stringFromDate:testResult.date];
cell.textLabel.text = theDate;
cell.detailTextLabel.text = testResult.overallPassOrFail;
NSLog(#"overall: %#", testResult.overallPassOrFail);
NSLog(#"light color: %#", testResult.trafficLightColor);
// retrieve an image
if ([testResult.trafficLightColor isEqualToString:#"Red"]) {
self.lightImage = #"Red_Light";
} else if ([testResult.trafficLightColor isEqualToString:#"Yellow"]) {
self.lightImage = #"Yellow_Light";
} else {
self.lightImage = #"Green_Light";
}
NSString *imagefile = [[NSBundle mainBundle] pathForResource:self.lightImage ofType:#"png"];
self.imageOne = [[UIImage alloc] initWithContentsOfFile:imagefile];
cell.imageView.image = self.imageOne;
//
return cell;
}

You are calling a method that is not implemented, check your code ;)

Related

UILabel in custom UITableView cell not updating with Core Data change

I'm attempting to build a game scoring app that utilizes a custom table cell with player photos, names, buttons etc... There are add/subtract buttons directly in the custom cell of the tableview that are hitting my save method, and it's storing it back in Core Data for that specific user.
The problem is with the on-screen score not updating and reflecting the change. After the save action to Core Data is complete, I'm calling the [self.tableView reloadData];... nothing. However, if I restart the app, then the change in score (for any of the players I've clicked on), appears.
Maybe I'm making this harder than it needs to be, either that, or I'm just not grasping the real problem.
Thoughts / comments?
Thanks a load in advance.
:-)
Sorry if this is overkill, but here is the majority of my implementation file:
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
[self resetViews];
}
- (void)viewDidLoad {
[super viewDidLoad];
AppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];
NSManagedObjectContext *context = [appDelegate managedObjectContext];
[context setUndoManager:nil];
_managedObjectContext = context;
self.tableView.delegate = self;
[self setNeedsStatusBarAppearanceUpdate];
}
-(void)resetViews {
NSLog(#"\n\n\nresetViews()");
[self setupFetchedResultsController];
[self.tableView reloadData];
[self.view setNeedsDisplay];
}
- (void)setupFetchedResultsController {
NSString *entityName = #"Players";
NSLog(#"Setting up a Fetched Results Controller for the Entity named %#", entityName);
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:entityName];
request.sortDescriptors = [NSArray arrayWithObject:
[NSSortDescriptor
sortDescriptorWithKey:#"playerName"
ascending:YES
selector:#selector(localizedCaseInsensitiveCompare:)]];
self.fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:request
managedObjectContext:self.managedObjectContext
sectionNameKeyPath:nil
cacheName:nil];
NSError *error;
NSArray *results = [_managedObjectContext executeFetchRequest:request error:&error];
_playerArray = [[NSMutableArray alloc]initWithArray:results];
NSLog(#"_playerArray count: %i", [_playerArray count]);
NSLog(#"\n");
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return _playerArray.count;
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *cellIdentifier = #"playerCell";
ScoringCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
// Configure the cell...
Players *player_info = [_playerArray objectAtIndex:indexPath.row];
NSSet *score = player_info.scores;
for (Scoring *perObj in score){
cell.lblPlayerScore.text = [perObj.score stringValue];
NSLog(#"\n\n\n score for %#: %#", player_info.playerName, perObj.score);
}
cell.lblPlayerName.text = player_info.playerName;
cell.lblPlayerNickName.text = player_info.playerNickName;
cell.btnIncreaseScore.tag = indexPath.row;
cell.btnDecreaseScore.tag = indexPath.row;
cell.imgPlayerPhoto.image = [UIImage imageNamed:#"tmp_playerImage"];
return cell;
}
- (IBAction)increaseScore:(id)sender {
NSLog(#"PageContentViewController: increaseScore()");
UIButton* btn=(UIButton*)sender;
int selectedPlayerInt = btn.tag;
//NSLog(#"Selected row is: %d",btn.tag);
Players *player_info = [_playerArray objectAtIndex:selectedPlayerInt];
[self updateRowScore:player_info:#"add"];
}
- (IBAction)decreaseScore:(id)sender {
NSLog(#"PageContentView: decreaseScore()");
UIButton* btn=(UIButton*)sender;
int selectedPlayerInt = btn.tag;
//NSLog(#"Selected row is: %d",btn.tag);
Players *player_info = [_playerArray objectAtIndex:selectedPlayerInt];
[self updateRowScore:player_info:#"subtract"];
}
-(void)updateRowScore: (Players *)player_info :(NSString *)modifier {
NSLog(#"\n\nupdateRowScore()");
NSLog(#"Update score (%#) for: %#\n", modifier, player_info.playerName);
NSArray *scoreDataArray;
if ([self playerScoreCount:player_info] == 0) {
// NEW score... we've never scored before.
Scoring *scoring_data = [NSEntityDescription
insertNewObjectForEntityForName:#"Scoring"
inManagedObjectContext:_managedObjectContext];
//Since this is the first score, always set it to 1
scoring_data.score = [NSNumber numberWithInt:1];
scoring_data.holeNumber = [NSNumber numberWithInt:_pageIndex];
scoring_data.scoredBy = player_info;
} else {
//Update existing player score..
NSError *error = nil;
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *BEntity = [NSEntityDescription entityForName:#"Scoring" inManagedObjectContext:_managedObjectContext];
[fetchRequest setEntity:BEntity];
NSPredicate *predicate = [NSPredicate
predicateWithFormat:#"(scoredBy = %#)", [player_info objectID]];
[fetchRequest setPredicate:predicate];
NSArray *results = [_managedObjectContext executeFetchRequest:fetchRequest error:&error];
scoreDataArray = [[NSMutableArray alloc]initWithArray:results];
Scoring *score_update = [scoreDataArray objectAtIndex:0];
int currentScore = [score_update.score intValue];
NSLog(#"current score: %d", currentScore);
if ([modifier isEqual: #"add"]) {
currentScore++;
} else {
// Don't allow negative scores.
if (currentScore >= 1) {
currentScore--;
} else {
currentScore = 0;
}
}
NSLog(#"NEW score: %d", currentScore);
score_update.score = [NSNumber numberWithInt:currentScore];
}
// write to database
[self.managedObjectContext save:nil];
[self resetViews];
}
UPDATE:
Thanks for the tip bbarnhart... I had read through that post before and had used that for a basis from which I had started. Decided to take it a step further and refactor a chunk of code using more of the Ray Wenderlich example.
I've seen some improvements to what's being recorded, and reported back through the NSLog's... but the view just still is not changing.
The action is increasing the score, and then I'm resetting the cell using [self configureCell:cell atIndexPath:path]; In there... the method that is responsible for sending text to the display... the NSLog is showing 2014-12-04 22:40:40.199 appName[7153:150248] Score for Tim: 4 when the display still only shows 3.
I know this is some stupid rookie move... I'm just doing something dead wrong that I can't figure out. Here's a snippet of the amended code.
- (NSFetchedResultsController *)fetchedResultsController {
if (_fetchedResultsController != nil) {
return _fetchedResultsController;
}
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription
entityForName:#"Players"
inManagedObjectContext:_managedObjectContext];
[fetchRequest setEntity:entity];
NSSortDescriptor *sort = [[NSSortDescriptor alloc]
initWithKey:#"playerName" ascending:YES];
[fetchRequest setSortDescriptors:[NSArray arrayWithObject:sort]];
NSFetchedResultsController *theFetchedResultsController =
[[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
managedObjectContext:_managedObjectContext
sectionNameKeyPath:nil
cacheName:#"Root"];
self.fetchedResultsController = theFetchedResultsController;
_fetchedResultsController.delegate = self;
NSError *error;
NSArray *results = [_managedObjectContext executeFetchRequest:fetchRequest error:&error];
_playerArray = [[NSMutableArray alloc]initWithArray:results];
NSLog(#"_playerArray count: %i", [_playerArray count]);
return _fetchedResultsController;
}
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
id sectionInfo = [[_fetchedResultsController sections] objectAtIndex:section];
return [sectionInfo numberOfObjects];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *cellIdentifier = #"playerCell";
ScoringCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
if (!cell) {
cell = [[ScoringCell alloc] initWithStyle:UITableViewCellStyleSubtitle
reuseIdentifier:cellIdentifier];
}
[self configureCell:cell atIndexPath:indexPath];
return cell;
}
- (void)configureCell:(ScoringCell *)cell atIndexPath:(NSIndexPath *)indexPath {
Players *player_info = [_fetchedResultsController objectAtIndexPath:indexPath];
NSSet *scoreSet = player_info.scores;
NSString *cell_score;
for (Scoring *scoreObj in scoreSet) {
cell_score = [scoreObj.score stringValue];
}
NSLog(#"Score for %#: %#", player_info.playerName, cell_score);
if (cell_score != nil) {
cell.lblPlayerScore.text = cell_score;
}
cell.lblPlayerName.text = player_info.playerName;
cell.lblPlayerNickName.text = player_info.playerNickName;
cell.btnIncreaseScore.tag = indexPath.row;
cell.btnDecreaseScore.tag = indexPath.row;
cell.imgPlayerPhoto.image = [UIImage imageNamed:#"demo_playerb"];
[self resetViews];
NSLog(#"\n");
}
- (IBAction)increaseScore:(id)sender {
NSLog(#"PageContentViewController: increaseScore()");
UIButton *senderButton = (UIButton *)sender;
int selectedPlayerInt = senderButton.tag;
NSIndexPath *path = [NSIndexPath indexPathForRow:senderButton.tag inSection:0];
Players *player_info = [_playerArray objectAtIndex:selectedPlayerInt];
[self updateRowScore:player_info:#"add":selectedPlayerInt:path];
}
-(void)updateRowScore:(Players *)player_info :(NSString *)modifier :(int)selectedPlayerInt :(NSIndexPath *)path {
NSArray *scoreDataArray;
if ([self playerScoreCount:player_info] == 0) {
// NEW score... we've never scored before.
Scoring *scoring_data = [NSEntityDescription
insertNewObjectForEntityForName:#"Scoring"
inManagedObjectContext:_managedObjectContext];
//Since this is the first score, always set it to 1
scoring_data.score = [NSNumber numberWithInt:1];
scoring_data.holeNumber = [NSNumber numberWithInt:_pageIndex];
scoring_data.scoredBy = player_info;
} else {
//Update existing player score..
NSError *error = nil;
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *BEntity = [NSEntityDescription entityForName:#"Scoring"
inManagedObjectContext:_managedObjectContext];
[fetchRequest setEntity:BEntity];
NSPredicate *predicate = [NSPredicate
predicateWithFormat:#"(scoredBy = %#)", [player_info objectID]];
[fetchRequest setPredicate:predicate];
NSArray *results = [_managedObjectContext executeFetchRequest:fetchRequest error:&error];
scoreDataArray = [[NSMutableArray alloc]initWithArray:results];
Scoring *score_update = [scoreDataArray objectAtIndex:0];
int currentScore = [score_update.score intValue];
NSLog(#"current score: %d", currentScore);
if ([modifier isEqual: #"add"]) {
currentScore++;
} else {
// Don't allow negative scores.
if (currentScore >= 1) {
currentScore--;
} else {
currentScore = 0;
}
}
NSLog(#"NEW score: %d", currentScore);
score_update.score = [NSNumber numberWithInt:currentScore];
}
// write to database
[self.managedObjectContext save:nil];
static NSString *cellIdentifier = #"playerCell";
ScoringCell *cell = [_tableView dequeueReusableCellWithIdentifier:cellIdentifier];
[self configureCell:cell atIndexPath:path];
[self resetViews];
}
----------
UPDATE:
Been awhile since I've had a chance to revisit, and just noticed a new problem since enabling your tips. When scrolling down or up in the list and pulling beyond the normal boundaries, the tableview data seems to overwrite the display for the row either above or below the current line. Weird... Not sure if this animated Gif will show up in Stack. Here's an example:
The main reason your table view is not updating dynamically is NSFetchedResultsController uses a delegate for notification when changes occur. You'll need to set that delegate, self.fetchedResultsController.delegate = self and then add the delegate methods.
Here is a link to an example for managing a UITableView with a NSFetchedResultsController.
Update
Implement these NSFetchResultsController delegate methods to allow your table to be dynamically updated.
- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller
- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath: (NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath
- (void)controller:(NSFetchedResultsController *)controller didChangeSection:(id )sectionInfo atIndex:(NSUInteger)sectionIndex forChangeType:(NSFetchedResultsChangeType)type
Generally, these methods contain boilerplate code for updating your table which you will also find in the link above.

Core data : NSFetchedResultsController

This is my datamodel
In my TableView, I want to display first and city
The following is my code where I fetch them. I'm getting the first data but not the city:
-(NSFetchedResultsController *)fetchedResultsController
{
if(fetchedResultsController != nil)
{
return fetchedResultsController;
}
NSManagedObjectContext *context = [self managedObjectContext];
NSFetchRequest *request = [[NSFetchRequest alloc] init];
NSEntityDescription *entityPerson = [NSEntityDescription entityForName:#"Person" inManagedObjectContext:context];
[request setEntity:entityPerson];
NSSortDescriptor *sortName = [[NSSortDescriptor alloc] initWithKey:#"first" ascending:YES];
NSSortDescriptor *sortAddress = [[NSSortDescriptor alloc] initWithKey:#"address.city" ascending:NO];
request.sortDescriptors = #[sortAddress, sortName];
[request setFetchBatchSize:50];
NSFetchedResultsController *fetchedResults = [[NSFetchedResultsController alloc] initWithFetchRequest:request managedObjectContext:context sectionNameKeyPath:nil cacheName:nil];
fetchedResultsController = fetchedResults;
fetchedResultsController.delegate = self;
return fetchedResultsController;
}
This is where i fetch the results and following is where i try to display the fetched results
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"TableCell";
TableCell *cell = (TableCell *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
if (cell == nil)
{
cell = [[TableCell alloc] initWithStyle:UITableViewCellStyleDefault
reuseIdentifier:CellIdentifier];
}
[self configureCell:cell atIndexPath:indexPath];
return cell;
}
-(void)configureCell:(TableCell *)cell atIndexPath:(NSIndexPath *)indexPath
{
NSManagedObject *obj = [fetchedResultsController objectAtIndexPath:indexPath];
// cell.lblName.text = #"";
[cell.lblName setText:[obj valueForKey:#"first"]];
// cell.lblCity.text = #"";
[cell.lblCity setText:[obj valueForKey:#"city"]];
}
Your fetched results controller is configured to return Person objects, so you need to reference the properties of the relationship indirectly:
[cell.lblCity setText:[obj valueForKeyPath:#"address.city"]];
or if you have created a subclass of NSManagedObject, say as Person, you can amend your code to:
Person *obj = [fetchedResultsController objectAtIndexPath:indexPath];
cell.lblName.text = obj.first;
cell.lblCity.text = obj.address.city;

FetchedResultsController objects lose Relationships while scrolling

I have a custom ViewController managing a TableView with a FetchedResultsController.
When my view is loaded everything seem to work fine (the cells contain the right data), however after scrolling the view the cells that get bind again with the data from the FetchedResultsController but the relationships of some objects are now set to nil.
controllerWillChangeContent and controllerDidChangeContent are not triggered during the scrolling.
This is the code in my view controller:
- (UITableViewCell *)tableView:(UITableView *)tb cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"ClaimProductCell";
ClaimProductTableViewCell *cell = (ClaimProductTableViewCell*)[tb dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
NSArray *topLevelItems = [[self productCellLoader] instantiateWithOwner:self options:nil];
cell = [topLevelItems objectAtIndex:0];
}
[self configureCell:cell atIndexPath:indexPath];
return cell;
}
- (void)configureCell:(UITableViewCell*)cell atIndexPath:(NSIndexPath*)indexPath
{
ClaimProduct* claimProduct = (ClaimProduct*)[[self fetchedResultsController] objectAtIndexPath:indexPath];
ClaimProductTableViewCell* productCell = (ClaimProductTableViewCell*)cell;
productCell.indexPath = indexPath;
[productCell bindWithClaimProduct:claimProduct];
}
Here it follows the table view cell binding code:
- (void)bindWithClaimProduct:(ClaimProduct*)claimProduct
{
DocumentProduct* documentProduct = claimProduct.claimProductToDocumentProduct;
Product* product = documentProduct.documentProductToProduct;
self.addButton.hidden = YES;
self.productCode.text = product.code;
self.quantityLabel.text = [documentProduct.qty stringValue];
self.nameLabel.text = product.descriptionName;
self.documentCode.text = documentProduct.documentProductToDocument.code;
NSDate * date = documentProduct.documentProductToDocument.date;
NSDateComponents *components = [[NSCalendar currentCalendar] components:NSCalendarUnitDay | NSCalendarUnitMonth | NSCalendarUnitYear fromDate:date];
self.documentDate.text = [NSString stringWithFormat:#"%ld/%ld/%ld", (long)[components day], (long)[components month], (long)[components year]];
// product image
NSString* imagePath = [ImagePathUtility imagePathFromImageName:product.image imageCode:product.code imageType:kImageProduct];
[self.productImageView setImageWithPath:imagePath placeholderName:kProductPlaceholderImage];
}
If I close and open again the app the view controller correctly shows the cells. If I scroll them I get the same problem again.
I am using Xcode 5 and working on an old project that does not use ARC.
[EDIT] This is NSFetchedResultsController lazy instantiation code:
- (NSFetchedResultsController*)fetchedResultsController
{
if (_fetchedResultsController)
return _fetchedResultsController;
NSManagedObjectContext* mainContext = [[AppDelegate sharedAppDelegate] managedObjectContext];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"ClaimProduct" inManagedObjectContext:mainContext];
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
[fetchRequest setEntity:entity];
[fetchRequest setRelationshipKeyPathsForPrefetching:[NSArray arrayWithObject:#"claimProductToDocumentProduct.documentProductToProduct"]];
[fetchRequest setReturnsObjectsAsFaults:NO];
[fetchRequest setFetchBatchSize:20];
NSSortDescriptor *sortDescriptor1 = [[NSSortDescriptor alloc] initWithKey:#"claimProductToDocumentProduct.documentProductToProduct.code" ascending:YES];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor1, nil];
[fetchRequest setSortDescriptors:sortDescriptors];
NSPredicate* predicate = [NSPredicate predicateWithFormat:#"claimProductToClaim == %#", [self claim]];
[fetchRequest setPredicate:predicate];
_fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:mainContext sectionNameKeyPath:nil cacheName:nil];
_fetchedResultsController.delegate = self;
[fetchRequest release];
[sortDescriptor1 release];
[sortDescriptors release];
return _fetchedResultsController;
}
[EDIT 2] I noticed another error that arises when looping on the [self.fetchedresultscontroller fetchedObjects]. The object in the relationship seems to be non nil, however its properties are all nil. This is the portion of code:
- (void)updateTotals
{
NSArray * claimProducts = [self.fetchedResultsController fetchedObjects];
NSDecimalNumber * imponibile = [NSDecimalNumber decimalNumberWithString:#"0"];
NSDecimalNumber * lordo = [NSDecimalNumber decimalNumberWithString:#"0"];
for (ClaimProduct * claimProduct in claimProducts) {
if (![claimProduct.qty isEqualToNumber:#0]) {
//compute totals
NSDecimalNumber * unitPrice = [claimProduct.claimProductToDocumentProduct.price decimalNumberByDividingBy:claimProduct.claimProductToDocumentProduct.qty];
NSDecimalNumber * claimProductPrice = [unitPrice decimalNumberByMultiplyingBy:claimProduct.qty];
imponibile = [imponibile decimalNumberByAdding:claimProductPrice];
NSDecimalNumber * unitPriceLordo = [claimProduct.claimProductToDocumentProduct.totalprice decimalNumberByDividingBy:claimProduct.claimProductToDocumentProduct.qty];
NSDecimalNumber * claimProductPriceLordo = [unitPriceLordo decimalNumberByMultiplyingBy:claimProduct.qty];
lordo = [lordo decimalNumberByAdding:claimProductPriceLordo];
}
}
...
}
Here the claimProductToDocumentProduct relationship is not nil, however sometimes it happens that its properties are nil and I get a EXC_BAD_ACCESS in decimalNumberByAdding.

Identify the array of arrays in NSFetchedResultsController

I have built some code from a tutorial I found in a book. It works and I am able to display my data from CoreData successfully in a tableView. I now want to identify the data/object that the fetchRequest returns. I feel like such a dummy because I cannot understand the syntax enough to isolate my object containing my array of data. This is a snippet of the code which I am having difficulty understanding:
- (NSFetchedResultsController *)fetchedResultsController {
if (_fetchedResultsController != nil) {
return _fetchedResultsController;
}
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Sessions" inManagedObjectContext:_context];
[fetchRequest setEntity:entity];
//NSLog(#"Entity is set to: %#", entity);
NSSortDescriptor *sort = [[NSSortDescriptor alloc] initWithKey:#"refid" ascending:NO];
[fetchRequest setSortDescriptors:[NSArray arrayWithObject:sort]];
//[fetchRequest setFetchBatchSize:20];
NSFetchedResultsController *theFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:_context sectionNameKeyPath:nil cacheName:#"Root"];
self.fetchedResultsController = theFetchedResultsController;
_fetchedResultsController.delegate = self;
return _fetchedResultsController;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
id <NSFetchedResultsSectionInfo> sectionInfo = [[_fetchedResultsController sections] objectAtIndex:section];
return [sectionInfo numberOfObjects];
}
- (void)configureCell:(UITableViewCell *)cell atIndexPath:(NSIndexPath *)indexPath {
Sessions *info = [_fetchedResultsController objectAtIndexPath:indexPath];
//Format cell data ready to be displayed
NSDateFormatter *dateFormat = [[NSDateFormatter alloc] init];
[dateFormat setDateFormat:#"EE, dd LLL yyyy"];
NSString *dateString = [dateFormat stringFromDate:info.date];
NSNumber *dist1Nbr = info.dist1;
int dist1Int = [dist1Nbr integerValue];
float distIntKorM = ([dist1Nbr integerValue])/1000;
NSString *dist1StrMeters = [[NSString alloc] initWithFormat:#"%i", dist1Int];
NSString *dist1StrKorM = [[NSString alloc] initWithFormat:#"%.01f", distIntKorM];
//Select image to display
if ([info.sport isEqualToString:#"Run"]) {
UIImage *image = [UIImage imageNamed:#"trainers-15x10.png"];
cell.imageView.image = image;
cell.textLabel.text = [[NSString alloc] initWithFormat:#"%#: (%#),", dateString, info.sport];
cell.detailTextLabel.text = [[NSString alloc] initWithFormat:#"Type: %#, Dist: %#", info.sessiontype, dist1StrKorM];
} else if ([info.sport isEqualToString:#"Other"]) {
UIImage *image = [UIImage imageNamed:#"weights-15x10.png"];
cell.imageView.image = image;
cell.textLabel.text = [[NSString alloc] initWithFormat:#"%#: (%#),", dateString, info.sport];
cell.detailTextLabel.text = [[NSString alloc] initWithFormat:#"Type: %#, Dist: %#", info.sessiontype, dist1StrKorM];
}
}
If anyone can help me it would be massively appreciated.
You can access an array of NSFetchedResultsSectionInfo objects by calling:
NSArray *sections = _fetchedResultsController.sections;
Each of these objects represents a section in your table. For a given section, you can do something like:
id<NSFetchedResultsSectionInfo>section = sections[0];
NSArray *objects = section.objects;
to access the managed objects in that section. Or, if you've got an index path, you can access the associated object directly by doing:
NSManagedObject *object = [_fetchedResultsController objectAtIndexPath:indexPath];
On in reverse:
NSIndexPath *indexPath = [_fetchedResultsController indexPathForObject:object];
Is this what you're looking for?
update
Forgot this one. As Juan suggested below, you can access all fetched objects with:
NSArray *allObjects = _fetchedResultsController.fetchedObjects;
From the code you posted the NSFetchedresultsController is going to return all objects of class Sessions ordered descending by key 'refid'. NSFetchedResultsCOntroller is used together with a UITableViewController to display the results of a fetch request to the Core Data storage.
However if you want just to obtain all the objects in a array without using the table view you could do the following, for example in viewWillAppear:
if (self.managedObjectContext) {
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:#"Sessions"];
request.sortDescriptors = #[[NSSortDescriptor sortDescriptorWithKey:#"refid" ascending:NO];
self.fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:request managedObjectContext:self.managedObjectContext sectionNameKeyPath:nil cacheName:nil];
[self.fetchedResultsController performfetch];
NSArray *results = [self.fetchedResultsController fetchedObjects]; // an array of Sessions objects ordered descending by refid key
for (Sessions *sessions in results) {
NSLog(#"Sessions = %#", sessions);
}
}
You have to make sure that the NSManagedContext is properly initialized before or nothing will be displayed.

Passing one to many core data between view controllers

I am having problems passing data between view controllers.
All three view controllers are table views.
WorkoutTypeVC
WorkoutSetVC
WorkoutExerciseVC
I have three entities,
WorkoutType
workouts(->WorkoutSet) One to Many
WorkoutSet
exercises(->WorkoutExercise) One to Many
workoutType(->WorkoutType) Inverse
WorkoutExercise
workoutSet(->WorkoutSet) Inverse
I am able to switch between all three view controllers, WorkoutTypeVC loads correctly showing all entries, When selected WorkoutSetVC is loaded showing the correct entries corresponding to the selection made from WorkoutTypeVC.
But when i select an entry from WorkoutSetVC, WorkoutExerciseVC loads but is empty, Even the title of the selection doesn't load.
I have used the same code which i used when switching from WorkoutTypeVC and WorkoutSetVC.
Below is the code for switching views in WorkoutType.m file:
-(void)fetchWorkoutTypes
{
NSFetchRequest *fetchRequest =
[NSFetchRequest fetchRequestWithEntityName:#"WorkoutType"];
NSString *cacheName = [#"WorkoutType" stringByAppendingString:#"Cache"];
NSSortDescriptor *sortDescriptor =
[NSSortDescriptor sortDescriptorWithKey:#"workoutType" ascending:YES];
[fetchRequest setSortDescriptors:#[sortDescriptor]];
self.fetchedResultsController = [[NSFetchedResultsController alloc]
initWithFetchRequest:fetchRequest managedObjectContext:self.managedObjectContext
sectionNameKeyPath:nil cacheName:cacheName];
NSError *error;
if (![self.fetchedResultsController performFetch:&error])
{
NSLog(#"Fetch failed: %#", error);
}
}
- (void)viewDidAppear:(BOOL)animated{
[self fetchWorkoutTypes];
[self.tableView reloadData];
}
- (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.fetchedResultsController.fetchedObjects.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell =
[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil)
{
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1
reuseIdentifier:CellIdentifier];
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
}
WorkoutType *workoutType = (WorkoutType *)[self.fetchedResultsController
objectAtIndexPath:indexPath];
cell.textLabel.text = workoutType.workoutType;
cell.detailTextLabel.text = [NSString stringWithFormat:#"(%d)", workoutType.workouts.count];
return cell;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
WorkoutType *workoutType = (WorkoutType *)[self.fetchedResultsController objectAtIndexPath:indexPath];
WorkoutSetViewController *detailViewController = [[WorkoutSetViewController alloc] initWithWorkoutType:workoutType];
[self.navigationController pushViewController:detailViewController animated:YES];
}
Below is the code for WorkoutSetVC.m
-(void)fetchWorkoutSets
{
NSFetchRequest *fetchRequest =
[NSFetchRequest fetchRequestWithEntityName:#"WorkoutSet"];
NSString *cacheName = [#"WorkoutSet" stringByAppendingString:#"Cache"];
NSSortDescriptor *sortDescriptor =
[NSSortDescriptor sortDescriptorWithKey:#"workoutName" ascending:YES];
[fetchRequest setSortDescriptors:#[sortDescriptor]];
self.fetchedResultsController = [[NSFetchedResultsController alloc]
initWithFetchRequest:fetchRequest managedObjectContext:self.managedObjectContext
sectionNameKeyPath:nil cacheName:cacheName];
NSError *error;
if (![self.fetchedResultsController performFetch:&error])
{
NSLog(#"Fetch failed: %#", error);
}
}
- (id)initWithWorkoutType:(WorkoutType *)workoutType
{
self = [super initWithStyle:UITableViewStylePlain];
if (self)
{
self.workoutType = workoutType;
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
self.title = self.workoutType.workoutType;
[self fetchWorkoutSets];
}
- (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.workoutType.workouts.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell =
[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil)
{
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle
reuseIdentifier:CellIdentifier];
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
}
WorkoutSet *workoutSet = [self.workoutType.workouts.allObjects objectAtIndex:indexPath.row];
cell.textLabel.text = workoutSet.workoutName;
cell.detailTextLabel.text = [NSString stringWithFormat:#"(%d)", workoutSet.exercises.count];
return cell;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
WorkoutSet *workoutSet = (WorkoutSet *)[self.fetchedResultsController objectAtIndexPath:indexPath];
WorkoutExerciseTableViewController *detailViewController = [[WorkoutExerciseTableViewController alloc] initWithWorkoutSet:workoutSet];
[self.navigationController pushViewController:detailViewController animated:YES];
}
Below is the code for WorkoutExercise.m
- (id)initWithWorkoutSet:(WorkoutSet *)workoutSet
{
self = [super initWithStyle:UITableViewStylePlain];
if (self)
{
self.workoutSet = workoutSet;
}
return self;
}
-(void)fetchWorkoutExercises
{
NSFetchRequest *fetchRequest =
[NSFetchRequest fetchRequestWithEntityName:#"WorkoutExercise"];
NSString *cacheName = [#"WorkoutExercise" stringByAppendingString:#"Cache"];
NSSortDescriptor *sortDescriptor =
[NSSortDescriptor sortDescriptorWithKey:#"exerciseName" ascending:YES];
[fetchRequest setSortDescriptors:#[sortDescriptor]];
self.fetchedResultsController = [[NSFetchedResultsController alloc]
initWithFetchRequest:fetchRequest managedObjectContext:self.managedObjectContext
sectionNameKeyPath:nil cacheName:cacheName];
NSError *error;
if (![self.fetchedResultsController performFetch:&error])
{
NSLog(#"Fetch failed: %#", error);
}
}
- (void)viewDidLoad
{
[super viewDidLoad];
self.title = self.workoutSet.workoutName;
[self fetchWorkoutExercises];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell =
[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil)
{
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle
reuseIdentifier:CellIdentifier];
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
}
WorkoutExercise *exercise = [self.workoutSet.exercises.allObjects objectAtIndex:indexPath.row];
cell.textLabel.text = exercise.exerciseName;
return cell;
}
Not sure as to what i need to do for the third view controller to list all the entries, Even the title for the third view controller doesn't load which is coded in the ViewDidLoad Method.
Thank You
The problem is (I assume) the inconsistent use of data sources in the second (and third?)
view controller.
In your WorkoutSetViewController, cellForRowAtIndexPath accesses the objects directly via self.workoutType.workouts.allObjects, but didSelectRowAtIndexPath uses a fetched results controller (FRC). This does not make sense. If the table view is driven by a FRC, all data source methods must use the FRC.
Perhaps self.fetchedResultsController is nil in the second view controller?
Then the workoutSet passed to the third view controller would be nil, which would
explain that no title is set and no objects are displayed.
And generating an array from self.workoutType.workouts with allObjects is also problematic, because the order of the array elements can be random.
The second view controller should use a fetched results controller to display
all WorkoutSet objects related to the given workoutType.
And the third view controller should use a fetched results controller to display
all WorkoutExercise objects related to the given workoutSet.
UPDATE: fetchWorkoutSets in WorkoutSetVC.m should look like this:
-(void)fetchWorkoutSets
{
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:#"WorkoutSet"];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"workoutType = %#", self.workoutType];
[fetchRequest setPredicate:predicate];
NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:#"workoutName" ascending:YES];
[fetchRequest setSortDescriptors:#[sortDescriptor]];
self.fetchedResultsController = [[NSFetchedResultsController alloc]
initWithFetchRequest:fetchRequest managedObjectContext:self.managedObjectContext
sectionNameKeyPath:nil cacheName:nil];
self.fetchedResultsController.delegate = self;
NSError *error;
if (![self.fetchedResultsController performFetch:&error])
{
NSLog(#"Fetch failed: %#", error);
}
}
The predicate is important to fetch only workout sets that are related to self.workoutType.
And similarly, fetchWorkoutExercises in WorkoutExercise.m would use the predicate
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"workoutSet = %#", self.workoutSet];
[fetchRequest setPredicate:predicate];
to fetch only exercises that are related to self.workoutSet.
For the data source methods, have a look at the NSFetchedResultsController documentation, it contains sample code that you can copy
and adapt to your needs. Or you create a fresh Xcode iOS application with the "Master-Detail Application" template and select the "Core Data" checkbox. That will also give you
sample code.
For example, in cellForRowAtIndexPath in WorkoutSetVC.m you would replace
WorkoutSet *workoutSet = [self.workoutType.workouts.allObjects objectAtIndex:indexPath.row];
by
WorkoutSet *workoutSet = [self.fetchedResultsController objectAtIndexPath:indexPath];

Resources