I have a problem that Im sure many objectiveC developers would be able to solve pretty easily. Im developing an app which is a dynamic tableview with different groups that the user can create. The user can enter a number of groups with different totals. I am trying to sum the total of all the groups created.
The IBAction I am using to calculate the subtotal is
- (IBAction)Calculate:(id)sender {
//---------------
NSInteger Tempsum = [[_dataArray valueForKeyPath:#"#sum.name2"] integerValue];
_TotalAnimals.text = [[NSString alloc] initWithFormat:#"%.1ld", (long)Tempsum];
//--------------
}
I have created the following code in the .m file of my tableview and also created a NSArray property called;
#property NSArray *dataArray;
however I am unsure as to the correct code to use to be able to link the result ( #"#sum.name2" ) to the text value in the Tableviewcontroller ( _TotalAnimals.text)
Any help with this problem would be much appreciated. below is the full code for the .m file
#import "TSPViewController.h"
#import <CoreData/CoreData.h>
#import "TSPToDoCell.h"
#import "TSPAddToDoViewController.h"
#import "TSPUpdateToDoViewController.h"
int TempTotalAnimals = 0;
int TotalAnimals = 0;
int Tempsum = 0;
#interface TSPViewController () <NSFetchedResultsControllerDelegate>
#property (strong, nonatomic) NSFetchedResultsController *fetchedResultsController;
#property (strong, nonatomic) NSIndexPath *selection;
#property NSArray *dataArray;
#end
#implementation TSPViewController
#pragma mark -
#pragma mark View Life Cycle
- (void)viewDidLoad {
[super viewDidLoad];
// Initialize Fetch Request
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] initWithEntityName:#"TSPItem"];
// Add Sort Descriptors
[fetchRequest setSortDescriptors:#[[NSSortDescriptor sortDescriptorWithKey:#"createdAt" ascending:YES]]];
// Initialize Fetched Results Controller
self.fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:self.managedObjectContext sectionNameKeyPath:nil cacheName:nil];
// Configure Fetched Results Controller
[self.fetchedResultsController setDelegate:self];
// Perform Fetch
NSError *error = nil;
[self.fetchedResultsController performFetch:&error];
if (error) {
NSLog(#"Unable to perform fetch.");
NSLog(#"%#, %#", error, error.localizedDescription);
}
}
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([segue.identifier isEqualToString:#"addToDoViewController"]) {
// Obtain Reference to View Controller
UINavigationController *nc = (UINavigationController *)[segue destinationViewController];
TSPAddToDoViewController *vc = (TSPAddToDoViewController *)[nc topViewController];
// Configure View Controller
[vc setManagedObjectContext:self.managedObjectContext];
} else if ([segue.identifier isEqualToString:#"updateToDoViewController"]) {
// Obtain Reference to View Controller
TSPUpdateToDoViewController *vc = (TSPUpdateToDoViewController *)[segue destinationViewController];
// Configure View Controller
[vc setManagedObjectContext:self.managedObjectContext];
if (self.selection) {
// Fetch Record
NSManagedObject *record = [self.fetchedResultsController objectAtIndexPath:self.selection];
if (record) {
[vc setRecord:record];
}
// Reset Selection
[self setSelection:nil];
}
}
}
#pragma mark -
#pragma mark Fetched Results Controller Delegate Methods
- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller {
[self.tableView beginUpdates];
}
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller {
[self.tableView endUpdates];
}
- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath {
switch (type) {
case NSFetchedResultsChangeInsert: {
[self.tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
break;
}
case NSFetchedResultsChangeDelete: {
[self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
break;
}
case NSFetchedResultsChangeUpdate: {
[self configureCell:(TSPToDoCell *)[self.tableView cellForRowAtIndexPath:indexPath] atIndexPath:indexPath];
break;
}
case NSFetchedResultsChangeMove: {
[self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
[self.tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
break;
}
}
}
#pragma mark -
#pragma mark Table View Data Source Methods
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return [[self.fetchedResultsController sections] count];
}
- (void)configureCell:(TSPToDoCell *)cell atIndexPath:(NSIndexPath *)indexPath {
// Fetch Record
NSManagedObject *record = [self.fetchedResultsController objectAtIndexPath:indexPath];
// Update Cell
[cell.nameLabel setText:[record valueForKey:#"name"]];
[cell.nameLabel2 setText:[record valueForKey:#"name2"]];
[cell.doneButton setSelected:[[record valueForKey:#"done"] boolValue]];
[cell setDidTapButtonBlock:^{
BOOL isDone = [[record valueForKey:#"done"] boolValue];
// Update Record
[record setValue:#(!isDone) forKey:#"done"];
}];
}
- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath {
return YES;
}
- (BOOL)tableView:(UITableView *)tableView canMoveRowAtIndexPath:(NSIndexPath *)indexPath {
return NO;
}
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
if (editingStyle == UITableViewCellEditingStyleDelete) {
NSManagedObject *record = [self.fetchedResultsController objectAtIndexPath:indexPath];
if (record) {
[self.fetchedResultsController.managedObjectContext deleteObject:record];
}
}
}
#pragma mark -
#pragma mark Table View Delegate Methods
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
[tableView deselectRowAtIndexPath:indexPath animated:YES];
// Store Selection
[self setSelection:indexPath];
// Perform Segue
[self performSegueWithIdentifier:#"updateToDoViewController" sender:self];
}
// NSArray Below============
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
NSArray *sections = [self.fetchedResultsController sections];
id<NSFetchedResultsSectionInfo> sectionInfo = [sections objectAtIndex:section];
return [sectionInfo numberOfObjects];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
TSPToDoCell *cell = (TSPToDoCell *)[tableView dequeueReusableCellWithIdentifier:#"ToDoCell" forIndexPath:indexPath];
// Configure Table View Cell
[self configureCell:cell atIndexPath:indexPath];
return cell;
}
- (IBAction)Calculate:(id)sender {
//---------------
NSInteger Tempsum = [[_dataArray valueForKeyPath:#"#sum.name2"] integerValue];
_TotalAnimals.text = [[NSString alloc] initWithFormat:#"%.1ld", (long)Tempsum];
//--------------
}
/*
//---------------
NSInteger TempsumTotalAnimals = [[_charactersArray valueForKeyPath:#"#sum.passenger"] integerValue];
_TotalAnimals.text = [[NSString alloc] initWithFormat:#"%.1ld", (long)TempsumTotalAnimals];
//--------------
*/
#end
i work on project where is TableViewController as initial controller. Then i have CoreData where i store my data. From my first TableViewController i get to ViewController via push segue in storyboard. In that ViewController i add data to my Core data, but when ViewController dismiss and TableViewController is back my table is still empty.
here is my code for table:
- (void)viewDidLoad
{
[super viewDidLoad];
AppDelegate *appDelegate = [[UIApplication sharedApplication]delegate];
_managedObjectContext = [appDelegate managedObjectContext];
NSFetchRequest*request = [[NSFetchRequest alloc]init];
NSEntityDescription*name = [NSEntityDescription entityForName:#"nameEntity" inManagedObjectContext:_managedObjectContext];
[request setEntity:name];
NSError*error = nil;
NSMutableArray*mutableFetchResults = [[_managedObjectContext executeFetchRequest:request error:&error]mutableCopy];
if (mutableFetchResults == nil) {
}
[self setMutableArray:mutableFetchResults];
[self.tableView reloadData];
}
than my code for table row:
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return mutableArray.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Name";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
Name*name = (Name*) [mutableArray objectAtIndex:indexPath.row ];
cell.textLabel.text = name.shop;
cell.detailTextLabel.text = name.date;
return cell;
}
and here code where i store that data in my ViewController:
- (IBAction)addData:(id)sender;{
Name *name = [NSEntityDescription insertNewObjectForEntityForName:#"nameEntity" inManagedObjectContext:_managedObjectContext];
[name setShop:textField.text];
NSDate*date = [NSDate date];
NSDateFormatter *df = [[NSDateFormatter alloc] init];
[df setDateFormat:#"dd.MM.yyyy"];
NSString *dateString = [df stringFromDate:date];
[zoznam setDate:dateString];
You are calling
[self.tableView reloadData];
in your viewDidLoad which is not going to be invoked when you dismiss your viewController. If you would like to reload your dataSource, try this:
- (void)viewWillAppear:(BOOL)animated{
AppDelegate *appDelegate = [[UIApplication sharedApplication]delegate];
_managedObjectContext = [appDelegate managedObjectContext];
NSFetchRequest*request = [[NSFetchRequest alloc]init];
NSEntityDescription*name = [NSEntityDescription entityForName:#"nameEntity" inManagedObjectContext:_managedObjectContext];
[request setEntity:name];
NSError*error = nil;
NSMutableArray*mutableFetchResults = [[_managedObjectContext executeFetchRequest:request error:&error]mutableCopy];
[self setMutableArray:mutableFetchResults];
[self.tableView reloadData];
}
Ok guys. This one is driving me up the wall. I have
UIManagedDocument and its 2 MOContexts (regular and parent.)
A UITableViewController (subclassed to CoreDataTableViewController by Paul Hegarty) that runs off of an
NSFetchedResultsController
A background GCD Queue for syncing with the server that the parent cue accesses
I've tried this so many different ways and I run into problems each time.
When I add a new "animal" entity, it is no problem and immediately shows up on the table. But when I upload it to the server (on the upload queue) and changed its "status" (with the parent context) so that it should be in the uploaded section, it appears there but doesn't disappear from the un-uploaded section.
I END UP WITH TWINS I DIDN'T WANT! or it doesn't even make the correct one sometimes and just keeps the wrong one.
***BUT, the extra one will disappear when the app is shut down and reloaded. So it's just in memory somewhere. I can verify in the store that everything is correct. But the NSFetchedResultsController isn't firing the controllerDidChange... stuff.
Here is the superclass of my view controller
CoreDataTableViewController.m
#pragma mark - Fetching
- (void)performFetch
{
self.debug = 1;
if (self.fetchedResultsController) {
if (self.fetchedResultsController.fetchRequest.predicate) {
if (self.debug) NSLog(#"[%# %#] fetching %# with predicate: %#", NSStringFromClass([self class]), NSStringFromSelector(_cmd), self.fetchedResultsController.fetchRequest.entityName, self.fetchedResultsController.fetchRequest.predicate);
} else {
if (self.debug) NSLog(#"[%# %#] fetching all %# (i.e., no predicate)", NSStringFromClass([self class]), NSStringFromSelector(_cmd), self.fetchedResultsController.fetchRequest.entityName);
}
NSError *error;
[self.fetchedResultsController performFetch:&error];
if (error) NSLog(#"[%# %#] %# (%#)", NSStringFromClass([self class]), NSStringFromSelector(_cmd), [error localizedDescription], [error localizedFailureReason]);
} else {
if (self.debug) NSLog(#"[%# %#] no NSFetchedResultsController (yet?)", NSStringFromClass([self class]), NSStringFromSelector(_cmd));
}
[self.tableView reloadData];
}
- (void)setFetchedResultsController:(NSFetchedResultsController *)newfrc
{
NSFetchedResultsController *oldfrc = _fetchedResultsController;
if (newfrc != oldfrc) {
_fetchedResultsController = newfrc;
newfrc.delegate = self;
if ((!self.title || [self.title isEqualToString:oldfrc.fetchRequest.entity.name]) && (!self.navigationController || !self.navigationItem.title)) {
self.title = newfrc.fetchRequest.entity.name;
}
if (newfrc) {
if (self.debug) NSLog(#"[%# %#] %#", NSStringFromClass([self class]), NSStringFromSelector(_cmd), oldfrc ? #"updated" : #"set");
[self performFetch];
} else {
if (self.debug) NSLog(#"[%# %#] reset to nil", NSStringFromClass([self class]), NSStringFromSelector(_cmd));
[self.tableView reloadData];
}
}
}
#pragma mark - UITableViewDataSource
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
if (self.debug) NSLog(#"fetchedResultsController returns %d sections", [[self.fetchedResultsController sections] count]);
return [[self.fetchedResultsController sections] count];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [[[self.fetchedResultsController sections] objectAtIndex:section] numberOfObjects];
}
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
{
return [[[self.fetchedResultsController sections] objectAtIndex:section] name];
}
- (NSInteger)tableView:(UITableView *)tableView sectionForSectionIndexTitle:(NSString *)title atIndex: (NSInteger)index
{
return [self.fetchedResultsController sectionForSectionIndexTitle:title atIndex:index];
}
- (NSArray *)sectionIndexTitlesForTableView:(UITableView *)tableView
{
return [self.fetchedResultsController sectionIndexTitles];
}
#pragma mark - NSFetchedResultsControllerDelegate
- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller
{
if (!self.suspendAutomaticTrackingOfChangesInManagedObjectContext)
{
[self.tableView beginUpdates];
self.beganUpdates = YES;
}
}
- (void)controller:(NSFetchedResultsController *)controller
didChangeSection:(id <NSFetchedResultsSectionInfo>)sectionInfo
atIndex:(NSUInteger)sectionIndex
forChangeType:(NSFetchedResultsChangeType)type
{
if (!self.suspendAutomaticTrackingOfChangesInManagedObjectContext)
{
switch(type)
{
case NSFetchedResultsChangeInsert:
[self.tableView insertSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeDelete:
[self.tableView deleteSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade];
break;
}
}
}
- (void)controller:(NSFetchedResultsController *)controller
didChangeObject:(id)anObject
atIndexPath:(NSIndexPath *)indexPath
forChangeType:(NSFetchedResultsChangeType)type
newIndexPath:(NSIndexPath *)newIndexPath
{
if(self.debug) NSLog(#"controller didChangeObject: %#", anObject);
if (!self.suspendAutomaticTrackingOfChangesInManagedObjectContext)
{
NSLog(#"#########Controller did change type: %d", type);
switch(type)
{
case NSFetchedResultsChangeInsert:
[self.tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeDelete:
[self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeUpdate:
[self.tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeMove:
[self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
[self.tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
break;
}
}
}
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller
{
if (self.beganUpdates) [self.tableView endUpdates];
if (self.debug) NSLog(#"controller Did Change Content");
}
- (void)endSuspensionOfUpdatesDueToContextChanges
{
_suspendAutomaticTrackingOfChangesInManagedObjectContext = NO;
}
- (void)setSuspendAutomaticTrackingOfChangesInManagedObjectContext:(BOOL)suspend
{
if (suspend) {
_suspendAutomaticTrackingOfChangesInManagedObjectContext = YES;
} else {
[self performSelector:#selector(endSuspensionOfUpdatesDueToContextChanges) withObject:0 afterDelay:0];
}
}
#end
And here's my specific view controller I subclassed from it:
- (NSArray *)sectionHeaderTitles
{
if (_sectionHeaderTitles == nil) _sectionHeaderTitles = [NSArray arrayWithObjects:#"Not Yet Uploaded", #"Uploaded But Not Featured", #"Previously Featured", nil];
return _sectionHeaderTitles;
}
- (NSDictionary *)selectedEntry
{
if (_selectedEntry == nil) _selectedEntry = [[NSDictionary alloc] init];
return _selectedEntry;
}
- (void)setupFetchedResultsController
{
[self.photoDatabase.managedObjectContext setStalenessInterval:0.0];
[self.photoDatabase.managedObjectContext.parentContext setStalenessInterval:0.0];
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:#"Animal"];
request.sortDescriptors = [NSArray arrayWithObjects:[NSSortDescriptor sortDescriptorWithKey:#"status" ascending:YES], [NSSortDescriptor sortDescriptorWithKey:#"unique" ascending:NO], nil];
self.fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:request managedObjectContext:self.photoDatabase.managedObjectContext sectionNameKeyPath:#"status" cacheName:nil];
NSError *error;
BOOL success = [self.fetchedResultsController performFetch:&error];
if (!success) NSLog(#"error: %#", error);
else [self.tableView reloadData];
self.fetchedResultsController.delegate = self;
}
- (void)useDocument
{
if (![[NSFileManager defaultManager] fileExistsAtPath:[self.photoDatabase.fileURL path]]) {
[self.photoDatabase saveToURL:self.photoDatabase.fileURL forSaveOperation:UIDocumentSaveForCreating completionHandler:^(BOOL success) {
[self setupFetchedResultsController];
}];
} else if (self.photoDatabase.documentState == UIDocumentStateClosed) {
[self.photoDatabase openWithCompletionHandler:^(BOOL success) {
[self setupFetchedResultsController];
}];
} else if (self.photoDatabase.documentState == UIDocumentStateNormal) {
[self setupFetchedResultsController];
}
}
- (void)setPhotoDatabase:(WLManagedDocument *)photoDatabase
{
if (_photoDatabase != photoDatabase) {
_photoDatabase = photoDatabase;
[self useDocument];
}
}
- (void)viewDidLoad
{
[super viewDidLoad];
UILabel *label = [[UILabel alloc] initWithFrame:CGRectZero];
label.backgroundColor = [UIColor clearColor];
label.font = [UIFont fontWithName:#"AmericanTypewriter" size:20];
label.shadowColor = [UIColor colorWithWhite:0.0 alpha:0.5];
label.textAlignment = UITextAlignmentCenter;
label.textColor = [UIColor whiteColor];
self.navigationItem.titleView = label;
label.text = self.navigationItem.title;
[label sizeToFit];
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
// Get CoreData database made if necessary
if (!self.photoDatabase) {
NSURL *url = [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
url = [url URLByAppendingPathComponent:#"Default Photo Database"];
self.photoDatabase = [[WLManagedDocument alloc] initWithFileURL:url];
NSLog(#"No existing photoDatabase so a new one was created from default photo database file.");
}
self.tableView.backgroundColor = [UIColor colorWithPatternImage:[UIImage imageNamed:#"DarkWoodBackGround.png"]];
}
- (void)syncWithServer
{
// This is done on the syncQ
// Start the activity indicator on the nav bar
dispatch_async(dispatch_get_main_queue(), ^{
[self.spinner startAnimating];
self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithCustomView:self.spinner];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(managedObjectContextDidSave:)
name:NSManagedObjectContextDidSaveNotification
object:self.photoDatabase.managedObjectContext.parentContext];
});
// Find new animals (status == 0)
NSFetchRequest *newAnimalsRequest = [NSFetchRequest fetchRequestWithEntityName:#"Animal"];
newAnimalsRequest.predicate = [NSPredicate predicateWithFormat:#"status == 0"];
NSError *error;
NSArray *newAnimalsArray = [self.photoDatabase.managedObjectContext.parentContext executeFetchRequest:newAnimalsRequest error:&error];
if ([newAnimalsArray count]) NSLog(#"There are %d animals that need to be uploaded.", [newAnimalsArray count]);
if (error) NSLog(#"fetchError: %#", error);
// Get the existing animals from the server
NSArray *parsedDownloadedAnimalsByPhoto = [self downloadedAllAnimalsFromWeb];
// In the parent context, insert downloaded animals into core data
for (NSDictionary *downloadedPhoto in parsedDownloadedAnimalsByPhoto) {
[Photo photoWithWebDataInfo:downloadedPhoto inManagedObjectContext:self.photoDatabase.managedObjectContext.parentContext];
// table will automatically update due to NSFetchedResultsController's observing of the NSMOC
}
// Upload the new animals if there are any
if ([newAnimalsArray count] > 0) {
NSLog(#"There are %d animals that need to be uploaded.", [newAnimalsArray count]);
for (Animal *animal in newAnimalsArray) {
// uploadAnimal returns a number that lets us know if it was accepted by the server
NSNumber *unique = [self uploadAnimal:animal];
if ([unique intValue] != 0) {
animal.unique = unique;
// uploadThePhotosOf returns a success BOOL if all 3 uploaded successfully
if ([self uploadThePhotosOf:animal]){
[self.photoDatabase.managedObjectContext performBlock:^{
animal.status = [NSNumber numberWithInt:1];
}];
}
}
}
}
[self.photoDatabase.managedObjectContext.parentContext save:&error];
if (error) NSLog(#"Saving parent context error: %#", error);
[self performUpdate];
// Turn the activity indicator off and replace the sync button
dispatch_async(dispatch_get_main_queue(), ^{
// Save the context
[self.photoDatabase saveToURL:self.photoDatabase.fileURL forSaveOperation:UIDocumentSaveForOverwriting completionHandler:^(BOOL success) {
if (success)
{
NSLog(#"Document was saved");
[self.photoDatabase.managedObjectContext processPendingChanges];
} else {
NSLog(#"Document was not saved");
}
}];
[self.spinner stopAnimating];
self.navigationItem.leftBarButtonItem = self.syncButton;
});
// Here it skips to the notification I got from saving the context so I can MERGE them
}
- (NSNumber *)uploadAnimal:(Animal *)animal
{
NSURL *uploadURL = [NSURL URLWithString:#"index.php" relativeToURL:self.remoteBaseURL];
NSString *jsonStringFromAnimalMetaDictionary = [animal.metaDictionary JSONRepresentation];
NSLog(#"JSONRepresentation of %#: %#", animal.namestring, jsonStringFromAnimalMetaDictionary);
ASIFormDataRequest *request = [ASIFormDataRequest requestWithURL:uploadURL];
[request setPostValue:jsonStringFromAnimalMetaDictionary forKey:#"newmeta"];
[request startSynchronous];
NSError *error = [request error];
NSString *response;
if (!error) {
response = [request responseString];
NSNumber *animalUnique = [(NSArray *)[response JSONValue]objectAtIndex:0];
return animalUnique;
} else {
response = [error description];
NSLog(#"%# got an error: %#", animal.namestring, response);
return [NSNumber numberWithInt:0];
}
}
- (BOOL)uploadThePhotosOf:(Animal *)animal
{
NSURL *uploadURL = [NSURL URLWithString:#"index.php" relativeToURL:self.remoteBaseURL];
int index = [animal.photos count];
for (Photo *photo in animal.photos) {
// Name the jpeg file
NSTimeInterval timeInterval = [NSDate timeIntervalSinceReferenceDate];
NSString *imageServerPath = [NSString stringWithFormat:#"%lf-Photo.jpeg",timeInterval];
// Update the imageServerPath
photo.imageURL = imageServerPath;
NSData *photoData = [[NSData alloc] initWithData:photo.image];
NSString *photoMeta = [photo.metaDictionary JSONRepresentation];
ASIFormDataRequest *request = [ASIFormDataRequest requestWithURL:uploadURL];
[request addPostValue:photoMeta forKey:#"newphoto"];
[request addData:photoData withFileName:imageServerPath andContentType:#"image/jpeg" forKey:#"filename"];
[request setUploadProgressDelegate:self.progressView];
[request startSynchronous];
NSLog(#"%# progress: %#", animal.namestring, self.progressView.progress);
NSString *responseString = [request responseString];
NSLog(#"uploadThePhotosOf:%# photo at placement: %d has responseString: %#", animal.namestring, [photo.placement intValue], responseString);
SBJsonParser *parser= [[SBJsonParser alloc] init];
NSError *error = nil;
id jsonObject = [parser objectWithString:responseString error:&error];
NSNumber *parsedPhotoUploadResponse = [(NSArray *)jsonObject objectAtIndex:0];
// A proper response is not 0
if ([parsedPhotoUploadResponse intValue] != 0) {
photo.imageid = parsedPhotoUploadResponse;
--index;
}
}
// If the index spun down to 0 then it was successful
int success = (index == 0) ? 1 : 0;
return success;
}
- (NSArray *)downloadedAllAnimalsFromWeb
{
NSURL *downloadURL = [NSURL URLWithString:#"index.php" relativeToURL:self.remoteBaseURL];
ASIFormDataRequest *request = [ASIFormDataRequest requestWithURL:downloadURL];
[request setPostValue:#"yes" forKey:#"all"];
request.tag = kGetHistoryRequest;
[request startSynchronous];
NSString *responseString = [request responseString];
NSLog(#"downloadedAllAnimalsFromWeb responseString: %#", responseString);
SBJsonParser *parser= [[SBJsonParser alloc] init];
NSError *error = nil;
id jsonObject = [parser objectWithString:responseString error:&error];
NSArray *parsedDownloadedResponseStringArray = [NSArray arrayWithArray:jsonObject];
return parsedDownloadedResponseStringArray;
}
- (void)performUpdate
{
NSManagedObjectContext * context = self.photoDatabase.managedObjectContext.parentContext;
NSSet * inserts = [context updatedObjects];
if ([inserts count])
{
NSError * error = nil;
NSLog(#"There were inserts");
if ([context obtainPermanentIDsForObjects:[inserts allObjects]
error:&error] == NO)
{
NSLog(#"BAM! %#", error);
}
}
[self.photoDatabase updateChangeCount:UIDocumentChangeDone];
}
- (void)managedObjectContextDidSave:(NSNotification *)notification
{
[[NSNotificationCenter defaultCenter] removeObserver:self name:NSManagedObjectContextDidSaveNotification object:self.photoDatabase.managedObjectContext.parentContext];
NSLog(#"userInfo from the notification: %#", [notification userInfo]);
// Main thread context
NSManagedObjectContext *context = self.fetchedResultsController.managedObjectContext;
SEL selector = #selector(mergeChangesFromContextDidSaveNotification:);
[context performSelectorOnMainThread:selector withObject:notification waitUntilDone:YES];
NSLog(#"ContextDidSaveNotification was sent. MERGED");
}
#pragma mark - Table view data source
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"EntryCell"];
if (!cell) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#"EntryCell"];
}
// Configure the cell here...
Animal *animal = [self.fetchedResultsController objectAtIndexPath:indexPath];
cell.textLabel.text = animal.namestring;
if (([animal.numberofanimals intValue] > 0) && animal.species) {
cell.detailTextLabel.text = [NSString stringWithFormat:#"%#s", animal.species];
} else {
cell.detailTextLabel.text = animal.species;
}
return cell;
}
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
NSIndexPath *indexPath = [self.tableView indexPathForCell:sender];
Animal *animal = [self.fetchedResultsController objectAtIndexPath:indexPath];
// be somewhat generic here (slightly advanced usage)
// we'll segue to ANY view controller that has a photographer #property
if ([segue.identifier isEqualToString:#"newAnimal"]) {
NSLog(#"self.photodatabase");
[(NewMetaEntryViewController *)[segue.destinationViewController topViewController] setPhotoDatabaseContext:self.photoDatabase.managedObjectContext];
} else if ([segue.destinationViewController respondsToSelector:#selector(setAnimal:)]) {
// use performSelector:withObject: to send without compiler checking
// (which is acceptable here because we used introspection to be sure this is okay)
[segue.destinationViewController performSelector:#selector(setAnimal:) withObject:animal];
NSLog(#"animal: %# \r\n indexPath: %#", animal, indexPath);
}
}
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section
{
return 30;
}
- (NSArray *)sectionIndexTitlesForTableView:(UITableView *)tableView
{
return nil;
}
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
{
NSLog(#"header for section called for section: %d", section);
NSLog(#"fetchedResultsController sections: %#", self.fetchedResultsController.sections);
CGRect headerRect = CGRectMake(0, 0, tableView.bounds.size.width, 30);
UIView *header = [[UIView alloc] initWithFrame:headerRect];
UILabel *headerTitleLabel = [[UILabel alloc] initWithFrame:CGRectMake(5, 5, tableView.bounds.size.width - 10, 20)];
if ([(Animal *)[[[[self.fetchedResultsController sections] objectAtIndex:section] objects] objectAtIndex:0] status] == [NSNumber numberWithInt:0]) {
headerTitleLabel.text = [self.sectionHeaderTitles objectAtIndex:0];
} else if ([(Animal *)[[[[self.fetchedResultsController sections] objectAtIndex:section] objects] objectAtIndex:0] status] == [NSNumber numberWithInt:1]) {
headerTitleLabel.text = [self.sectionHeaderTitles objectAtIndex:1];
} else {
headerTitleLabel.text = [self.sectionHeaderTitles objectAtIndex:2];
}
headerTitleLabel.textColor = [UIColor whiteColor];
headerTitleLabel.font = [UIFont fontWithName:#"AmericanTypewriter" size:20];
headerTitleLabel.backgroundColor = [UIColor clearColor];
headerTitleLabel.alpha = 0.8;
[header addSubview:headerTitleLabel];
return header;
}
Way too much code for anyone to want to wade through.
However, from a quick inspection, it looks like you are violating the MOC constraints. Specifically, you are accessing the parent context directly, and not from its own thread, either.
Typically, you would start a new thread, then create a MOC in that thread, make its parent be the MOC of the document. then do your stuff, and call save on the new MOC. It will then notify the parent, which should handle the updating.
I am having a weird issue and need some help.
I am on a core data project and did not yet use a fetchedResultsController, just working with fetchRequets and arrays to populate zableviews. So now I decided to change and make use of FRC ...
Everything was pretty easy so far ... but with commitEditingStyle I am having issues since then - when deleting rows I am thrown an exception like this one:
The number of rows contained in an existing section after the update (1) must be equal to the number of rows contained in that section before the update.
In the end I found out that it is because the object I want to delete remains in FRC ... I put some NSLog sections in liko so:
NSLog(#"Number before deleting: %i - deleting %#",[[fetchedResultsController fetchedObjects] count], [fetchedResultsController objectAtIndexPath:indexPath]);
NSManagedObjectContext *context = [fetchedResultsController managedObjectContext];
[context deleteObject:[fetchedResultsController objectAtIndexPath:indexPath]];
NSLog(#"Number before saving: %i",[[fetchedResultsController fetchedObjects] count]);
NSError *error;
if (![context save:&error]) {
[NSException raise:NSGenericException format:#"Following error occured when trying to delete %#: %#", [fetchedResultsController objectAtIndexPath:indexPath], [error description]];
}
NSLog(#"Number after saving: %i",[[fetchedResultsController fetchedObjects] count]);
NSArray * cellsToDelete = [NSArray arrayWithObject:indexPath];
[tableView deleteRowsAtIndexPaths:cellsToDelete withRowAnimation:UITableViewRowAnimationFade];
The result was this:
2011-05-20 14:49:35.398 Nivellator[6000:207] Number before deleting: 3
2011-05-20 14:49:35.399 Nivellator[6000:207] Number before saving: 3
2011-05-20 14:49:35.404 Nivellator[6000:207] Number after saving: 3
Of course it will puke when I'm telling the tableview that it'll get 3 rows but only pass two ... but what's wrong here?
My old code was looking like this and worked without any issues ...
-(void) tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
{
if ([tableView isEqual:self.entityTableView] == YES) {
if (editingStyle != UITableViewCellEditingStyleDelete) {
return;
}
if ([self.entityArray count] <= indexPath.row) {
return;
}
Member *thisEntity = [self.entityArray objectAtIndex:indexPath.row];
[delegate.managedObjectContext deleteObject:thisEntity];
NSError *savingError = nil;
if ([delegate.managedObjectContext save:&savingError] == YES) {
// Remove the entity from the Array and delete the corresponding table cell with animation
//
[self.entityArray removeObject:thisEntity];
NSArray * cellsToDelete = [NSArray arrayWithObject:indexPath];
[tableView deleteRowsAtIndexPaths:cellsToDelete withRowAnimation:UITableViewRowAnimationFade];
} else {
/* Error handling missing */
}
}
}
OK
in the end it came down to RTFM ... so I made it partly work by changing the code such as:
-(void) tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
{
if ([tableView isEqual:self.entityTableView] == YES) {
if (editingStyle != UITableViewCellEditingStyleDelete) {
return;
}
NSManagedObjectContext *context = [fetchedResultsController managedObjectContext];
[context deleteObject:[fetchedResultsController objectAtIndexPath:indexPath]];
NSError *error;
if (![context save:&error]) {
[NSException raise:NSGenericException format:#"Following error occured when trying to delete %#: %#", [fetchedResultsController objectAtIndexPath:indexPath], [error description]];
}
if ([fetchedResultsController performFetch:&error]) {
[tableView beginUpdates];
NSArray * cellsToDelete = [NSArray arrayWithObject:indexPath];
[tableView deleteRowsAtIndexPaths:cellsToDelete withRowAnimation:UITableViewRowAnimationFade];
[tableView endUpdates];
} else {
[NSException raise:NSGenericException format:#"Following error occured when trying to delete %#: %#", [fetchedResultsController objectAtIndexPath:indexPath], [error description]];
}
}
}
But when I'm using sectments in my FRC I still get the same error ... I cannot find anything more than this to do with the FRC ...
Any idea?
Delete the FRC's cache after the delete
Check that you have implemented the FRC's delegate methods properly.
Make sure you freeze the tableview with beginUpdates before you run the FRC's delegate methods.
I have created one application with core data but is unable to display the data in table view. I am writing the code below which I have written for fetching the results:
#import "RootViewController.h"
#import "DetailViewController.h"
#import "AddViewController.h"
#import "EmployeeDetailsAppDelegate.h"
/*
This template does not ensure user interface consistency during editing operations in the table view. You must implement appropriate methods to provide the user experience you require.
*/
#interface RootViewController ()
- (void)configureCell:(UITableViewCell *)cell atIndexPath:(NSIndexPath *)indexPath;
#end
#implementation RootViewController
#synthesize detailViewController, fetchedResultsController, managedObjectContext;
#synthesize array;
#pragma mark -
#pragma mark View lifecycle
- (void)viewDidLoad {
// EmployeeDetailsAppDelegate *appDelegate = [[UIApplication sharedApplication]delegate];
self.title = #"Employee Name";
self.navigationItem.rightBarButtonItem = self.editButtonItem;
NSMutableArray *tempArr = [[NSMutableArray alloc]initWithCapacity:0];
if (self.fetchedResultsController) {
tempArr = self.fetchedResultsController.fetchedObjects;
}
self.array = tempArr;
[super viewDidLoad];
[tempArr release];
}
- (void)viewWillAppear:(BOOL)animated {
[self.tableView reloadData];
[super viewWillAppear:animated];
}
/*
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
}
*/
/*
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
}
*/
/*
- (void)viewDidDisappear:(BOOL)animated {
[super viewDidDisappear:animated];
}
*/
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
// Ensure that the view controller supports rotation and that the split view can therefore show in both portrait and landscape.
return YES;
}
#pragma mark -
#pragma mark Add a new object
- (void)insertNewObject:(id)sender {
AddViewController *add = [[AddViewController alloc]initWithNibName:#"AddViewController" bundle:nil];
self.modalPresentationStyle = UIModalPresentationFormSheet;
add.wantsFullScreenLayout = NO;
[self presentModalViewController:add animated:YES];
[add release];
}
#pragma mark -
#pragma mark Table view data source
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
if([self.array count])
{
return [array count];
}
return 1;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
}
// Configure the cell.
[self configureCell:cell atIndexPath:indexPath];
return cell;
}
-(void)configureCell:(UITableViewCell *)cell atIndexPath:(NSIndexPath *)indexPath{
AddViewController *detail = [fetchedResultsController objectAtIndexPath:indexPath];
cell.textLabel.text = detail.empName.text;
}
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
if (editingStyle == UITableViewCellEditingStyleDelete) {
// Delete the managed object.
NSManagedObject *objectToDelete = [self.fetchedResultsController objectAtIndexPath:indexPath];
if (self.detailViewController.detailItem == objectToDelete) {
self.detailViewController.detailItem = nil;
}
NSManagedObjectContext *context = [fetchedResultsController managedObjectContext];
[context deleteObject:[fetchedResultsController objectAtIndexPath:indexPath]];
NSError *error;
if (![context save:&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();
}
}
}
- (BOOL)tableView:(UITableView *)tableView canMoveRowAtIndexPath:(NSIndexPath *)indexPath {
// The table view should not be re-orderable.
return NO;
}
#pragma mark -
#pragma mark Table view delegate
- (void)tableView:(UITableView *)aTableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
// Set the detail item in the detail view controller.
detailViewController = [[DetailViewController alloc]initWithStyle:UITableViewStylePlain];
AddViewController *selectedName = (AddViewController *)[[self fetchedResultsController]objectAtIndexPath:indexPath];
detailViewController.detail = selectedName;
[self.navigationController pushViewController:detailViewController animated:YES];
[detailViewController release];
}
#pragma mark -
#pragma mark Fetched results controller
- (NSFetchedResultsController *)fetchedResultsController {
if (fetchedResultsController != nil) {
return fetchedResultsController;
}
/*
Set up the fetched results controller.
*/
// Create the fetch request for the entity.
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
// Edit the entity name as appropriate.
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Details" inManagedObjectContext:managedObjectContext];
[fetchRequest setEntity:entity];
// Set the batch size to a suitable number.
[fetchRequest setFetchBatchSize:20];
// Edit the sort key as appropriate.
NSSortDescriptor *nameDescriptor = [[NSSortDescriptor alloc] initWithKey:#"EmployeeName" ascending:NO];
NSSortDescriptor *idDescriptor = [[NSSortDescriptor alloc] initWithKey:#"EmployeeID" ascending:NO];
NSMutableArray *sortDescriptors = [[NSMutableArray alloc] initWithObjects:nameDescriptor, idDescriptor, nil];
self.array = sortDescriptors;
[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:managedObjectContext sectionNameKeyPath:nil cacheName:#"Root"];
aFetchedResultsController.delegate = self;
self.fetchedResultsController = aFetchedResultsController;
[aFetchedResultsController release];
[fetchRequest release];
[nameDescriptor release];
[sortDescriptors release];
return fetchedResultsController;
}
#pragma mark -
#pragma mark Fetched results controller delegate
- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller {
[self.tableView beginUpdates];
}
- (void)controller:(NSFetchedResultsController *)controller didChangeSection:(id <NSFetchedResultsSectionInfo>)sectionInfo
atIndex:(NSUInteger)sectionIndex forChangeType:(NSFetchedResultsChangeType)type {
switch(type) {
case NSFetchedResultsChangeInsert:
[self.tableView insertSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeDelete:
[self.tableView 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.tableView;
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.tableView endUpdates];
}
#pragma mark -
#pragma mark Memory management
- (void)didReceiveMemoryWarning {
// Releases the view if it doesn't have a superview.
[super didReceiveMemoryWarning];
// Relinquish ownership any cached data, images, etc. that aren't in use.
}
- (void)viewDidUnload {
self.fetchedResultsController = nil;
// Relinquish ownership of anything that can be recreated in viewDidLoad or on demand.
// For example: self.myOutlet = nil;
self.array = nil;
}
- (void)dealloc {
[array release];
[detailViewController release];
[fetchedResultsController release];
[managedObjectContext release];
[super dealloc];
}
#end
The problem is I am getting a warning with the following line:
tempArr = self.fetchedResultsController.fetchedObjects;
The warning is: "/Users/satyam/Desktop/EmployeeDetails/Classes/RootViewController.m:43:0 /Users/satyam/Desktop/EmployeeDetails/Classes/RootViewController.m:43: warning: incompatible Objective-C types assigning 'struct NSArray *', expected 'struct NSMutableArray *'"
Just read the warning word for word.
you try to assign an NSArray to a variable that expects a NSMutableArray.
self.fetchedResultsController.fetchedObjects is an NSArray
tempArr is an NSMutableArray
but if you use the NSFetchedResultsController you don't need your own data array at all.
The NSFetchedResultsController handles all the data for you.
so maybe you want to replace
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
if([self.array count])
{
return [array count];
}
return 1;
}
with
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
id <NSFetchedResultsSectionInfo> sectionInfo = [[self.fetchedResultsController sections] objectAtIndex:section];
return [sectionInfo numberOfObjects];
}
your own datasource is invalid anyway. because you assign the sortDescriptor used for the NSFetchedResultsController to it.
NSMutableArray *sortDescriptors = [[NSMutableArray alloc] initWithObjects:nameDescriptor, idDescriptor, nil];
self.array = sortDescriptors;
I don't know what you wanted to achieve by doing this.