Update Entity properties via FetchedResultsController - ios

I made an application which is collecting user bills and payments. But it didn't have "Edit" property so I want to update my app with "Edit" property.
I have an tableviewcontroller which is listing user all payments. If user click the cell app goes "Detail Payment" view controller.
First of all there is my Add Payment View Controller prepare for segue codes ;
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([[segue identifier] isEqualToString:#"addKredi"]) {
UINavigationController *navigationController = [segue destinationViewController];
ESMAddKrediViewController *addKrediViewController = (ESMAddKrediViewController *) navigationController.topViewController;
Kredi *addKredi = [NSEntityDescription insertNewObjectForEntityForName:#"Kredi" inManagedObjectContext:[self managedObjectContext]];
Odeme *addKrediToOdeme = [NSEntityDescription insertNewObjectForEntityForName:#"Odeme" inManagedObjectContext:[self managedObjectContext]];
addKrediViewController.addKredi = addKredi;
addKrediViewController.addKrediToOdeme = addKrediToOdeme;
}
There is the Add Credit View Controller codes;
static NSString *kategoriKredi = #"Banka Kredilerim";
_addKredi.krediAdi = _txtKrediAdi.text; //Kredi Entity
_addKrediToOdeme.odemeAdi = _txtKrediAdi.text; //Odeme Entity
_addKrediToOdeme.kategori = kategoriKredi;
My tableviewcontroller fetches user all payments via "Odeme" entity.
There is my tableviewcontroller codes to Detail ViewController ;
if user select the Credit type payment (Kredi) my app checks category with those codes and sending some info to Detail View Controller ;
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
Odeme *odeme = [self.fetchedResultsController objectAtIndexPath:indexPath];
if ([odeme.kategori isEqualToString:#"Kredi Kartlarım"]){
[self performSegueWithIdentifier:#"toTaksitler" sender:nil];
}
if ([odeme.kategori isEqualToString:#"Faturalarım"]){
[self performSegueWithIdentifier:#"toFaturaDetay" sender:nil];
}
if ([odeme.kategori isEqualToString:#"Banka Kredilerim"]){
[self performSegueWithIdentifier:#"toKrediDetay" sender:nil];
}
I send odemeAdi to krediViewController.krediAdi
if ([[segue identifier] isEqualToString:#"toKrediDetay"]) {
ESMKrediViewController *krediViewController = [segue destinationViewController];
NSIndexPath *indexPath = [self.tableView indexPathForSelectedRow];
Odeme *selectedKredi = (Odeme *)[self.fetchedResultsController objectAtIndexPath:indexPath];
krediViewController.odendi = selectedKredi.odendi;
krediViewController.krediAdi = selectedKredi.odemeAdi;
krediViewController.selectedKredi = selectedKredi;
krediViewController.navigationItem.title = selectedKredi.odemeAdi;
}
I can catch the name of Credit and I fetch credit Details in my Detail ViewController with those codes ; (In CreditDetailViewController)
#pragma mark - Fetched Results Controller Section
-(NSFetchedResultsController *)fetchedResultsController{
if (_fetchedResultsController !=nil) {
return _fetchedResultsController;
}
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc]init];
NSManagedObjectContext *context = [self managedObjectContext];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Kredi" inManagedObjectContext:context];
[fetchRequest setEntity:entity];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"krediAdi" ascending:YES];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"krediAdi == %# ",krediAdi];
[fetchRequest setPredicate:predicate];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];
fetchRequest.sortDescriptors = sortDescriptors;
_fetchedResultsController = [[NSFetchedResultsController alloc]initWithFetchRequest:fetchRequest managedObjectContext:context sectionNameKeyPath:nil cacheName:nil];
_fetchedResultsController.delegate = self;
return _fetchedResultsController;
}
And my app can show all properties about selected Credit via this way.I tried to explain all my codes.But I know its complicated question and my English is not enough to explain clearly. But maybe someone can read the codes and help this desperate guy.
So there is my question.
I want to edit Credit Detail View Controller. How can i change Kredi entity properties like krediAdi ?
I fetched all Kredi Entity properties in this detail View Controller but i can't update or override properties. I stuck there.

I solved my problem with this code ;
[[[self.fetchedResultsController fetchedObjects]objectAtIndex:0]setBorcAdi:self.txtBorcAdi.text];
BorcAdi is a Borclar entity's property.
You can edit or change the entity properties with this code.
Note:I used Predicate and i get only 1 result so i can reach it with objectAtIndex:0.

Related

UITable won't refresh even with reload called on main thread

I have a main view controller(SecondViewController) with a UITable and a navigation controller. When a navigation bar button is pressed, a menu drops down from the navigation bar on top of the table. This menu is created by adding a view controller as a subview like so:
//SecondViewController.m
self = sortMenu.secondVC;
[self addChildViewController:sortMenu];
[self.view addSubview:sortMenu.view];
[sortMenu didMoveToParentViewController:self];
sortMenu contains a button that changes the order the cells are displayed in by calling a class method of the main view controller.
//SortMenuViewController.m
- (IBAction)sortButtonPressed:(id)sender {
[_secondVC sortButtonPressed:[sender tag]];
}
In sortButtonPressed, it calls a method to make a fetch request with updated sort filter value.
//SecondViewController.m
-(void)sortButtonPressed:(NSInteger)sortDescriptor{
_sortDescriptor = sortDescriptor;
currentPredicate = [NSPredicate predicateWithFormat:#"dataset & %d > 0", 4];
[self fetchResultsUsingSegmentedControlIndex];
}
The fetch request is performed and returns the data in a new order.
//SecondViewController.m
- (IBAction)fetchResultsUsingSegmentedControlIndex
{
NSString* sectionNameKeyPath = nil;
NSArray* sortDescriptors = nil;
NSSortDescriptor *scientificNameDescriptor = [[NSSortDescriptor alloc] initWithKey:#"scientificName" ascending:YES];
NSSortDescriptor *commonNameFirstDescriptor = [[NSSortDescriptor alloc] initWithKey:#"commonNameFirst" ascending:YES];
NSSortDescriptor *commonNameLastDescriptor = [[NSSortDescriptor alloc]
initWithKey:#"commonNameLast"
ascending:YES
selector:#selector(localizedCaseInsensitiveCompare:)];
if (_sortDescriptor == kSortByCommonNameFirst )
{
sortDescriptors = [[NSArray alloc] initWithObjects:commonNameFirstDescriptor, commonNameLastDescriptor, scientificNameDescriptor, nil];
sectionNameKeyPath = #"commonNameFirst";
}
else if (_sortDescriptor == kSortByCommonNameLast )
{
sortDescriptors = [[NSArray alloc] initWithObjects:commonNameLastDescriptor, commonNameFirstDescriptor, scientificNameDescriptor, nil];
sectionNameKeyPath = #"commonNameLast";
}
else if (_sortDescriptor == kSortByScientificName )
{
sortDescriptors = [[NSArray alloc] initWithObjects:scientificNameDescriptor, commonNameFirstDescriptor, commonNameLastDescriptor, nil];
sectionNameKeyPath = #"scientificName";
}
NSError *error;
NSLog(#"current predicate: %#", currentPredicate);
[[self fetchedResultsControllerWithsectionNameKeyPath:sectionNameKeyPath sortDescriptors:sortDescriptors predicate:currentPredicate] performFetch:&error];
[scientificNameDescriptor release];
[commonNameLastDescriptor release];
[commonNameFirstDescriptor release];
[sortDescriptors release];
NSUInteger sectionsCt = [[speciesFetchedResultsController sections] count];
int sum = 0;
for (int i=1; i < sectionsCt; i++){
id <NSFetchedResultsSectionInfo> sectionInfo = [[speciesFetchedResultsController sections] objectAtIndex:i];
NSUInteger numOfObj = [sectionInfo numberOfObjects];
NSLog(#" in section %d number of objects is %lu ", i, (unsigned long)numOfObj);
sum = sum + numOfObj;
}
[_table performSelectorOnMainThread:#selector(reloadData)
withObject:nil
waitUntilDone:NO];
}
When I call fetchResultsUsingSegmentedControlIndex from the main view controller (before dropping down the sort menu), it works correctly. However, when called from sortMenu, numberOfRowsInSection, numberOfSectionsInTableView, and cellForRowAtIndexPath are not called. I have tried to call reloadData on the main thread with performSelectorOnMainThread and also dispatching it to the main queue, but neither works.
I originally created a sort menu by adding a pickerview to the main view controller on pressing the navigation bar button, and my table reloaded correctly. Since creating a separate view controller for the menu (to have greater design control), it doesn't work.
Ended up using delegation.
// SortMenuViewController.h
#import <UIKit/UIKit.h>
#protocol SortMenuViewControllerDelegate <NSObject>
-(void)sortButtonPressed:(NSInteger)sortDescriptor;
-(void)viewButtonPressed:(NSInteger)viewDescriptor;
#end
#interface SortMenuViewController : UIViewController{
}
//SortMenuViewController.m
- (IBAction)changeSort:(id)sender {
[_delegate sortButtonPressed:[sender tag]];
}
//SecondViewController.h
#interface SecondViewController : UIViewController <NSFetchedResultsControllerDelegate, SortMenuViewControllerDelegate>{
}
-(void)sortButtonPressed:(NSInteger)sortDescriptor{
_sortDescriptor = sortDescriptor;
currentPredicate = [NSPredicate predicateWithFormat:#"dataset & %d > 0", dataset];
[self fetchResultsUsingSegmentedControlIndex];
}

Managedobject becomes nil in a UIViewController

Managedobject is becoming nil when i try to load its attributes in a view controller. My operation flow is as below. All my operations are happening in the main thread(thread1).
I save a new entity in MOC1.
Then am getting this newly created entity in MOC2 (I have subscribed for NSManagedObjectContextDidSaveNotification and changes done in MOC1 is merged to MOC2).
Then the retrieved Managed Object is set to singleton class's property.
After that I load a view controller.
Inside view controller when I try to get attributes of managedobject from singleton class, I get nil.
Here I create a managedobject from moc2
ManagedObjectClass* someClass = (ManagedObjectClass*)[NSEntityDescription insertNewObjectForEntityForName:#"ManagedObjectClass" inManagedObjectContext:moc2];
someClass.orderID = #"123";
NSError *error = nil;
moc2 save:&error];
// Below is my NSManagedObjectContextDidSaveNotification handler
- (void) mocDidSaveNotification:(NSNotification *)notification
{
NSManagedObjectContext *savedContext = [notification object];
// ignore change notifications for the main MOC
if ([CoreDataController sharedCoreDataController].moc2 == savedContext)
{
return;
}
dispatch_async(dispatch_get_main_queue(), ^{
[[CoreDataController sharedCoreDataController].moc1 mergeChangesFromContextDidSaveNotification:notification];
});
}
// Now get the created someClass using moc1
dispatch_async(dispatch_get_main_queue(),^{
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc]init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"ManagedObjectClass" inManagedObjectContext:moc1];
[fetchRequest setEntity:entity];
NSPredicate *matchedPredicate = [NSPredicate predicateWithFormat:#"orderID = 123"];
[fetchRequest setPredicate:matchedPredicate];
NSError *error = nil;
NSMutableArray *fetchedObjects = [[[moc1 executeFetchRequest:fetchRequest error:&error]mutableCopy]autorelease];
// Here am able to see the contents of currentOrder
[appcontroller sharedAppController].currentOrder = [fetchedObjects objectAtIndex:0]
});
// Now load view controller
DetailsViewController *aSelectedViewController = [[DetailsViewController alloc]initWithNibName:#"DetailsViewController" bundle:nil];
UINavigationController aNavigationController = [[[UINavigationController alloc] initWithRootViewController:aSelectedViewController]autorelease];
self.applicationWindow.rootViewController = navigationController;
// View controller's implementation
#implementation DetailsViewController
- (void)viewDidLoad
{
if([appcontroller sharedAppController].currentOrder == nil)
{
NSLog(#"currentOrder became nil");
}
}
#end

Deleting object from one to many relationship in Core Data

I currently have a problem deleting an object in a to many relationship.
My app have the following relationship:
Product <<- Cart
When the user pushes a "add to cart" button in my viewcontroller, the following code is setting the relations between the product object and the cart
+ (Cart *)addProductToCartWithProduct:(Product *)product inManagedObjectContext:(NSManagedObjectContext *)context {
Cart *cart = nil;
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:#"Cart"];
NSError *error = nil;
NSArray *carts = [context executeFetchRequest:request error:&error];
if (!carts || ([carts count] > 1)) {
// handle error
} else if (![carts count]) {
cart = [NSEntityDescription insertNewObjectForEntityForName:#"Cart" inManagedObjectContext:context];
} else { // they already have a cart started
cart = [carts lastObject];
}
/*Get Object ID to safely pass NSMangedObject between threads (A background worker thread and the main thread). */
NSManagedObjectID *retID = [product objectID];
[cart addProductsObject:(Product *)[context objectWithID:retID]];
//Inverse relationship
[(Product *) [context objectWithID:retID] setInCart:cart];
return cart;
}
This then returns a cart object, which I pass to my cart viewcontroller, and fetch the products in that relationship like so:
// Fetch request for "Product":
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:#"Product"];
// Fetch only products for the cart:
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"inCart = %#", self.cart];
[fetchRequest setPredicate:predicate];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"navn" ascending:YES];
[fetchRequest setSortDescriptors:#[sortDescriptor]];
self.fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:_theManagedObjectContext sectionNameKeyPath:nil cacheName:nil];
_fetchedResultsController.delegate = self;
When I then try to delete an object from the relationship like so:
-(void)RemoveFromCart:(UIButton *)sender {
NSIndexPath *ip = [NSIndexPath indexPathForRow:sender.tag inSection:0];
Product *prod = (Product *)[self.fetchedResultsController objectAtIndexPath:ip];
prod.inCart = nil;
[_cart removeProductsObject:prod];
NSLog(#"Cart %# %#", _cart.products, prod);
[self saveCurrentContext:_theManagedObjectContext];
[self loadCart];
[_orderTable reloadData];
}
The product is removed visually (gone from the tableview/screen) because inCart is set to nil, but not technically... when I log the cart object, the product object is still in the relationship, so it seems like the [_cart removeProductsObject:prod]; is not working.
And it also doesn't work the other way around, when I try to add the same product to the cart, I just have deleted (from the cart), for some reason the inverse relationship "inCart" is not set, after I have set it to "nil", when the product object is removed.
Why is this happening? and how do I fix it? :).
EDIT:
Pictures showcasing inverse relationships in Core data model editor:
Pass Cart to other viewcontroller:
[[[DataManager sharedInstance] backgroundManagedObjectContext] performBlock:^{
UITabBarController *tabBarController = self.tabBarController;
for (UINavigationController *navController in tabBarController.viewControllers) {
for (UIViewController *vc in navController.viewControllers) {
if ([vc isMemberOfClass:NSClassFromString(#"CartViewController")]){
CartViewController *cartVC = (CartViewController *) vc;
cartVC.cart = [Cart addProductToCartWithProduct:prod inManagedObjectContext: [[DataManager sharedInstance] backgroundManagedObjectContext]];
[[DataManager sharedInstance] saveBackgroundContext];
[[DataManager sharedInstance] saveMasterContext];
NSLog(#" %#", cartVC.cart);
}
}
}
}];
Save Context
-(void)saveCurrentContext:(NSManagedObjectContext *)context {
NSError *error = nil;
if (![context save:&error]) {
NSLog(#"NOT SAVED");
}
[[DataManager sharedInstance] saveBackgroundContext];
[[DataManager sharedInstance] saveMasterContext];
}
Okay, so I found a work around for this very weird problem.
Since the autogenerated accessor methods didn't work for some reason, I had to think of another way to delete the object from the relationship.
for (Product *prod in _cart.products) {
//To Very reliable to check for item by name attribute, but It works :)
if ([prod.name isEqualToString:product.name]) {
product = prod;
NSMutableSet *set = [NSMutableSet setWithSet:_cart.products];
[set removeObject:prod];
_cart.products = set;
}
}

Trying to use NSFetchedResultsController to integrate CoreData into my UITableView, but it fails as soon as the app launches

I've been following this tutorial to a T, but when I run my app, it fails on launch every time with the following error:
* Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '+entityForName: nil is not a
legal NSManagedObjectContext parameter searching for entity name
'ArticleInfo''
But I can't figure out what I'm doing wrong that would cause that. As a background for what I've done, I have the datamodel file with an entity called ArticleInfo with a bunch of attributes of different types (some transient, if that's of note). Following the advice of this article I subclassed NSManagedObject as ArticleInfo.
If it's worth noting, I made the preview, wordsInBody and progress transient in the datamodel (and I gave position a default value of 0 in the Data Model Inspector). So in the subclass I made custom getters as follows:
- (NSString *)preview {
[self willAccessValueForKey:#"preview"];
NSString *preview = [self primitiveValueForKey:#"preview"];
[self didAccessValueForKey:#"preview"];
if (self.body.length < 200) {
preview = self.body;
}
else {
preview = [self.body substringWithRange:NSMakeRange(0, 200)];
}
return preview;
}
- (NSNumber *)progress {
[self willAccessValueForKey:#"progress"];
NSNumber *progress = [self primitiveValueForKey:#"progress"];
[self didAccessValueForKey:#"progress"];
if (self.body.length == 0) {
progress = #100;
}
else {
progress = #(100 * [self.position intValue] / [self.wordsInBody intValue]);
}
return progress;
}
- (NSNumber *)wordsInBody {
[self willAccessValueForKey:#"wordsInBody"];
NSNumber *wordsInBody = [self primitiveValueForKey:#"wordsInBody"];
[self didAccessValueForKey:#"wordsInBody"];
if (!wordsInBody) {
__block int numberOfWordsInBody = 0;
NSRange range = {0, self.body.length};
[self.body enumerateSubstringsInRange:range options:NSStringEnumerationByWords usingBlock:^(NSString *substring, NSRange substringRange, NSRange enclosingRange, BOOL *stop) {
numberOfWordsInBody++;
}];
wordsInBody = #(numberOfWordsInBody);
}
return wordsInBody;
}
(Again, not sure if that's relevant.)
Now in my main viewcontroller class (the one that has the tableview I'm using NSFetchedResultsController for) I declared the property, and overrode its getter:
In .h:
#property (nonatomic, strong) NSFetchedResultsController *fetchedResultsController;
In .m:
- (NSFetchedResultsController *)fetchedResultsController {
if (!_fetchedResultsController) {
NSManagedObjectContext *context = [(AppDelegate *)[[UIApplication sharedApplication] delegate] managedObjectContext];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"ArticleInfo" inManagedObjectContext:context];
NSFetchRequest *request = [[NSFetchRequest alloc] init];
request.entity = entity;
NSSortDescriptor *descriptor = [[NSSortDescriptor alloc] initWithKey:#"timeStamp" ascending:NO];
request.sortDescriptors = [NSArray arrayWithObject:descriptor];
request.fetchBatchSize = 20;
NSFetchedResultsController *fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:request managedObjectContext:context sectionNameKeyPath:nil cacheName:#"Root"];
_fetchedResultsController = fetchedResultsController;
_fetchedResultsController.delegate = self;
}
return _fetchedResultsController;
}
But again, it keeps giving me that error on app launch. What exactly am I doing wrong that's causing that error?
NSEntityDescription *entity = [NSEntityDescription entityForName:#"ArticleInfo" inManagedObjectContext:context]
Something here is nil. Log context and entity to see which one. Maybe your entity name in your model isn't "ArticleInfo" (typo?) or your app delegate has a problem creating the context.

Load CoreData objects' string property into UIPickerView

Currently I have an entity named "Events" in a CoreData app. "Events" has one string property named "eventName".
In the -(void)viewDidLoad I am trying to Fetch all the "Events" objects and load their "eventName" by alphabetical order into a UIPickerView.
The ultimate end goal is through the use of a textField, buttons and the pickerView being add new objects in and remove unwanted objects out. Basically turning the UIPickerView into a UITableView. Currently I am able to save objects to the CoreData store, but am not able to pull them/their properties out into the UIPickerView.
I am willing and able to share the project source code to anyone who wants it, or is willing to look at it to help out.
thanks
Chris
-(void)update
{
NSMutableArray *array2 = [[NSMutableArray alloc] init];
CDPickerAppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];
NSManagedObjectContext *moc = [appDelegate managedObjectContext];
NSEntityDescription *entityDescription = [NSEntityDescription entityForName:#"Event" inManagedObjectContext:moc];
NSFetchRequest *request = [[[NSFetchRequest alloc] init] autorelease];
[request setEntity:entityDescription];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"callName" ascending:YES];
[request setSortDescriptors:[NSArray arrayWithObject:sortDescriptor]];
[sortDescriptor release];
NSArray *array = [moc executeFetchRequest:request error:&error];
for (int i=0; i<array.count; i++) {
Event *theEvent = [array objectAtIndex:i];
NSString *StringOne = [NSString stringWithFormat:#"%#",theEvent.callName];
[array2 addObject:StringOne];
}
self.pickerData = array2;
[singlePicker reloadAllComponents];
}
-(IBAction)addCall{
CDPickerAppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];
NSManagedObjectContext *context = [appDelegate managedObjectContext];
NSManagedObject *theEvent = [NSEntityDescription insertNewObjectForEntityForName:#"Event" inManagedObjectContext:context];
[theEvent setValue:callField.text forKey:#"callName"];
[context save:&error];
callField.text=#"";
[callField resignFirstResponder];
self.update;
}

Resources