I have a question based on this link, just to show you what it look like
http://www.techotopia.com/index.php/An_iPhone_OS_Core_Data_Tutorial
Scroll down to almost the bottom of the page to see how the user interface look like.
My problem is that I tried to retrieve the text that I typed in and saved it via save button. After retrieving the text, I want to present it on the screen when I open application next time(on UILabel or whatever). There should be some code in viewDidLoad but I'm clueless on how to retrieve the text or anything else I save.thanks in advance
There are a variety of ways you could approach this issue, including:
1.You could fetch an array of objects to display, based on some criteria (see below). A dateSaved/dateModified attribute might fill such a role.
2.You could put a flag (BOOL) on your CoreData object and fetch based on that flag (modify below). You'd likely want to clear that flag at some point.
3.You could store the value you'd like to display in NSUserDefaults as defaultObject (or something like that).
Here's a little code snippet that may help...
- (NSArray *)findContactsNamed:(NSString *)aName inContext:(NSManagedObjectContext *)aContext
{
NSEntityDescription *entityDesc = [NSEntityDescription entityForName:#"Contacts" inManagedObjectContext:aContext];
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:entityDesc];
NSPredicate *pred = [NSPredicate predicateWithFormat:#"(name = %#)", aName];
[request setPredicate:pred];
return [context executeFetchRequest:request error:nil];
}
Related
Maybe I'm not going about showing a detail for a selected row using CoreData, but I can't figure out why I'm getting a "BAD_ACCESS" error. I've googled around and can't find what I'm looking for.
Basically I use CoreData to populate the data for a Table View. It retrieves all of the title attributes for all of the entities. When the user clicks on a row, I have a Detail View that needs to show the description for that entity. I think I need to make a new NSManagedObjectContext and a new NSEntityDescription for a new NSFetchRequest in my DetailViewController and then use a NSPredicate to say "where title = [user selected title]". I get an error when I select a row. See code:
- (void)viewDidLoad
{
// Do any additional setup after loading the view from its nib.
// Get the objects from Core Data database
Caregiver_Activity_GuideAppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];
NSManagedObjectContext *context = [appDelegate managedObjectContext];
NSEntityDescription *entityDescription = [NSEntityDescription
entityForName:#"Definition"
inManagedObjectContext:context];
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:entityDescription];
NSPredicate *pred = [NSPredicate predicateWithFormat:#"(title = %#)", self.title];
[request setPredicate:pred];
NSError *error;
NSArray *objects = [context executeFetchRequest:request error:&error];
if (objects == nil) {
NSLog(#"There was an error!");
// Do whatever error handling is appropriate
}
for (NSManagedObject *oneObject in objects) {
[definitionDescriptionTextView setText:[oneObject valueForKey:#"desc"]];
}
[objects release];
[request release];
[super viewDidLoad];
}
I comment out that code and everything works. But when I try to debug with breakpoints, nothing catches. So I'm more confused.
I know CoreData is probably overkill for what I'm doing but this is a learning app for me.
EDIT: I didn't include that I'm using a sqlite database that is pre-populated with the entities.
You can also download my project on my github page.
Normally, with a Core Data backed Master-Detail interface, you don't fetch for the Detail view.
When you select a row in the Master tableview, you are selecting a particular managed object instance. You then pass that managed object instance to the detail view. There is no need to refetch the object that you selected in the tableview.
A good example of this would be the Contacts app. The Master tableview would be a list of Contact objects (displaying the name.) When you select a row, the Master tableview controller takes the specific Contact object associated with the selected row and then passes it to the Detail view controller which then populates the Detail view using data taking from the properties of the passed Contact object.
So, that entire code block where the error occurs is unnecessary.
However, the immediate error in this code is that you are releasing an object you didn't create. In this line:
NSArray *objects = [context executeFetchRequest:request error:&error];
... you are not creating a NSArray instance with a init, new or create method. Instead, you are merely receiving an autoreleased NSArray instance created and returned by the context NSManagedObjectContext instance. When you release an object you did not create here:
[objects release];
... you cause the crash.
Conversely, you do create a NSFetchRequest here:
NSFetchRequest *request = [[NSFetchRequest alloc] init];
... because you used init so you do have to balance that with:
[request relwase];
BTW, this type of code should not be put in viewDidLoad as the method is only called when the view is read in the first time from the nib file on disk. That is only guaranteed to happen once as the view may remain in memory when the user switches to another view. Instead, put code that needs to run each time the view appears in viewWillAppear.
Is there a method similar to insertNewObjectForEntityName that edits the current entity being passed in managed object context? I don't want to create another duplicate entity.
In addition, I don't want users to be able to enter two entities with identical attributes (one attribute, the event title). How can I make it so an alert pops up when they try to add a new entity with an identical title attribute?
Your first question it sounds like what you want to do is get an object that is already in the context with a fetch request, change some values on the object then call the -save method on your context.
For the second part, what you would do is when the user tries to add an item, search the context for an object with the same title, if the item exists, pop up an alert.
Edit: here is some code from my app (edited a bit) in which I set up and execute a fetch request:
NSFetchRequest *categoryRequest = [[NSFetchRequest alloc] init];
[categoryRequest setEntity:[NSEntityDescription entityForName:#"Category" inManagedObjectContext:[self managedObjectContext]]];
NSString *categoryName = #"Cooking";
NSPredicate *categoryNameMatchesPredicate = [NSPredicate predicateWithFormat:#"name MATCHES %#", categoryName];
[categoryRequest setPredicate:categoryNameMatchesPredicate];
NSError *error = nil;
NSArray *categoryArray = [[self managedObjectContext] executeFetchRequest:categoryRequest error:&error];
After this request, the array categoryArray contains all category entities with the name "Cooking". If there are no entities with the name "Cooking" the array will be empty.
It is probably faster to use -countForFetchRequest:error: and check for nonzero count before you actually execute the fetch request, but I am not sure it matters that much in a smallish iOS app.
So im really new to core data, but i went through a tutorial and pretty much understand it, well at least the idea behind most of the things. But I still have 1 question that i cant find anywhere. It seems really simple but here it is. If I were to have two strings inside one entity lets say:
1.name
2.position
If the name is already entered how might i allow a user to enter text into a textField and assign it to their position at a later time? Even if there were 20 names, considering no duplicates?
I was thinking it might be something like this...But it doesnt seem to work.
UserInfo *userInfo = (UserNumber *)[NSEntityDescription insertNewObjectForEntityForName:#"UserInfo" inManagedObjectContext:managedObjectContext];
if ([userName isEqualToString:"#James"]) {
userInfo.Position = nameField.text;
}
On the code above you are casting (UserNumber*) to an object that you are declaring as (UserInfo*)? Which is what and is there any reason why you are doing that?
If I understand your question correctly, you want to create a record with only the username pre-populated and then allow that record to be updated at a later stage.
I will assume your entity is called UserInfo and that there are 2 NSString properties created for it - userName and position. I also assume you have created the class files for UserInfo and imported the header into the relevant view controllers.
Here's how you would do it:
1) Firstly, assuming you have username typed in a UITextField *userNameField, let's create a new record.
UserInfo *userInfo = (UserInfo*)[NSEntityDescription insertNewObjectForEntityForName:#"UserInfo" inManagedObjectContext:self.managedObjectContext];
[userInfo setValue:userNameField.text forKey:#"userName"];
This will create a new instance of UserInfo in your managed object context and set the value of userName to the value on userNameField.text
Then at a later stage a user will get to a point where they can update their records in your app (you may need to think about authentication somewhere here). You will fetch the record that matches your specified username:
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSPredicate *userNamePredicate = [NSPredicate predicateWithFormat:#"(userName == %#)", userNameField.text];
[fetchRequest setPredicate:userNamePredicate];
NSEntityDescription *userInfo = [NSEntityDescription entityForName:#"UserInfo" inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:userInfo];
NSError *error;
NSArray *fetchRequestArray = [self.managedObjectContext executeFetchRequest:fetchRequest error:&error];
[fetchRequest release];
If the fetchRequest found match(es) to your userNameField.text paramater, they will be saved in the fetchRequestArray. There should only be a maximum of one object there if you take the necessary steps to make the userName property mandatory AND unique.
Access the object by grabbing the objectAtIndex:0 in the array and change it's position property:
UserInfo *userInfoToBeEdited = [fetchRequestArray objectAtIndex:0];
[userInfoToBeEdit setValue:positionTextField.text forKey:#"position"];
In both cases above, remember to invoke CoreData's save method when you are ready to commit your changes. Before save is invoked your changes are only kept in your managed object context which is basically a scratch pad for your persistent data.
[EDIT TO ADD SAVE METHOD]
As per your comment, I usually have the save method below in my AppDelegate (copy/paste directly from Apple template)
- (void)saveContext
{
error = nil;
NSManagedObjectContext *managedObjectContext = self.managedObjectContext;
if (managedObjectContext != nil)
{
if ([managedObjectContext hasChanges] && ![managedObjectContext save:&error])
{
[self seriousErrorAlert];
}
}
}
And then whenever I need to save changes, from any view controller I simply grab a reference to my AppDelegate and fire it off:
AppDelegate *theDelegate = (AppDelegate*)[[UIApplication sharedApplication] delegate];
[theDelegate saveContext];
I need to reload a Person NSManagedObject before I pass it onto the next View.
This is because the fetchedResultsController I'm using is only returning a subset of attributes and I need the full set in the next view.
Thus far I'm trying something like:
- (void)tableView:(UITableView *)tableViewPassed didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
Person *partialPerson = (Person *)[self.fetchedResultsController objectAtIndexPath:indexPath];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Person" inManagedObjectContext:[partialPerson.managedObjectContext]];
NSFetchRequest *request = [[[NSFetchRequest alloc] init] autorelease];
[request setEntity:entity];
...
Now I can't seem to get the predicate to do this working correctly so far I've tried:
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"SELF == %#", partialPerson];
and
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"SELF == %#", partialPerson.objectID];
But neither of these seem to work. What am I doing wrong here? Is this a good approach?
Thanks in advance for any suggestions,
Matt
You just need to access the attributes that are not faulted and they will get faulted automatically. You do not need to refetch the object at all.
Update
Storing images or any binary data in Core Data has some basic rules to follow:
< 100kb store it in the same entity
< 1 mb store it in a separate entity on the other end of a relationship
> 1 mb store it on disk and reference it in the same entity
Sounds like you are storing too much binary data in the primary table. To correct this follow the rule above and it will solve your problem.
But that does not negate my original answer in that you can instruct your fetch to pull in attribute1, attribute3 and attribute 5 and when you need to access attribute3, you just access it and Core Data will "do the right thing" and load it when you try and access it. There is never a reason to "reload" the object in this situation.
Why you need to refetch partialPerson?
Simply pass it to the next view and you are done!!! You can do all you want with partialPerson variable on the next view. I don't see any reason why you need to refetch it.
myViewController.myPartialPerson = partialPerson;
I have a "Song" Entity and a "Tag" entity and they have a many to many relationship between them. A Song can have multiple Tags and a Tag can be applied to multiple Songs.
I want to check if a Song has a particular Tag associated with it. If the Song has the Tag associted with it, I want to show a checkmark in the table view.
For a similar logic, in Apple "TaggedLocations" sample code, the following check is made to check for the presence of the relationship.
if ([event.tags containsObject:tag]) {
cell.accessoryType = UITableViewCellAccessoryCheckmark;
}
This may be inefficient if there are a lot of Tags in the database as this will fetch all of them in the memory. Please correct me if I am wrong here.
Is there a more efficient way to check if the Song is associated with a particular Tag instead of checking in Song.Tags?
It's actually pretty easy to do, if completely undocumented. You want to create a fetch request with a predicate that has a set operation. If we imagine that your Tag model has a property called tagValue, the predicate you care about is "ANY tags.tagValue == 'footag'"
NSString *tagSearch = #"footag";
// However you get your NSManagedObjectContext. If you use template code, it's from
// the UIApplicationDelegate
NSManagedObjectContext *context = [delegate managedObjectContext];
// Is there no shortcut for this? Maybe not, seems to be per context...
NSEntityDescription *songEntity = [NSEntityDescription entityForName:#"Song" inManagedObjectContext:context];
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:songEntity];
// The request looks for this a group with the supplied name
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"ANY tags.tagValue == %#", tagSearch];
[request setPredicate:predicate];
NSError *error = nil;
NSArray *results = [context executeFetchRequest:request error:&error];
[request release];
You are correct, using that code will retrieve the entire set and the object comparison may be quite complex, depending on how many properties and relationship are part of the object's entity.
Anyway, you can not avoid a set comparison for inclusion. Probably, the best you can do is to avoid fetching all of the properties/relationships by asking Core Data to retrieve NSManagedObjectID Objects only.
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
[fetchRequest setEntity:[NSEntityDescription entityForName:#"Tag" inManagedObjectContext:[self managedObjectContext]]];
[fetchRequest setResultType:NSManagedObjectIDResultType];
NSManagedObjectID objects are guaranteed to be unique, therefore you can safely use them to check for set inclusion. This should be much more efficient for a performance perspective.