Core Data Drill down table - objective-c

I'm working with Core Data and I was hoping you can help me with a problem I'm having. The Example is let say I have 3 entities Make and Model, Options, and image of car with multiple attributes. There can be 1 Make, multiple Options and 1 image per Option. What I want to happen is if there is only one Option skip the Options in the drill down and go straight to the image. Example is:
Table of more than one Option
make - Model | Options | Photo
Chevy Camaro -> LS -> Image of Car.
Chevy Camaro -> LT -> Image of Car.
table of one Option
Chevy Blazer -> Image of Car
This is how I envisioned doing it
This is the if statement
if(options > 1 || count.totalCount == 0)
{
//
[self performSegueWithIdentifier:#"multipleDetails" sender:self];
}else
{
[self loadImage];
Image *link = [_urlFetchedResultsController objectAtIndexPath:indexPath];
webViewController *detailViewController = [self.storyboard instantiateViewControllerWithIdentifier:#"webView"];
detailViewController.webData = link.link;
[self.navigationController pushViewController:detailViewController animated:YES];
}
and this is the part that fetches the data.
- (void)loadMakeModel {
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
[fetchRequest setEntity:[NSEntityDescription entityForName:#"MakeModel" inManagedObjectContext:localContext]];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"(make == %#)",currentMake];
[fetchRequest setPredicate: predicate];
//NSLog(#"fetch request: %#",fetchRequest);
[fetchRequest setFetchBatchSize:40];
NSSortDescriptor *sortByName = [NSSortDescriptor sortDescriptorWithKey:#"Model" ascending:YES];
[fetchRequest setSortDescriptors:[NSArray arrayWithObject:sortByName]];
_fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:localContext sectionNameKeyPath:nil cacheName:nil];
[_fetchedResultsController performFetch:nil];
}
- (void)loadOptions {
[self loadMakeModel];
NSIndexPath *indexPath = [self.tableView indexPathForSelectedRow];
optionTitle = [_fetchedResultsController objectAtIndexPath:indexPath];
NSString *options = [MakeModel MR_findFirstByAttribute:#"OptionTitle" withValue:optionTitle.makeTitle];
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
[fetchRequest setEntity:[NSEntityDescription entityForName:#"FCOptions" inManagedObjectContext:localContext]];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"(options == %#)",options];
//
[fetchRequest setPredicate: predicate];
NSSortDescriptor *sortByName = [NSSortDescriptor sortDescriptorWithKey:#"title" ascending:YES];
[fetchRequest setSortDescriptors:[NSArray arrayWithObject:sortByName]];
_optionsFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:localContext sectionNameKeyPath:nil cacheName:nil];
[_optionsFetchedResultsController performFetch:nil];
}
- (void)loadImage {
[self loadOptions];
NSIndexPath *indexPath = [self.tableView indexPathForSelectedRow];
ImageUrl = [_optionsFetchedResultsController objectAtIndexPath:indexPath];
//
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
[fetchRequest setEntity:[NSEntityDescription entityForName:#"CarImage" inManagedObjectContext:localContext]];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"(url == %#)",ImageUrl.url];
//
[fetchRequest setPredicate: predicate];
NSSortDescriptor *sortByName = [NSSortDescriptor sortDescriptorWithKey:#"url" ascending:YES];
[fetchRequest setSortDescriptors:[NSArray arrayWithObject:sortByName]];
_urlFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:localContext sectionNameKeyPath:nil cacheName:nil];
[_urlFetchedResultsController performFetch:nil];
}
The problem is the optionTitle is nil for some reason.
Any help or example would be appreciated.

Ok I fiqured it out by using:
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:0 inSection:0];
instead of
NSIndexPath *indexPath = [self.tableView indexPathForSelectedRow];

Related

Fetch Object with relationship with NSFetchedResultsController

Here's my problem:
I've got 2 Entities :
Session (Date date, String name, NSSet behaviours)
Behaviour (String category, String Time, String name, NSManagedobject session).
They are in a one-to-many relationship.
In SessioneViewController, I fetch the Session and the Behaviours:
// This is for the fetch request
#property (nonatomic, ratain) NSArray *sessionArray;
// Here I create the Session Object
Session *newSession = [NSEntityDescription insertNewObjectForEntityForName:#"Session" inManagedObjectContext:self.managedObjectContext];
newSession.name = self.lblName.text;
newSession.date = [NSDate date];
self.managedObjectContext save:nil];
// I add the Session into the Array
self.sessionsArray = [self.sessionsArray arrayByAddingObject:newSession];
// Here I create the Behaviour Object:
Comportamento *newBehaviour = [NSEntityDescription insertNewObjectForEntityForName:#"Behaviour" inManagedObjectContext:self.managedObjectContext];
newBehaviour.name = cell.textLabel.text;
newBehaviour.category = #"Relationship";
newBehaviour.time = _lblTimer.text;
// Here I say which Session is part of the Behaviour
// --- EDIT ---
// Error Code: newBehaviour.session = [self.sessionsArray objectAtIndex:indexPath.row];
newBehaviour.session = [self.sessionsArray lastObject];
Then I have two view controller classes in which I want to display the Sessions and then when a Session is selected I want the related Behaviours.
// In "ArchiveSessionVC.h":
#property (nonatomic, strong) NSFetchedResultsController *frs;
// In "ArchiveSessionVC.m":
- (void) viwDidLoad
{
if(![[self frs] performFetch:&e]) {
NSLog(#"Error %#", e);
abort();
}
-(NSFetchedResultsController *)frs {
if (_frs != nil) {
return _frs;
}
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Session" inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"date"
ascending:NO];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];
[fetchRequest setSortDescriptors:sortDescriptors];
_frs = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:self.managedObjectContext sectionNameKeyPath:nil cacheName:nil];
return _fetchedResultControllerSessions;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"SessioneCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
Sessione *sessione = [self.fetchedResultControllerSessions objectAtIndexPath:indexPath];
cell.textLabel.text = sessione.nome;
return cell;
}
Here is where I push the Second view controller for display the Behaviuors:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
// Push del ViewController dei comportamenti:
ArchiveBehaviuorsVC *aBehavioursVC = [[ArchiveBehaviuorsVC alloc] initWithNibName:#"ArchiveBehaviuorsVC" bundle:nil];
aBehavioursVC.sessioneSelezionata = [self.fetchedResultControllerSessions objectAtIndexPath:indexPath];
[self.navigationController pushViewController:aBehavioursVC animated:YES];
}
My ArchiveBehaviuorsVC is like this:
//"ArchiveBehaviuorsVC.h"
#property (nonatomic, strong) Session *selectedSession;
-(NSFetchedResultsController *)fetchedResultControllerBehaviours
{
if (_fetchedResultControllerBehaviours != nil) {
return _fetchedResultControllerBehaviours;
}
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Behaviour" inManagedObjectContext:self.managedObjectContext];
//NSPredicate *predicate = [NSPredicate predicateWithFormat:#"session == %#",self.sessioneSelezionata];
[fetchRequest setEntity:entity];
[fetchRequest setPredicate:predicate];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"nome"
ascending:NO];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];
[fetchRequest setSortDescriptors:sortDescriptors];
_fetchedResultControllerBehaviours = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:self.managedObjectContext sectionNameKeyPath:nil cacheName:nil];
return _fetchedResultControllerBehaviours;
}
This is part of the code but it doesn't work. How do I tell the fetchedResultzController to display only the Behaviours associate with the Session?
-- EDIT ---
I found the problem, it wasn't a NSPredicate error, the problem was in this line of code:
// Error Code: newBehaviour.session = [self.sessionsArray objectAtIndex:indexPath.row];
// Right Code:
newBehaviour.session = [self.sessionsArray lastObject];
The predicate
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"session == %#",self.sessioneSelezionata];
looks ok to me.
Are you sure you are obtaining the wrong behavior? Did you try to enable SQL debugging?
-com.apple.CoreData.SQLDebug X
where X can be 1,2 or 3. The higher the value the more SQL output.
In addition, when you do a save ALWAYS pass in an error reference.
NSError* error = nil;
[context save:&error];
The above snippet returns a bool value so check it. If NO is returned print the error.

CoreData ascending:YES, show latest from the bottom

I have list of messages in my Core Data store(100 messages).
NSSortDescriptor * sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"created" ascending:YES];
NSArray *sortDescriptors = #[sortDescriptor];
[fetchRequest setSortDescriptors:sortDescriptors];
[fetchRequest setFetchLimit:20];
It shows only first 20 messages in the order I need.
What I need to do is: Show last 20 messages, sorted by date of creation, but they should appeared form the bottom of the TableView.
Storage:
1 : Test 1
2 : Test 2
3 : Test 3
It shows now as :
1 : Test 1
2 : Test 2
**Supposed to show in table View:**
3 : Test 3
2 : Test 2
1 : Test 1
fetchedResultsController:
- (NSFetchedResultsController *)fetchedResultsController{
if (_fetchedResultsController != nil) {
return _fetchedResultsController;
}
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Messages" inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];
[fetchRequest setFetchLimit:20];
NSSortDescriptor * sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"created" ascending:YES];
NSArray *sortDescriptors = #[sortDescriptor];
[fetchRequest setSortDescriptors:sortDescriptors];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"(owner = %#) and (stream == %#) and (topic == %#)", ownerGuid,openStream,currentTopic];
[fetchRequest setPredicate:predicate];
NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:self.managedObjectContext sectionNameKeyPath:nil cacheName:nil];
aFetchedResultsController.delegate = self;
self.fetchedResultsController = aFetchedResultsController;
self.fetchedResultsController.delegate = self;
NSError *error = nil;
if (![self.fetchedResultsController performFetch:&error]) {
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
[self.fetchedResultsController performFetch:NULL];
return _fetchedResultsController;
}
All you should need to do is change the sortDescriptor to use ascending:NO so that it will get the last 20 messages.
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"created" ascending:NO];
Now, you've got the right messages, they're just backwards in your array. You can easily reverse an array using the following code.
NSArray *messages = [[[fetchedMessages] reverseObjectEnumerator] allObjects];
EDIT:
Since you using a NSFetchedResultsController this solution would require a lot of additional code to work.
The easiest solution I can think of would be implement a custom method for getting your objects out of a fetched results controller.
- (id)objectAtIndexPath:(NSIndexPath *)indexPath
{
NSInteger sectionCount = [self.tableView numberOfRowsInSection:indexPath.section];
NSInteger newRowIndex = sectionCount - indexPath.row - 1;
NSIndexPath *newIndexPath = [NSIndexPath indexPathForRow:newRowIndex inSection:indexPath.section];
return [self.fetchedResultsController objectAtIndexPath:newIndexPath];
}
Then just replace
[self.fetchedResultsController objectAtIndexPath:indexPath];
with
[self objectAtIndexPath:indexPath];
#Clever answer is working for me. I called only NSIndexPath (return newIndexPath). I did not override method of the objectAtIndexPath. I was replaced below method.
- (NSIndexPath *)objectNewIndexPath:(NSIndexPath *)indexPath {
NSInteger sectionCount = [self.tableView numberOfRowsInSection:indexPath.section];
NSInteger newRowIndex = sectionCount - indexPath.row - 1;
NSIndexPath *newIndexPath = [NSIndexPath indexPathForRow:newRowIndex inSection:indexPath.section];
return newIndexPath;
}
Then just replace
[self.fetchedResultsController objectAtIndexPath:[self objectNewIndexPath:indexPath];

Is this the correct use of NSFetchedResultsController?

Is this is correct us of NSFetchedResultsController's getter method with the most modern Object-C and LLVM compiler that doesn't require synthesizing properties?
- (NSFetchedResultsController *)fetchedResultsController {
if (_fetchedResultsController) {
return _fetchedResultsController;
}
if (!self.managedObjectContext) {
self.managedObjectContext = [(AppDelegate *)[[UIApplication sharedApplication] delegate] managedObjectContext];
}
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Session" inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];
[fetchRequest setPredicate: self.predicate];
[fetchRequest setFetchBatchSize:20];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"timeStamp" ascending:NO];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];
[fetchRequest setSortDescriptors:sortDescriptors];
NSFetchedResultsController *aFetchedResultsController= [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:self.managedObjectContext sectionNameKeyPath:nil cacheName:nil];
aFetchedResultsController.delegate = self;
NSError *error = nil;
if (![aFetchedResultsController performFetch:&error]) {
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
return aFetchedResultsController;
}
In other parts of my code, I have this instead:
- (NSFetchedResultsController *)fetchedResultsController {
if (_fetchedResultsController) {
return _fetchedResultsController;
}
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Set" inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];
[fetchRequest setPredicate:[NSPredicate predicateWithFormat: #"sets == %#", self.exercise]];
[fetchRequest setFetchBatchSize:20];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"timeStamp" ascending:YES];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];
[fetchRequest setSortDescriptors:sortDescriptors];
NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc]initWithFetchRequest:fetchRequest managedObjectContext:self.managedObjectContext sectionNameKeyPath:nil cacheName:nil];
aFetchedResultsController.delegate = self;
self.fetchedResultsController = aFetchedResultsController;
NSError *error = nil;
if (![self.fetchedResultsController performFetch:&error]) {
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
return self.fetchedResultsController;
}
In your first example, you never assign anything to the property or ivar, so the lazy loading will occur every time the property is accessed. This isn't right.
In your second example you assign to the property, (thereby triggering any side effects of the property setter) and also return the value of the property (which wouldn't take many errors to leave you with an infinite loop). It may work now but it would be easy to break with a careless update.
It would be cleaner to assign to the internal ivar and return that.
So, at the end of your first example:
_fetchedResultsController = aFetchedResultsController;
return _fetchedResultsController;

NSFetchedResultsController for new object

I have UITableViewController with static editable cells. In my UITableViewController I make CoreData object, like this:
- (User*)user {
if (_user == nil) {
_user = [NSEntityDescription insertNewObjectForEntityForName:TABLE_USER inManagedObjectContext:self.managedObjectContext];
NSError* error;
[self.managedObjectContext save:&error];
if (error != nil) {
abort();
}
}
return _user;
}
I want to get the object in NSFetchedResultsController that would display its fields in a table, and then save the object.
I'm trying to do:
- (NSFetchedResultsController *)fetchedResultsController
{
if (_fetchedResultsController != nil) {
return _fetchedResultsController;
}
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
// Edit the entity name as appropriate.
NSEntityDescription *entity = [NSEntityDescription entityForName:TABLE_USER_PROFILE inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];
// Set the batch size to a suitable number.
[fetchRequest setFetchBatchSize:20];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"lastName" ascending:NO];
NSArray *sortDescriptors = #[sortDescriptor];
NSPredicate* pred = [NSPredicate predicateWithFormat:#"%# = %#", TABLE_USER, self.user];
[fetchRequest setPredicate:pred];
[fetchRequest setSortDescriptors:sortDescriptors];
NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:self.managedObjectContext sectionNameKeyPath:nil cacheName:TABLE_USER];
aFetchedResultsController.delegate = self;
self.fetchedResultsController = aFetchedResultsController;
NSError *error = nil;
if (![self.fetchedResultsController performFetch:&error]) {
abort();
}
return _fetchedResultsController;
}
But it didn't work. I get empty fetchedResultsController.
If self.user is a NSManagedObject and you query against a User entity you could try to edit your predicate like the following:
NSPredicate* pred = [NSPredicate predicateWithFormat:#"self == %#", self.user];
Hope that helps.
P.S. If my answer doesn't work try to provide some other details (see my comment)

Detect updates on sectionNameKeyPath from NSFetchedResultsController

Here is the case. I have 2 models:
Sport (with attribute name and relationShip players)
Player (with attribute name and relationShip sport)
In a ViewController, I wan't to display all players in a UITableView ordered by sport.
When a player name changes, NSFetchedResultsControllerDelegate callbacks are called.
But when the name of a Sport is updated, no NSFetchedResultsControllerDelegate callbacks are called.
Is this a normal behaviour ? How can I know the updates of Sport.name (without creating another NSFetchedResultsController) ?
Thank you and Best Regards.
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"sport.name != NULL"];
[fetchRequest setPredicate:predicate];
NSArray *sortDescriptors = [NSArray arrayWithObjects:
[NSSortDescriptor sortDescriptorWithKey:#"sport.name" ascending:YES],
[NSSortDescriptor sortDescriptorWithKey:#"name" ascending:YES],
nil];
[fetchRequest setSortDescriptors:sortDescriptors];
NSEntityDescription *entityDescription = [NSEntityDescription entityForName:#"Player"
inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entityDescription];
fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
managedObjectContext:self.managedObjectContext
sectionNameKeyPath:#"sport.name"
cacheName:nil];
[fetchRequest release];
fetchedResultsController.delegate = self;
NSError *error = nil;
if ([fetchedResultsController performFetch:&error] == NO) {
ALog(#"Fetch error: %#", [error localizedDescription]);
}
You can using a tip:
If (fetchresultcontroller != nil || isOtherEnthityUpdate)
{
return fetchresultcontroller;
}
And do self.tableview reload from any place where u check other entity. It can be notification center or other tableview.