Update/Edit coreData managed object - core-data

I'm trying to edit a CoreData object when a user clicks on a cell in a UITableView based on the cell.accessoryType to show if the item has been clicked. Here is the current code.
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
NSManagedObject *itemToUpdate = [groceryArray objectAtIndex:indexPath.row];
NSLog(#"updating: %#", itemToUpdate);
if (cell.accessoryType == UITableViewCellAccessoryCheckmark) {
cell.accessoryType = UITableViewCellAccessoryNone;
itemToUpdate.purchased = NO;
}else {
cell.accessoryType = UITableViewCellAccessoryCheckmark;
itemToUpdate.purchased = YES;
}
// Commit the change.
NSError *error;
if (![managedObjectContext save:&error]) {
// Handle the error.
NSLog(#"Saving changes failed: %#", error);
}
}
It seems to be selecting the right object because the NSLog() will show the correct item but when I try to update using the dot notation e.g. "itemToUpdate.purchased = YES;" the compiler throws an error "request for member 'purchased' in something not a structure or union".
I know I'm probably doing this wrong (my first project in xcode) - any advice would be greatly appreciated!
Thanks

Have you tried:
[itemToUpdate setValue:[NSNumber numberWithBool:NO] forKey:#"purchased"]
form?
I always subclass NSManagedObject and the dot notation works for declared properties. But you might try this "older" notation to see if that works.

I suppose you created a custom subclass of 'NSManagedObject' with 'purchased' as one of the properties. Declare 'itemToUpdate' as an object of this subclass, rather than NSManagedObject:
YourCustomSubclassOfNSManagedObject *itemToUpdate = [groceryArray objectAtIndex:indexPath.row];

Related

updating NSManagedObject doesn't call NSFetchedResultsControllerDelegate using MagicalRecord

I have a model with this one to many relationShip:
Order -->> LineItem
I display LineItems in UITableViewCells:
I use UIPickerView for changing quantity of LineItems.
GOAL=> by changing picker value, subTotal be recalculated again.
the problem is here by updating lineItem, NSFetchedResultsController Delegate doesn't call (where I can reconfigure the cell again and display updated data). but when I update Order e.g set it as completed NSFetchedResultsController Delegate methods will be called.
why by updating lineItem doesn't affect delegates methods to be called?
I use magicalRecord and here is how I get NSFetchedResultsController
- (NSFetchedResultsController *)fetchedResultsController
{
if (_fetchedResultsController != nil) {
return _fetchedResultsController;
}
else
{
_fetchedResultsController = [Order fetchAllSortedBy:#"orderDate" ascending:YES withPredicate:nil groupBy:nil delegate:self];
}
return _fetchedResultsController;
}
the way I setup table view:
ConfigureCellBlock configureCell = ^(OrderDetailsCell *cell, LineItem *lineItem)
{
[cell configureForLineItem:lineItem];
};
//set fetchedresults controller delegate
Order *order = [[self.fetchedResultsController fetchedObjects] lastObject];
NSArray *lineItems = [order.lineItems allObjects];
self.ordersDataSource = [[ArrayDataSource alloc] initWithItems:lineItems cellIdentifier:#"lineItemCell" configureCellBlock:configureCell];
self.tableView.dataSource = self.ordersDataSource;
configuring cell:
- (void)configureForLineItem:(LineItem *)lineItem
{
self.menuItemName.text = lineItem.menuItemName;
self.price.text = [lineItem.unitPrice stringValue];
self.quantity.text = [lineItem.quantity stringValue];
self.totalPrice.text = [lineItem.totalPrice stringValue];
self.pickerController.model = lineItem;
self.picker.delegate = self.pickerController;
self.picker.dataSource = self.pickerController;
[self.picker setSelectedNumber:lineItem.quantity];
}
does fetching obj1 then updating obj3 cause the NSFRC delegate methods to be called?
The FRC will only observe changes to the objects that it is directly interested in, not any of the objects that they are related to.
You should configure your own observation, either directly with KVO or to the context being saved, and use that to trigger a UI refresh.

How to use groupBy with MagicalRecord?

I am stuck on how to use groupBy with MagicalRecord.
I have a list of countries with venues
Country -<< Venues
I need to group all the venues by the country and sort the countries by name.
But I am not sure how to do this with MagicalRecord.
I have tried to use a NSFetchedController but sometimes it crashes saying that the array is nil or 0 length.
Other times, it only ever sees 1 category when there are multiple.
Finally, I am not sure how to execute the fetch on an entity.
ie;
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view from its nib.
_objects = [NSMutableArray array];
self.fetchedResultsController = [self fetchedResultsController];
[Venue performFetch:self.fetchedResultsController];
// At this point how do I make the Venue findAllSortedBy work on the performFetch?
_objects = [NSMutableArray arrayWithArray:[Venue findAllSortedBy:#"name" ascending:YES inContext:[NSManagedObjectContext defaultContext]]];
self.title = #"Venues";
}
- (NSFetchedResultsController *)fetchedResultsController {
if (!fetchedResultsController) {
fetchedResultsController = [Venue fetchAllSortedBy:#"name"
ascending:YES
withPredicate:nil
groupBy:#"country"
delegate:self];
}
return fetchedResultsController;
}
-(NSString*)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
NSLog(#"Section = %d", section);
id <NSFetchedResultsSectionInfo> sectionInfo = [[self.fetchedResultsController sections] objectAtIndex:section];
NSLog(#"sectionInfo = %#", sectionInfo);
return #"Header";
}
- (UITableViewCell *)tableView:(UITableView *)tv cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tv dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier];
}
[self configureCell:cell atIndexPath:indexPath];
return cell;
}
- (void)configureCell:(UITableViewCell *)cell atIndexPath:(NSIndexPath *)indexPath {
Venue *v = [_objects objectAtIndex:indexPath.row];
cell.textLabel.text = v.name;
}
I am not sure if I am doing this right.
The above will put everything in 1 country (when there are multiple) and the log will report;
CoreData: error: (NSFetchedResultsController) A section returned nil value for section
name key path 'country'. Objects will be placed in unnamed section
It seems not to see the different countries and I do not think I've done the GroupBy command correctly.
Thus, how do I do a GroupBy command with MagicalRecord?
Many thanks.
self.fetchedVenues = [Venue MR_fetchAllGroupedBy:#"country" withPredicate:nil sortedBy:#"name" ascending:YES];
This error is telling you that of all your Venue objects, there is at least one in your result set that does not have a value for "country". You need to verify that you are indeed filling in this field and saving it properly prior to fetching.
And FYI, in your viewDidLoad method, you don't need all that code. Simply do something like:
- (void) viewDidLoad;
{
[super viewDidLoad];
self.fetchedVenues = [Venue fetchAllSortedBy:#"name" ascending:YES withPredicate:nil groupBy:#"country" delegate:self];
}
fetchAllSortedBy... will perform the fetch for you, and log errors, etc. That is the point of a helper framework like MagicalRecord.

Core data inserting rows and saving issue

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

Core Data Transient Calculated Attributes

I have an entity that contains lastName and firstName attributes. For reasons beyond the scope of this question, I want a fullName attribute that gets calculated as a concatenation of firstName + space + lastName.
Because this is purely a calculated value, with no need for redo/undo or any other of the more sophisticated aspects of transient attributes (merging, etc.), my gut tells me to just override the getter method to return said calculated value. Reading suggests that, if I do this, my only concern would be whether it's KVO compliant, which I can address by using keyPathsForValuesAffectingVolume to ensure changes to firstName or lastName trigger notifications for anyone observing on fullName.
Am I missing anything? I'm checking because I'm a beginner to this environment.
I'm also new to this, so I'm not completely sure about my answer, but as I understand it you are correct.
- (NSString *)fullName
{
[self willAccessValueForKey:#"fullName"];
NSString *tmp = [self primitiveFullName];
[self didAccessValueForKey:#"fullName"];
if (!tmp) {
tmp = [NSString stringWithFormat:#"%# %#", [self firstName], [self lastName]];
[self setPrimitiveFullName:tmp];
}
return tmp;
}
- (void)setFirstName:(NSString *)aFirstName
{
[self willChangeValueForKey:#"firstName"];
[self setPrimitiveFirstName:aFirstName];
[self didChangeValueForKey:#"firstName"];
[self setPrimitiveFullName:nil];
}
- (void)setLastName:(NSString *)aLastName
{
[self willChangeValueForKey:#"lastName"];
[self setPrimitiveLastName:aLastName];
[self didChangeValueForKey:#"lastName"];
[self setPrimitiveFullName:nil];
}
+ (NSSet *)keyPathsForValuesAffectingFullName
{
return [NSSet setWithObjects:#"firstName", #"lastName", nil];
}

Mapkit Annotation type when zooming in and out?

i am working with Mapkit and i am on SDK 4.2, i am having a strange bug here, in fact i have 3 annotation types, "blue.png", red.png,black.png. I am loading these by a flux and depending on the type its will select these annotation types. Everything works fine when the map is loaded i have the the different annotation view, but when i move , zoom in or zoom out the annotation view changes i.e where it was supposed to be blue.png it becomes black.png.
I am actually testing it on device.
Thank you very much :)
Hey veer the problem is that this method is called if the user pans the map to view another location and then comes back to the place where the annotations are plotted.
- (MKAnnotationView *)mapView:(MKMapView *)mapview viewForAnnotation:(id <MKAnnotation>)annotation
I have seen many sample code for map application and this in what most of the people are using.
- (MKAnnotationView *)mapView:(MKMapView *)mapview viewForAnnotation:(id <MKAnnotation>)annotation
{
if ([annotation isKindOfClass:[MKUserLocation class]])
return nil;
static NSString* AnnotationIdentifier = #"AnnotationIdentifier";
MKAnnotationView *annotationView = [mapView dequeueReusableAnnotationViewWithIdentifier:AnnotationIdentifier];
if(annotationView)
return annotationView;
else
{
MKPinAnnotationView* pinView = [[[MKPinAnnotationView alloc]
initWithAnnotation:annotation reuseIdentifier:AnnotationIdentifier] autorelease];
pinView.animatesDrop=YES;
pinView.canShowCallout=YES;
pinView.draggable = YES;
pinView.pinColor = MKPinAnnotationColorGreen;
return pinView;
}
return nil;
}
i found the solution - in fact i am using a custom annotation view and having 3 diff types of images :
Soln:
- (AnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>)annotation
{
AnnotationView *annotationView = nil;
// determine the type of annotation, and produce the correct type of annotation view for it.
AnnotationDetails* myAnnotation = (AnnotationDetails *)annotation;
if(myAnnotation.annotationType == AnnotationTypeGeo)
{
// annotation for your current position
NSString* identifier = #"geo";
AnnotationView *newAnnotationView = (AnnotationView *)[self.mapView dequeueReusableAnnotationViewWithIdentifier:identifier];
if(nil == newAnnotationView)
{
newAnnotationView = [[[AnnotationView alloc] initWithAnnotation:myAnnotation reuseIdentifier:identifier] autorelease];
}
annotationView = newAnnotationView;
}
else if(myAnnotation.annotationType == AnnotationTypeMyfriends)
{
NSString* identifier = #"friends";
AnnotationView *newAnnotationView = (AnnotationView *)[self.mapView dequeueReusableAnnotationViewWithIdentifier:identifier];
if(nil == newAnnotationView)
{
newAnnotationView = [[[AnnotationView alloc] initWithAnnotation:myAnnotation reuseIdentifier:identifier] autorelease];
}
annotationView = newAnnotationView;
}
}

Resources