Making an UIButton add data to Core Data Model - iphone

Hey all, been scratching my head at this for a couple of days now, but still have no idea how to do it.
I have an UIButton, by clicking it I would like it to add data to my Core Data model. At the moment I'm using navigationItem.rightBarButtonItem = addButton; from my Core Data UITableViewController to add data, but instead I like to connect a UIViewController to the core data model and have an UIButton add data to it.
For example, when this UIbutton is click, it would change to another view while also adding data to the core data model.
-(IBAction)changeviewandadddata {
SecondViewController *screen = [[SecondViewController alloc] initWithNibName:nil bundle:nil];
screen.modalTransitionStyle = UIModalTransitionStyleCoverVertical;
[self presentModalViewController:screen animated:YES];
[screen release];
}
Any help or suggestions on how to do this would be appreciated.

Here is a sample to insert data into a Core Data store.
NSManagedObject *object = [NSEntityDescription insertNewObjectForEntityForName:#"EntityName"
inManagedObjectContext:managedObjectContext];
[object setValue:#"test" forKey:#"something"];
NSError *error = nil;
if (![managedObjectContext save:&error]) {
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
//handle the error
[managedObjectContext rollback];
}

Related

TableViewController's Table View not displaying until the data is loaded

I'm facing a strange issue. I have a method which populates an array with some data (fetchData) (quite a lot actually and it's a bit slow). I'm using the array to build the rows of the table.
I've tried calling fetchData in a number of places in my code, at various times in the construction of the view and I always seem to get the following: a black screen which is shown until the data from the array is loaded. I've called fetchData from the following:
(void)viewDidLoad;
(void)viewWillAppear:(BOOL)animated;
(void)viewDidAppear:(BOOL)animated;
Since I'm using a navigation view controller, having the app appear to hang is pretty bad looking since it gets stuck on a black screen. What I was hoping my code would achieve was displaying an empty table, with a progress indicator until the data is loaded - then refresh. Unfortunately I'm not getting this far since the view isn't being loaded no matter where I call fetchData.
Help appreciated!
P.S. To get around this problem I even tried using a TTTableViewController, but the Loading view is never displayed. Typical. sigh
Your load method must be blocking the UI. You should move it to another thread and let the data load there. You can instantiate the thread in viewDidLoad.
This is a skeleton code for that you need to (using GCD)
dispatch_queue_t downloadQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(downloadQueue, ^{
... Move all the loading part here.
dispatch_async(dispatch_get_main_queue(),^{
... Do all the UI updates, mostly, [tableView reloadData];
})
})
It possible that you could add a timer to delay the call somewhere in your viewDidAppear method. For example:
- (void)viewDidAppear:(BOOL)animated {
[NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(fetchData) userInfo:nil repeats:NO];
}
This will give your app time to load the UI and start your loading screen, then start fetching the data later. You can also try fetching the data in a background thread if you would prefer to go that route
I was having the same issue with a table view not loading initially, but it worked n another .m file I had. Here is the one that worked:
add this to your viewDidLoad:
NSError *error = nil;
if (![[self fetchedResultsController] performFetch:&error]) {
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
and this to the implementation block:
#pragma mark -
#pragma mark Fetched results controller
- (NSFetchedResultsController *)fetchedResultsController {
if (fetchedResultsController == nil) {
// Create the fetch request for the entity.
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
// Edit the entity name as appropriate.
NSEntityDescription *entity = [NSEntityDescription entityForName:#"OMFrackinG" inManagedObjectContext:managedObjectContext];
[fetchRequest setEntity:entity];
// grouping and sorting optional
//NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"country" ascending:YES];
NSSortDescriptor *sortDescriptor1 = [[NSSortDescriptor alloc] initWithKey:#"state" ascending:YES];// was name
NSSortDescriptor *sortDescriptor2 = [[NSSortDescriptor alloc] initWithKey:#"name" ascending:YES];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor1,sortDescriptor2, nil];
[fetchRequest setSortDescriptors:sortDescriptors];
propriate.
NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:managedObjectContext sectionNameKeyPath:#"state" cacheName:nil];//#"state"
aFetchedResultsController.delegate = self;
self.fetchedResultsController = aFetchedResultsController;
[aFetchedResultsController release];
[fetchRequest release];
[sortDescriptor1 release];
[sortDescriptor2 release];
[sortDescriptors release];
}
return fetchedResultsController;
}

How to load Core Data on another view besides the RootViewController?

I basically have the core data and the app working correctly except for the code in the AppDelegate. The code I'm having problems with is the following:
- (void)applicationDidFinishLaunching:(UIApplication *)application {
RootViewController *tableController = [[RootViewController alloc] initWithStyle:UITableViewStylePlain];
tableController.managedObjectContext = [self managedObjectContext];
self.navigationController = [[UINavigationController alloc] initWithRootViewController:tableController];
[tableController release];
[window addSubview: [self.navigationController view]];
[window makeKeyAndVisible];
}
I don't want to make the managedObjectContext the root view controller upon launch. I'm wanting to make it another view controller. However, if I change the classes to the view controller that I'm needing it for, it loads that view controller upon launch of the app, which is not what I want to do. I still want to launch the root view but I want to be able to load the core data context for my other view controller. I'm really confused on how to fix this issue. I've spent 2 days so far trying to find a way to fix this but no luck yet. Any help would be appreciated.
Also, if I leave out the following in the appdelegate didfinishlaunching:
RootViewController *tableController = [[RootViewController alloc] initWithStyle:UITableViewStylePlain];
tableController.managedObjectContext = [self managedObjectContext];
self.navigationController = [[UINavigationController alloc] initWithRootViewController:tableController];
[tableController release];
I get this error:
Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: '+entityForName: could not locate an NSManagedObjectModel for entity name 'Hello'
EDIT:
Here is the entity code:
- (void)viewDidLoad {
[super viewDidLoad];
self.title = #"Lap Times";
UIBarButtonItem *addButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAdd target:self action:#selector(addTime:)];
self.navigationItem.rightBarButtonItem = addButton;
[addButton release];
[self fetchRecords];
}
- (void)addTime:(id)sender {
addTimeEvent *event = (addTimeEvent *)[NSEntityDescription insertNewObjectForEntityForName:#"addTime" inManagedObjectContext:self.managedObjectContext];
[event setTimeStamp: [NSDate date]];
NSError *error;
if (![managedObjectContext save:&error]) {
// This is a serious error saying the record could not be saved.
// Advise the user to restart the application
}
[eventArray insertObject:event atIndex:0];
[self.tableView reloadData];
}
- (void)fetchRecords {
// Define our table/entity to use
NSEntityDescription *entity = [NSEntityDescription entityForName:#"addTime" inManagedObjectContext:self.managedObjectContext];
// Setup the fetch request
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:entity];
// Define how we will sort the records
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"timeStamp" ascending:NO];
NSArray *sortDescriptors = [NSArray arrayWithObject:sortDescriptor];
[request setSortDescriptors:sortDescriptors];
[sortDescriptor release];
// Fetch the records and handle an error
NSError *error;
NSMutableArray *mutableFetchResults = [[managedObjectContext executeFetchRequest:request error:&error] mutableCopy];
if (!mutableFetchResults) {
// Handle the error.
// This is a serious error and should advise the user to restart the application
}
// Save our fetched data to an array
[self setEventArray: mutableFetchResults];
[mutableFetchResults release];
[request release];
}
Also if I use my own appdelegate called MyAppDelegate
MyAppDelegate *tableController = [[MyAppDelegate alloc] initWithStyle:UITableViewStylePlain];
tableController.managedObjectContext = [self managedObjectContext];
self.navigationController = [[UINavigationController alloc] initWithRootViewController:tableController];
I get the following error:
Object cannot be set- either readonly property or no setter found
I can't see the problem with the original approach you are taking? You are basically creating the managedObjectContext in your App delegate and passing it on to the tableController via assignment.
The alternative way to go about it is to get your viewController to "ask" for the managedObjectContext from the App delegate. So you'd still have your CoreData methods placed in your AppDelegate and use the following where you want to get a reference to the context. Because the managedObjectContext is lazily loaded on request, it will only get instantiated the first time you access the managedObjectContext method in your app delegate.
AppDelegate *theDelegate = (AppDelegate*)[[UIApplication sharedApplication] delegate];
self.managedObjectContext = theDelegate.managedObjectContext;
PS1: obviously you need to replace AppDelegate with the correct name for your App.
PS2: the reason you're getting the error when you make the changes is that there is no context available for CoreData to work with.
There is nothing magical about the RootViewController beside the name. You can rename it or you can exchange it with any other View Controller as long as these View Controller are configured right. You might want to take the RootViewController and adjust your new View Controller accordingly.
That said I don't understand what you want to do. You might want to post the code that doesn't work.
if you get an exception for +entityForName: you should post the code around +entityForName:.
If I had to make a guess I would say that your code looks like this:
entity = [NSEntityDescription entityForName:#"Hello"
inManagedObjectContext:managedObjectContext];
^^^^^^^^^^^^^^^^^^^^
this means you are using the managedObjectContext without the getter. And the getter uses lazy loading to load the context if it is needed for the first time.
I bet managedObjectContext is nil at this point. Use the debugger to check this out.
And then change the line like this:
entity = [NSEntityDescription entityForName:#"Hello"
inManagedObjectContext:self.managedObjectContext];
^^^^^^^^^^^^^^^^^^^^^^^^^
The code works when you include the four line about the rootviewcontroller because of this call:
tableController.managedObjectContext = [self managedObjectContext];
^^^^^^^^^^^^^^^^^^^^^^^^^^^
this will create the context if it is nil. Lazy loading.
[self managedObjectContext] is the same as self.managedObjectContext
but everything in my post is a guess because you didn't include the code around +entityForName:inManagedObjectContext:

CoreData Save Permanently?

I have been working with Core Data in an iPad app and I can successfully save and fetch data inside the app. However when completely closing the application, fully, quit, take it out of multitasking, and that data disappears.
So does Core Data in anyway keep this data anywhere when the app is closed? Or do I need to look somewhere else?
EDIT: This is in the app delegate didFinishLaunchingWithOptions: [[[UIApplication sharedApplication] delegate] managedObjectContext]; and then I have this: context_ = [(prototypeAppDelegate *)[[UIApplication sharedApplication] delegate] managedObjectContext]; in the UIView subclass.
This is the NSPersistentStoreCoordinator code premade in the app delegate:
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator {
if (persistentStoreCoordinator_ != nil) {
return persistentStoreCoordinator_;
}
NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:#"prototype.sqlite"];
NSError *error = nil;
persistentStoreCoordinator_ = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
if (![persistentStoreCoordinator_ addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&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.
Typical reasons for an error here include:
* The persistent store is not accessible;
* The schema for the persistent store is incompatible with current managed object model.
Check the error message to determine what the actual problem was.
If the persistent store is not accessible, there is typically something wrong with the file path. Often, a file URL is pointing into the application's resources directory instead of a writeable directory.
If you encounter schema incompatibility errors during development, you can reduce their frequency by:
* Simply deleting the existing store:
[[NSFileManager defaultManager] removeItemAtURL:storeURL error:nil]
* Performing automatic lightweight migration by passing the following dictionary as the options parameter:
[NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:YES],NSMigratePersistentStoresAutomaticallyOption, [NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, nil];
Lightweight migration will only work for a limited set of schema changes; consult "Core Data Model Versioning and Data Migration Programming Guide" for details.
*/
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
return persistentStoreCoordinator_;
}
So far I am using this to fetch data:
NSFetchRequest *fetch = [[NSFetchRequest alloc] init];
NSEntityDescription *testEntity = [NSEntityDescription entityForName:#"DatedText" inManagedObjectContext:context_];
[fetch setEntity:testEntity];
NSPredicate *pred = [NSPredicate predicateWithFormat:#"dateSaved == %#", datePicker.date];
[fetch setPredicate:pred];
NSError *fetchError = nil;
NSArray *fetchedObjs = [context_ executeFetchRequest:fetch error:&fetchError];
if (fetchError != nil) {
NSLog(#"fetchError = %#, details = %#",fetchError,fetchError.userInfo);
}
noteTextView.text = [[fetchedObjs objectAtIndex:0] valueForKey:#"savedText"];
And this to save data:
NSManagedObject *newDatedText;
newDatedText = [NSEntityDescription insertNewObjectForEntityForName:#"DatedText" inManagedObjectContext:context_];
[newDatedText setValue:noteTextView.text forKey:#"savedText"];
[newDatedText setValue:datePicker.date forKey:#"dateSaved"];
NSError *saveError = nil;
[context_ save:&saveError];
if (saveError != nil) {
NSLog(#"[%# saveContext] Error saving context: Error = %#, details = %#",[self class], saveError,saveError.userInfo);
}
Do you save the context in the right places? It is a common mistake not to save the context when entering background application state only in willTerminate.
Save the context in the following appdelegate method:
-(void)applicationDidEnterBackground:(UIApplication *)application
You are saving your context directly after inserting the object, this should be sufficient. Check the sqlite file in simulator if it contains any data after saving.
if
noteTextView.text = [[fetchedObjs objectAtIndex:0] valueForKey:#"savedText"];
does not throw an exception, there is an object found in context. Maybe it does not contain the expected value?
Log the returned object from your fetchrequest to console to see if this might be the case
You should post the code that sets up your NSPersistentStoreCoordinator and adds your NSPersistentStore. By any chance are you using NSInMemoryStoreType as the type of your store? Because that would result in the behavior you're seeing. Alternately, you could be using a different path to the store each time, which would give you a fresh store each time. In general, your store should be in your Documents folder, and it should be given the same name on every launch. It should also use the NSSQLiteStoreType
I have discovered the problem. It turns out that due to its use of UIDatePicker, at the start of the program it set that date picker to today using:
NSDate *now = [[NSDate alloc] init];
[datePicker setDate:now];
So without using this it works perfectly. So currently I am looking for a solution to this issue, as this line seems to cause the problem.
UIDatePicker Interfering with CoreData
If you add a CoreData after create your project.There is a risk to make mistake in lazy_init your NSManagedObject.
-(NSManagedObjectContext*) managedObjectContext{
if (!_managedObjectContext) {
_managedObjectContext =[self createManageObjectContextWithName:#"name.sqlite"];
}
return _managedObjectContext;}
Here is the right way:
- (NSManagedObjectContext *)managedObjectContext {
if (_managedObjectContext != nil) {
return _managedObjectContext;
}
NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
if (!coordinator) {
return nil;
}
_managedObjectContext = [[NSManagedObjectContext alloc] init];
[_managedObjectContext setPersistentStoreCoordinator:coordinator];
return _managedObjectContext;}

Adding single row one by one in tableview from core data of iPhone

I am working on RSS reader code where articles get downloaded and are viewable offline.
The problem is only after all articles are downlaoded the tableview containing headlines gets updated. Core data is used. So everytime NSobjectcontext is saved , [self tableview updatebegins ] is called.The table is getting updated via fetchcontroller core data.
I tried saving NSobjectcontext everytime an article is saved but that is not updating the tableview. I want a mechanism similar to instapaper tableview where articles get saved and tableview gets updated immediately. Please help if you know the solution. Thanks in advance.
Adding code for better understanding
AppDelegate.m contains following code
- (void)feedSuccess:(ZSURLConnectionDelegate*)delegate
NSManagedObjectContext *moc = [self managedObjectContext];
CXMLElement *root = [document rootElement];
CXMLElement *channel = [[root elementsForName:#"channel"] lastObject];
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:[NSEntityDescription entityForName:#"FeedItem" inManagedObjectContext:moc]];
for (CXMLElement *item in [channel elementsForName:#"item"])
{
// push element in core data and then save context
//Save context
[moc save:&error];
ZAssert(error == nil, #"Error saving context: %#", [error localizedDescription]);
}
this triggers the table change code in RootviewController.m
- (void)controllerWillChangeContent:(NSFetchedResultsController*)controller
{
[[self tableView] beginUpdates];
}
If you use the NSFetchedResults controller, simply use its delegate method
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller;
as follows:
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller {
// In the simplest, most efficient, case, reload the table view.
[self.tableView reloadData];
}
Every time you update the underlying NSManagedObjectContext, the table will be automatically updated too.

The entity xxx is not key value coding-compliant for the key "(null)"

I am trying to write a simple table view editor for a Core Data entity. Unfortunately I'm running into problems.
The error occurs when adding the very first entity to the table. The process for bringing up the modal dialog is as follows:
NSManagedObjectContext *context = [fetchedResultsController managedObjectContext];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Group" inManagedObjectContext:context];
insertNewObjectForEntityForName:[entity name] inManagedObjectContext:context];
NSManagedObject *newManagedObject = [[NSManagedObject alloc] initWithEntity:entity insertIntoManagedObjectContext:context];
NameEditController *dialog = [[NameEditController alloc] init];
dialog.managedObject = newManagedObject;
[newManagedObject release];
UINavigationController *navCtrlr = [[UINavigationController alloc] initWithRootViewController:dialog];
navCtrlr.modalTransitionStyle = UIModalTransitionStyleCoverVertical;
[[self navigationController] presentModalViewController: navCtrlr animated:YES];
[navCtrlr release];
Inside of NameEditController, I have this after the Done button is pressed:
NSString* name = self.nameLabel.text;
[self.managedObject setValue:name forKey:#"name"];
NSError *error = nil;
if (![managedObject.managedObjectContext save:&error]) {
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
}
UIViewController *ctrl = [self parentViewController];
[ctrl dismissModalViewControllerAnimated:YES];
The very first time I create an object (when the list is empty) I get this:
Exception was caught during Core Data change processing: [ valueForUndefinedKey:]: the entity Group is not key value coding-compliant for the key "(null)".
If I fill out the 'name' field before bringing up the dialog, I am able to add the first entity successfully:
[newManagedObject setValue:#"New Group" forKey:#"name"]; // this works
I am using NSFetchedResultsController to manage the table view BTW.
Thanks!
In your first code block, the third line of code appears to be out of context. Not sure if there's something there that could be contributing.
Secondly, the easiest way to get yourself an NSManagedObject into a NSManagedObjectContext from an entity name is to use the [NSEntityDescription insertNewObjectForEntityName:inManagedObjectContext:] selector.
So, I'd do something like:
NSManagedObjectContext *context = [fetchedResultsController managedObjectContext];
NSManagedObject *newObject = [NSEntityDescription insertNewObjectForEntityName:#"Group" inManagedObjectContext:context];
You won't need to release newObject anymore, since the [NSEntityDescription insertNewObjectForEntityName:inManagedObjectContext:] selector will return an object with a retain count of 0. Also, make sure that you have NameEditController specifying its managedObject property as retained.
To address your actual issue, it sounds like maybe you have 'name' specified as a required property in your data model? A screenshot showing the details for 'name' in your data model would help.
Yarr... sorry guys, it was actually in my didChangeObject:atIndexPath:forChangeType:newIndexPath: function hastily copied from elsewhere. Apparently an exception thrown here can get obscured inside of the save: method.