Core data is already handling the relationship for me, do I need to build a new query to use the NSFetchedResultsController?
I have "Albums" that contain "Photos".
Album *anAlbum = [...getalbum...];
in the *cellForItemAtIndexPath: I'd like to do something with:
anAlbum.photos
However, I can't convert the indexPath to a NSSet member index. (obviously)
Without Core Data I'd typically just generate the query required myself. I'd like to make use of Core Data (again, obviously).
The "Photo" entity (anAlbum.photos is the relationship) contains the
asset url. I have no issue with the displaying it was more of a
concern of how do I use the NSSet (Core Data relationship) with the
NSFectchedResultsController -- or direct with the view
(collection/table).
First of all I would use a NSFetchedResultsController for this. This component is made to work in conjunction with tables and allows to load data in a lazy loading manner.
Second, the fetch request I would use should be run against Photo entity and not against Album. In other words, you should select all the photos that belong to a specific album.
Here the code, I would use...
- (NSFetchedResultsController *)fetchedResultsController {
if (_fetchedResultsController != nil) {
return _fetchedResultsController;
}
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription
entityForName:#"Photo" inManagedObjectContext:managedObjectContext];
[fetchRequest setEntity:entity];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"album == %#", anAlbum];
[request setPredicate:predicate];
NSSortDescriptor *sort = [[NSSortDescriptor alloc]
initWithKey:#"takeAt" ascending:NO];
[fetchRequest setSortDescriptors:[NSArray arrayWithObject:sort]];
[fetchRequest setFetchBatchSize:20];
NSFetchedResultsController *theFetchedResultsController =
[[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
managedObjectContext:managedObjectContext sectionNameKeyPath:nil
cacheName:nil];
self.fetchedResultsController = theFetchedResultsController;
return _fetchedResultsController;
}
So now in your tableView:cellForRowAtIndexPath: you would use
Photo *photo = [_fetchedResultsController objectAtIndexPath:indexPath];
// access the album the photo belongs to
Album* album = photo.album;
You can grasp the main concepts for NSFetchedResultsController at Core Data Tutorial for iOS: How To Use NSFetchedResultsController.
Usually, you're going to convert the set to an NSArray (maybe with NSSet's allobjects) and perhaps sort it or filter it with a NSSortDescriptor or NSPredicate or both. Then, you can access the array by index using the index path section/row.
You can obviously use a NSMutableArray if that's what you need, but it is a pain to try to access NSSet items individually as you essentially have to enumerate them to find what you're looking for. Just create an array. Here's an example where I've done this:
NSArray *tableViewData = [NSArray arrayWithArray:[album.photos allObjects]];
Then, you just access the tableViewData objects with:
UIImage *obj = UIImageJPEGRepresentation([tableViewData objectAtIndex:indexPath.row], 0.50f);
Something like that.
Related
I have two entities, A and B, and the following relationships:
A -> B - To many
B -> A - To one
In other words: A can have zero or more B and B can have only one A.
I want to use NSFetchedResultsController to show my A entries in a table view, but i want to filter the results by A -> B relationship.
To do so, i have a UISegmentedControl, if the user taps the first segment i want to show only the A entries that have at least one relationship with B, and if the second segment is tapped i want to show only the entries with no relationships with B.
I'm using CoreData's NSManagedObject, so my A object has a NSSet property with all B entries in a relationships with A.
This is how i'm instantiating my NSFetchedResultsController:
NSManagedObjectContext *context = self.managedObjectContext;
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:"A" inManagedObjectContext:self.managedObjectContext];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:descriptorKey ascending:NO];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];
[fetchRequest setSortDescriptors:sortDescriptors];
[fetchRequest setEntity:entity];
NSFetchedResultsController *controller = [[NSFetchedResultsController alloc]
initWithFetchRequest:fetchRequest
managedObjectContext:context
sectionNameKeyPath:controllerKey
cacheName:nil];
NSError *error;
BOOL success = [controller performFetch:&error];
if (success) {
return controller;
}
This code get all A entries, how can i make that filter?
You need to add a predicate to your fetch request:
e.g.
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"B.#count == 0"];
[fetchRequest setPredicate:predicate];
This will filter As that don't have any related B objects.
As #Abizern mentioned in comments, you need to add a NSPredicate to your NSFetchedResultsController. The predicate would be something like:
[fetchRequest setPredicate:[NSPredicate predicateWithFormat:#"b == %#", myVarReferenceToB]];
If you only have a unique identifier in B (lets call it identifier) instead of an object reference you could write it as:
[fetchRequest setPredicate:[NSPredicate predicateWithFormat:#"b.identifier == %#", myVarReferenceToBIdentifier]];
This will produce your filter.
Every time the user changes the segmented control you will need to re-build the fetch or you will need to keep one NSFetchedResultsController around per segment.
I am trying to load items from CoreData into a UITableView. The initial way I did it was to simply grab all the objects from my BankInfo entity, stuff them into an array, and then use the array to populate the UITableViewCells:
- (NSMutableArray *) bankInfos
{
NSManagedObjectContext *context = [self managedObjectContext];
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"BankInfo" inManagedObjectContext:context];
[fetchRequest setEntity:entity];
NSError *error;
NSMutableArray *bankInfos = (NSMutableArray*)[context executeFetchRequest:fetchRequest error:&error];
return bankInfos;
}
I'd heard that NSFetchedResultsController could improve performance / memory management so I tried it out (basically implemented it the way Ray Wenderlich tutorial recommended):
- (NSFetchedResultsController *)fetchedResultsController {
if (_fetchedResultsController != nil) {
return _fetchedResultsController;
}
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription
entityForName:#"FailedBankInfo" inManagedObjectContext:managedObjectContext];
[fetchRequest setEntity:entity];
NSSortDescriptor *sort = [[NSSortDescriptor alloc]
initWithKey:#"details.closeDate" ascending:NO];
[fetchRequest setSortDescriptors:[NSArray arrayWithObject:sort]];
[fetchRequest setFetchBatchSize:20];
NSFetchedResultsController *theFetchedResultsController =
[[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
managedObjectContext:managedObjectContext sectionNameKeyPath:nil
cacheName:#"Root"];
self.fetchedResultsController = theFetchedResultsController;
_fetchedResultsController.delegate = self;
return _fetchedResultsController;
}
What I'm finding after using instruments to profile the code is that the NSFetchedResultsController is taking about twice as long to load the objects into the UITableView as my initial method was. This line in particular:
BankInfo *bankInfo = [_fetchedResultsController objectAtIndexPath:indexPath];
is taking 292 ms whereas loading the entire array of BankInfos is taking about 150 ms. Anyone know why this is?
The problem I was having didn't have to do with CoreData performance, but had to do with the fact that I was accidentally saving / loading full size images as thumbnails in the tableview. Once I fixed this, the performance issues went away.
Well, we are talking about ms, it is still quite fast.
The fetched results controller is doing a query to sqllite for each cell. You can turn on sqllite debug in xcode: -com.apple.CoreData.SQLDebug 1 and see for yourself.
The NSArray is populated, stored, and fetched entirely in memory.
The choice between an array and fetched controller is not to be done by taking 'speed' into account.
Basically, if you have a small array of objects, immutable while on screen, then you can safely choose NSArray as table datasource.
Instead, if you have lot of objects or planning to have a growing numbers of objects, that also need to be refreshed often, NSFetchedResultsController is the preferred choice.
I'm trying to modify a simple Core Data fetch request for contacts to only look for contacts with a relationship with a certain tag. Contact and Tag are both entities with a many-to-many relationship.
I understand with Core Data I can do this by first fetching the Tag object, and then calling tag.contact, but I don't want to do it this way as the rest of the code is dependent on the fact that the fetchResultsController returns Contact objects, not Tag objects.
If I were to do relational databasing, I could do a simple cross-table query and find all contacts with a certain tag. Is there a simple way I can replicate this via Core Data?
-(NSFetchedResultsController *) fetchedResultsController {
//if fetch controller already exists
if(_fetchedResultsController != nil) {
return _fetchedResultsController;
}
//create a new fetch request
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Contact"
inManagedObjectContext:[self managedObjectContext]];
[fetchRequest setEntity:entity];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"lastName"
ascending:YES];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];
[fetchRequest setSortDescriptors:sortDescriptors];
//instantiate the fetch controller with the fetch request and sort by last name into sections
_fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:[self managedObjectContext] sectionNameKeyPath:nil cacheName:nil];
//declare delegate of fetch controller as self
_fetchedResultsController.delegate = self;
NSLog(#"fetchResultsController Created");
return _fetchedResultsController;
}
Use NSPredicate.
Lets say you have related Contacts with Tag by name tags and tag entity has property name.
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"ANY tags.name = [cd] %#", #"sales"];
[fetchRequest setPredicate:predicate];
I am new to iOS Developing, and I'm getting my feet wet in Core Data. I've got an app that I'm slowly piecing together (thanks to help from this site!), but I'm stuck on how to fetch data.
The app:
My app has two main screens, both UITableViews: A "Class List" view where they can add new classes, and an "Add My data model has two entities: Course (for class) and Student, with a to-many relationship from Course-Student. Right now I have it working so that when a I tap on a class in the "Class List" view I am taken to the "Add Students" view where I can add students to the class, but my fetch results controller is returning all students that I have added. My question: how do I format the fetch request in the "Add Students" view to fetch only those students that should belong to that class? Here is the fetched results controller I have right now:
-(NSFetchedResultsController *) fetchedResultsController {
if (_fetchedResultsController !=nil) {
return _fetchedResultsController;
}
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Student" inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"name" ascending:YES];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];
[fetchRequest setSortDescriptors:sortDescriptors];
_fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:self.managedObjectContext sectionNameKeyPath:#"name" cacheName:nil];
_fetchedResultsController.delegate = self;
return _fetchedResultsController;
}
I'm guessing it has something to do with predicates, but I haven't gotten that far in my learning. Any help at all would be appreciated. Thanks!
You are guessing right. To fetch all students that belong to a particular course,
add the following predicate to the fetch request:
Course *theCourse = ...; // your Course object
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"course = %#", theCourse];
[fetchRequest setPredicate:predicate];
(assuming that the to-one relationship from Student to Course is called "course").
I know how to sort Core Data objects in a tableview by NsDate, but this by default seems to create a new section for each object. I want to sort them by a medium formatted date with NSDateFormatter. How would I do this?
For example, if I have 3 objects created on the same day, I want them to be in the same section with the section title being that Day, no time needed.
Each object has an NSDate property. Thanks for your help.
This is the code I have in fetchedResultsController with rgeorge's suggestions. What am I missing here?
- (NSFetchedResultsController *)fetchedResultsController {
if (fetchedResultsController != nil) {
NSLog(#"get old fetched controller");
return fetchedResultsController;
}
else{
NSLog(#"get new fetched controller");
}
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"InTextEntity" inManagedObjectContext:managedObjectContext];
[fetchRequest setEntity:entity];
[fetchRequest setFetchBatchSize:20];
NSSortDescriptor *dateDescriptor = [[NSSortDescriptor alloc] initWithKey:#"dateModified" ascending:NO];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:dateDescriptor, nil];
[fetchRequest setSortDescriptors:sortDescriptors];
NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:managedObjectContext sectionNameKeyPath:#"mediumFormattedDate" cacheName:#"Root"];
aFetchedResultsController.delegate = self;
self.fetchedResultsController = aFetchedResultsController;
NSError *error = nil;
if (![fetchedResultsController performFetch:&error]) {
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
}
return fetchedResultsController;
}
(I'll write this up assuming you're using an NSFetchedResultsController to drive your tableview. If you're not, I recommend checking it out.)
An interesting feature of NSFetchedResultsController's sectioning abilities: although the property you sort on must be a modeled property (because sqlite does the actual sorting), the property you group the sections with need not be. The only requirement is that the grouping be consistent with the ordering. (i.e., sorting by the sort property will put the objects with matching group properties next to each other.)
So just add something like this to your modeled object class:
// in interface
#property (nonatomic, readonly) NSString *mediumFormattedDate;
// in impl
-(NSString *)mediumFormattedDate
{
// this can be fancier if you need a custom format or particular timezone:
return [NSDateFormatter localizedStringFromDate:self.date
dateStyle:NSDateFormatterMediumStyle
timeStyle:NSDateFormatterNoStyle];
}
(no need to mention mediumFormattedDate in the .xcdatamodel at all.)
Then go ahead and sort your objects by the date property, but group them by your new property. When you create your NSFetchedResultsController, do so along these lines:
NSFetchRequest *fr = [NSFetchRequest fetchRequestWithEntityName:#"MyFancyEntity"];
NSSortDescriptor *sd = [NSSortDescriptor sortDescriptorWithKey:#"date"
ascending:YES];
[fr setSortDescriptors:[NSArray arrayWithObject:sd]];
NSFetchedResultsController *frc =
[[NSFetchedResultsController alloc] initWithFetchRequest:fr
managedObjectContext:myManagedObjectContext
sectionNameKeyPath:#"mediumFormattedDate"
cacheName:nil];
// then do stuff with frc
That's all it takes! I've done this in a few apps to get date grouping and it works well.
Sounds like you're setting the section index on the fetched results controller to be your date property, which seems undesirable.
Instead you should probably be computing the section index yourself, and sorting by date. You can accomplish this in either your data model or by computing the sections manually in code.
For example, you could add a property to your managed object model called "Day" and set that to whatever value you want to use (you don't specify if its something like Monday or an actual date like 21).
You can then pass that property to the fetched results controller.
Alternatively you could implement the sections yourself, days are easy, its Monday-Sunday. Dates are a bit harder, 1-28,30,31 depending on what month it is. Then use an appropriate NSPredicate / NSFetchRequest to get the count of the items in each section.