I am using core data with two different entieties
userinformation. - firstname ,lastname , city etc
phonenumber - number
user information and phone number between i have given To many realtionship
-(NSArray*)getAllPhoneBookRecords
{
// initializing NSFetchRequest
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
//Setting Entity to be Queried
NSEntityDescription *entity = [NSEntityDescription entityForName:#"userinformation"
inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];
NSError* error;
// Query on managedObjectContext With Generated fetchRequest
NSArray *fetchedRecords = [self.managedObjectContext executeFetchRequest:fetchRequest error:&error];
for (NSManagedObject *info in fetchedRecords)
{
NSLog(#"city name: %#", [info valueForKey:#"city"]);
NSLog(#"first name: %#", [info valueForKey:#"firstName"]);
NSLog(#"Last name: %#", [info valueForKey:#"lastName"]);
NSLog(#"first phonenumber is %#",[info valueForKey:#"number"]);
}
and I have calling this method
AppDelegate* appDelegate = [UIApplication sharedApplication].delegate;
self.managedObjectContext = appDelegate.managedObjectContext;
self.fetchedRecordsArray = [[appDelegate getAllPhoneBookRecords]mutableCopy];
but it is show only first table data and than it come
NSLog(#"first phonenumber is %#",[info valueForKey:#"number"]);
it is showing Terminating app due to uncaught exception 'NSUnknownKeyException', reason:
'[<userinforamtion 0x84691b0> valueForUndefinedKey:]: the entity userinforamtion is not key value coding- compliant for the key "number
but in phonenumber entity i have taken attribute for number
Am i doing the wrong realtionship between user inforamtion and phonenumber ?
First, entity names are usually CamelCase. The core data model editor even enforces that, so you are already lucky your code does not crash right there.
Second, you should have Xcode create custom classes and use their accessors. That is exactly the way to prevent undefined key errors. You would set and access the attributes like this (also much more readable):
NSLog (#"Last name: %#", info.lastName);
info.lastName = #"Doe";
Third, you should be clear what you are accessing via the relationship. The relationship itself points to an instance of another entity. To get to that entities property you have to go another step.
PhoneNumber *phoneNumber = [info.numbers anyObject];
NSLog(#"Some phone number: %#", phoneNumber.number;
Finally, make sure your attribute and relationship names are correct. E.g. if you have a to-many relationship to phone numbers, the Info relationship to PhoneNumber should appropriately be called numbers. The order of numbers is not determined, so you don't know which is the "first".
Maybe you want to look into the possibility to use the ABAddressBook instead for your purposes.
Related
I am quite new into saving into coreData and using iOS dev.
What I am trying to achieve:
I want to be able to have a user in my db that has a unique identifier / is pulled with idFB and that user can create and retrieve their work out routines.
How far have I gone?
I managed (I think) to create a method that properly retriev the routineName from the Routine entity that is associated with the right User. See the fetch method.
My problem:
I think I am not saving with the right entities relationship association User (usersExercise) <--->> Routine (userID). In order words I think my save method is not right... as I am saving the whole user to userID and it just doesnt feel right? Mainly because when it spits out the Routine.userID it pulls the whole associated user instead of a specific ID? i dont really know what to expect
Could anyone please help me build these method properly? I am very confused with the whole process of coreData saving and making the right relationships.
- (void) save {
Routine *newRoutine = [NSEntityDescription insertNewObjectForEntityForName:#"Routine" inManagedObjectContext:context];
newRoutine.users = [self getCurrentUser];
newRoutine.routineName = #"myRoutine Test Name";
NSError* error;
[context save:&error ];
NSLog(#"Saved now try to fetch");
[self fetch];
}
-(void) fetch {
NSFetchRequest *fetchRequestItems = [[NSFetchRequest alloc] init];
NSEntityDescription *entityItem = [NSEntityDescription entityForName:#"Routine" inManagedObjectContext:context];
[fetchRequestItems setEntity:entityItem];
User* user = [self getCurrentUser];
// if i try [[self getCurrentUser] usersRoutine] it shows an error
[fetchRequestItems setPredicate:[NSPredicate predicateWithFormat:#"users == %#",user]];
//Sort by last edit ordered
NSArray *sortDescriptors = [NSArray arrayWithObjects:nil];
[fetchRequestItems setSortDescriptors:sortDescriptors];
NSError *error = nil;
NSArray* Routines = [context executeFetchRequest:fetchRequestItems error:&error];
NSLog(#"result %#", [(Routine *)Routines[0] users] );
}
-(User *)getCurrentUser {
NSEntityDescription *entityDesc = [NSEntityDescription entityForName:#"User" inManagedObjectContext:context];
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:entityDesc];
if (_appDelegate.isFB)
{
request.predicate = [NSPredicate predicateWithFormat:#"idFB LIKE %#",_appDelegate.fdID];
NSError *error = nil;
NSArray *matches = [[context executeFetchRequest:request error:&error] mutableCopy];
return (User *)matches[0];
} else
{
NSLog(#"CreateRoutinePOPUP NON FB TO BE TESTED");
request.predicate = [NSPredicate predicateWithFormat:#"email LIKE %#",_appDelegate.currentUser];
NSError *error = nil;
NSArray *matches = [[context executeFetchRequest:request error:&error] mutableCopy];
return (User *)matches[0];
}
This is what the NSLog in fetch is printing:
2013-04-28 22:33:26.555 iGym[7916:c07] result <User: 0xa480580> (entity: User; id: 0xa495a00 <x-coredata://D87CEBB4-016C-4A1B-802C-2D1117BB3E51/User/p1> ; data: {
dob = "1986-12-26 00:00:00 +0000";
email = ".com";
firstTime = nil;
gender = male;
height = nil;
idFB =3333;
idUserExternal = 0;
idUserInternal = 0;
isPT = nil;
language = "en_US";
location = "London, United Kingdom";
metricSystem = nil;
name = Joan;
nickname = nil;
password = nil;
surname = Thurft;
usersExercise = "<relationship fault: 0xa4824a0 'usersExercise'>";
usersRoutine = (
"0xa495f00 <x-coredata://D87CEBB4-016C-4A1B-802C-2D1117BB3E51/Routine/p6>",
"0xa4877e0 <x-coredata://D87CEBB4-016C-4A1B-802C-2D1117BB3E51/Routine/p1>",
"0xa4877f0 <x-coredata://D87CEBB4-016C-4A1B-802C-2D1117BB3E51/Routine/p2>",
"0xa487800 <x-coredata://D87CEBB4-016C-4A1B-802C-2D1117BB3E51/Routine/p3>",
"0xa487810 <x-coredata://D87CEBB4-016C-4A1B-802C-2D1117BB3E51/Routine/p4>",
"0xa487820 <x-coredata://D87CEBB4-016C-4A1B-802C-2D1117BB3E51/Routine/p5>"
);
weight = nil;
})
also when i add NSLog(#"get current result %#", [(User *)matches[0] usersRoutine] ); to the getCurrentUser method I get the whole user's data and the relationship says
usersExercise = "<relationship fault: 0xa464730 'usersExercise'>";
Core Data is not exactly like working with a standard database where you assign some foreign key like userID to another table where you want a relationship to the User object and then use that foreign ID to find the relationship like exercise.where('user_id = ?', userID). Instead, you define actual relationships and let Core Data handle everything behind the scenes for setting up any join tables or foreign keys.
Instead of how you have it set up, you'd just have in the User entity two relationships for exercises and routines that are mapped to the Exercise and Routine entities and then you'd have an inverse relationship on the Exercise and Routine called users if it's a has-and-belongs-to-many relationship. So now, you need to replace usersExercise with exercises, usersRoutine with routines and then userID with users for the Exercise and Routine entities.
Even if you don't actually need that inverse relationship, you still need it since Core Data uses it for data integrity purposes and Xcode will give you a warning if you leave it unpopulated.
When you set up those relationships, then you would call the routines or exercises like user.exercises which will return the associated set of exercises for that user. As you noticed, Core Data will return what they call a fault for a relationship that will get fired and the data returned when you actually need the contents of that relationship. Faults are there so that you are only returned exactly what info you need instead of running unnecessary queries on the data set.
Another thing to note is that Core Data doesn't reference unique id's like userID as you are doing. Instead, each object within Core Data has a unique ID found by [objectName objectID] (which is only permanent after it's been saved to the data store). You really shouldn't need to setup a unique ID as an attribute on an entity except for special cases.
Also, you really shouldn't need to use those unique objectID's unless you're passing objects around like in a multi-threaded application for background processing in which case NSManagedObjectID is thread-safe and you can use it to find the object again on a background thread/managed object context.
I'd really recommend reading a good intro to Core Data such as http://www.raywenderlich.com/934/core-data-on-ios-5-tutorial-getting-started
It can be a little strange at first converting to Core Data if you're used to normal database setup/architecture, but once you get used to it, it's actually a lot faster and handles all of the hard work behind the scenes for you.
Update from the comments:
You're misunderstanding the concept of relationships in Core Data. In Core Data, a relationship does not return an associated ID like a typical database join relationship would. Instead, it returns a fault which gets fired when you need the data from that relationship. So it's not returning the entire User object, but a fault to the associated User object which gets fired and queried when you do something like exercise.user.name
Your code is working exactly like it should be when you're saving, you are just under the incorrect assumption that it's not.
You need to use the provided method to add a "many object" in the one to many object. In your case it is called addRoutineObject:
Try this new save method:
- (void) save {
Routine *newRoutine = [NSEntityDescription insertNewObjectForEntityForName:#"Routine" inManagedObjectContext:context];
newRoutine.routineName = #"myRoutine Test Name";
NSEntityDescription *entityDesc = [NSEntityDescription entityForName:#"User" inManagedObjectContext:context];
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:entityDesc];
NSArray *matches;
NSError *error = nil;
if (_appDelegate.isFB)
{
request.predicate = [NSPredicate predicateWithFormat:#"idFB LIKE %#",_appDelegate.fdID];
matches = [[context executeFetchRequest:request error:&error] mutableCopy];
} else
{
NSLog(#"CreateRoutinePOPUP NON FB TO BE TESTED");
request.predicate = [NSPredicate predicateWithFormat:#"email LIKE %#",_appDelegate.currentUser];
matches = [[context executeFetchRequest:request error:&error] mutableCopy];
}
if (matches.count == 0)
{
NSLog(#"no user matched");
}
else
{
User *aUser = [matches objectAtIndex:0];
[aUser addRoutineObject:newRoutine];
if (![context save:&error])
{
NSLog(#"couldn't save: %#", [error localizedDescription]);
}
}
}
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 am new to iPhone development trying to figure out how to setup a one to many relationship Core Data. I have two Entities setup One Leagues which has a one to many relationship to an entity Teams. So lot's of teams in a league. Think all the teams that play baseball and are in the MLB.
I am pre-filling the data when the user first enter's their username and password, so I have the leagues populate first, which I have no problem with. Then when I begin adding the teams I do a search looking for league. Code is below.
- (NSMutableSet *)checkItem:(int *)identifier inTable:(NSString *)table inContext:(NSString *)context {
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:table inManagedObjectContext:managedObjectContext];
[fetchRequest setEntity:entity];
NSPredicate *predicate = [NSPredicate predicateWithFormat:context,identifier];
[fetchRequest setPredicate:predicate];
NSError *error;
NSArray *items = [managedObjectContext executeFetchRequest:fetchRequest error:&error];
[fetchRequest release];
if ([items count] >= 1) {
return [items objectAtIndex:0];
} else {
return nil;
}
}
By calling this and then set up the team entry and save ('league' is my relationship):
NSMutableSet *leagueObjectSet = [self checkItem:lid inTable:#"Leagues" inContext:#"id=%i"];
NSManagedObject *teamObject = [NSEntityDescription insertNewObjectForEntityForName:#"Teams" inManagedObjectContext:managedObjectContext];
[teamObject setValue:[NSNumber numberWithInt:tid] forKey:#"id"];
[teamObject setValue:[dict valueForKey:#"name"] forKey:#"teamName"];
[teamObject setValue:leagueObjectSet forKey:#"league"];
[self saveAction];
When I run this I get this error message in the console.
"the entity Teams is not key value coding-compliant for the key "league"."
Am I doing this right? With everything I have read it seems like I am. I come from a MySQL background so be gentle!
From your description, I believe that you have a wrong setup for your one to many relationship. Assuming you have two entities named League and Team, and that you have correctly setup the relationship, when you want to relate a team to a league, your league object must not be a NSMutableSet as shown in your code snippet: it must be a NSManagedObject too. A League object, being on the to many side of the relationship will have a property named teams or similar which will be a NSMutableSet. A Team object, on the one side of the relationship will have instead a property named league or similar which is a League object.
Then, to setup the relationship you can do as you did using key value coding, but for performance reasons it is best to do
teamObject.id = yourId;
teamObject.teamName = #"your team name";
teamObject.league = yourLeagueObject;
This is much faster and suggested by Apple.
I'm hoping someone can help me debugging an incredibly frustrating Core Data crash. I initially load data into two entities (representing 'speakers' and 'titles') I then load a third entity for 'sessions' and try to set relationships to 'speakers' and 'titles'. (from session to speaker and title is one-to-one. from speaker/title back to session is one-to-many). I use a integer key to set up each relationship.
Since a given speaker and title can point to multiple sessions, I wrote a function that searches for the appropriate managed object and returns the object. I then set it for the relationship.
This works fine for the speaker relationship but crashes consistently and horribly for the SECOND title. I've rewritten the code a couple of times in different ways and I always end up with the same problem. And the problem exists regardless of what title comes second. So I've gotta be doing something just fundamentally wrong but following along to the Core Data chapters in More iPhone 3 Development nothing jumps out to me. I'm hoping someone might see what I'm missing (and have been for days). (One last note: the crash occurs whether I save the managedObjectContext within the for loop or outside. Always on the second session). My endless thanks and first born child to whoever can help me with this.
Here's the relevent code that saves the session entity:
for (NSDictionary *session in self.sessions){
NSManagedObject *newSession = [NSEntityDescription insertNewObjectForEntityForName:[sessionEntity name] inManagedObjectContext:self.managedObjectContext];
[newSession setValue:[session valueForKey:#"ID"] forKey:#"id"];
[newSession setValue:[session valueForKey:#"notes"] forKey:#"notes"];
[newSession setValue:[session valueForKey:#"length"] forKey:#"length"];
//get the speaker value;
[newSession setValue:[self setupSpeaker:[session valueForKey:#"speaker"]] forKey:#"speaker"];
NSLog(#"now doing title");
//now get the title value;
[newSession setValue:[self setupTitle:[session valueForKey:#"title"]] forKey:#"title"];
NSLog(#"I got back this title:%#", [newSession valueForKey:#"title"]);
}
if (![self.managedObjectContext save:&error]) {
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
Here's the code that finds the appropriate speaker and title entity for the relationship (I realize it's pretty redundant)
-(NSManagedObject *) setupSpeaker:(NSNumber *)id {
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Speaker" inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"id==%#",id];
[fetchRequest setPredicate:predicate];
NSError *error;
NSArray *items=[self.managedObjectContext executeFetchRequest:fetchRequest error:&error];
[fetchRequest release];
if ([items count]>=1){
return [items objectAtIndex:0];
}else{
return 0;
}
}
-(NSManagedObject *) setupTitle:(NSNumber *)id {
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Title" inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];
NSLog(#"now looking for: %#", id);
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"id==%#",id];
[fetchRequest setPredicate:predicate];
NSError *error;
NSArray *items=[self.managedObjectContext executeFetchRequest:fetchRequest error:&error];
[fetchRequest release];
if ([items count]>=1){
NSLog(#"found %#", id);
return [items objectAtIndex:0];
}else{
return 0;
}
}
And finally here's what the logs says on crash:
2010-02-20 16:48:17.134 iconf[1438:207] now looking for: 1
2010-02-20 16:48:17.136 iconf[1438:207] found 1
2010-02-20 16:48:17.156 iconf[1438:207] I got back this title:<NSManagedObject: 0x3b11a10> (entity: Title; id: 0x3d4a3c0 <x-coredata://B76F62BD-AC82-4335-9013-7529C2471F9C/Title/p6> ; data: {
id = 1;
session = (
0x3d51640 <x-coredata:///Session/t2765697F-14C9-4282-A067-10A2413732B834>
);
title = "Bill Gates Speaks";
})
2010-02-20 16:48:17.158 iconf[1438:207] now doing title
2010-02-20 16:48:17.158 iconf[1438:207] now looking for: 2
2010-02-20 16:48:17.159 iconf[1438:207] found 2
2010-02-20 16:48:17.161 iconf[1438:207] I got back this title:<NSManagedObject: 0x3b16fd0> (entity: Title; id: 0x3d4d7a0 <x-coredata://B76F62BD-AC82-4335-9013-7529C2471F9C/Title/p12> ; data: {
id = 2;
session = (
0x3b1b320 <x-coredata:///Session/t2765697F-14C9-4282-A067-10A2413732B835>
);
title = "Lecture on Frogs";
})
2010-02-20 16:48:17.161 iconf[1438:207] *** -[NSManagedObject compare:]: unrecognized selector sent to instance 0x3b11a10
2010-02-20 16:48:17.162 iconf[1438:207] Serious application error. Exception was caught during Core Data change processing: *** -[NSManagedObject compare:]: unrecognized selector sent to instance 0x3b11a10 with userInfo (null)
2010-02-20 16:48:17.163 iconf[1438:207] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** -[NSManagedObject compare:]: unrecognized selector sent to instance 0x3b11a10'
2010-02-20 16:48:17.163 iconf[1438:207] Stack: (
It sounds like your data model (should) look like this:
Speaker <-->> Session
Title <-->> Session
Where both Speaker and Title each have a to-many relationship to Session.
Based on this:
I use a integer key to set up each
relationship.
and your code, it looks like you are managing the relationships manually. Why are you doing this?! It is complicated and unnecessary. Set up and use real relationships in your Core Data data model.
Also, do not use "id" for an attribute name since it is a keyword in objective-C.
Thanks, below is a copy of the data model. You are right about the basic layout.
http://www.freeimagehosting.net/image.php?debf5866c1.jpg
The reason I'm setting the relationships manually is because this is the first time through the program so I'm doing the initial load of data from three separate plists (one for speaker, one for title, one for session). Is there a better way to do this? I could be fundamentally not understanding core data but it seems as though if I just created a new title entity each time I created the session entity I'd have a one-to-one relationship from title to session rather than the one-to-many relationship that I want. Thus I put in the id variables (which I've now renamed with no change in the error) to act as a key for the first load into core data. After that I'd of course use core data to manage all that. Is there a better way to do this?
I've added the following to the AppDelegate in the Core Data Recipes example, but I don't get any printed result in the Console...any ideas?
#pragma mark -
#pragma mark TESTING RETRIEVAL OF RECIPE NAMES
- (NSArray *)allRecipes{
NSFetchRequest *request = [[[NSFetchRequest alloc] init] autorelease];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"name" inManagedObjectContext:self.managedObjectContext];
[request setEntity:entity];
NSArray *result = [[NSArray alloc] init];
result = [self.managedObjectContext executeFetchRequest:request error:NULL];
NSManagedObject *firstObject = [result objectAtIndex:0];
NSLog(#"firstObject.name: %#", [firstObject valueForKey:#"name"]);
}
XCode gives me a warning after this method: "Control reaches end of non-void function"
I think that means I'm not getting any results...which is obvious, because nothing is printing.
Does "name" exactly match the class of the entity - NOT the attribute you want from the entity - in your .xcdatamodel file?
Are you checking that in the app delegate the setup methods are completing without error?
Have you checked for managedObjectContext != nil?
Have you edited the .xcdatamodel file? If you have done so since running the program last you either need to delete the data file from the simulator's Documents directory for your application or you need to Add Model Version for your datamodel.
As gerry3 said, your first problem is that the function specifies you must return an array and you don't return anything.
Another problem is that you fetch attributes of class "name" but then you use "name" again as valueForKey. Do you really have an entity "name" with attribute "name"? More likely your entity is named something else and name is its attribute - in which case you need to alter the NSEntityDescription method entityForName: argument.