0
votes

I'm facing an uncaught exception with the wrong selector sent during runtime, of a core-data based app when passing objects between table view controllers.

The problem I'm facing (which will become clearer as you read through this question) is I'm not sure how to reference a Core Data Objects' relationship rather than calling the Object itself.

Let's take a step back to look at the Model and the premise of the app is simple; allow users to create "Transactions" which has a name, amount, occasion and date.

Transaction Entity with wasGiven attribute Person Entity with name attribute Occasion Entity with title attribute Item Entity with amount attribute The transaction has a relationship to the Person entity (whoBy), a link to the Occasion Entity (occasion) and a link to the Item Entity (itemType).

EDIT: Core Data Model

enter image description here

The App is a simple 2 Tabb-ed Table View Controller - the first tab contains all of the information, sectioned off by dates. For this TableViewController, I am looking into the "Transaction Entity" with the following fetchedResultsController code:

- (NSFetchedResultsController *)fetchedResultsController
{
    NSManagedObjectContext *managedObjectContext = [self managedObjectContext];

    if (_fetchedResultsController != nil)
    {
        return _fetchedResultsController;
    }

    NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];    
    NSEntityDescription *entity = [NSEntityDescription entityForName:@"Transaction" inManagedObjectContext:managedObjectContext];
    fetchRequest.entity = entity;
    NSSortDescriptor *sort = [[NSSortDescriptor alloc] initWithKey:@"occasion.dateOfEvent" ascending:NO];
    fetchRequest.sortDescriptors = [NSArray arrayWithObject:sort];
    fetchRequest.fetchBatchSize = 20;
    NSFetchedResultsController *theFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:managedObjectContext sectionNameKeyPath:@"occasion.dateOfEvent" cacheName:nil];
    self.fetchedResultsController = theFetchedResultsController;
    _fetchedResultsController.delegate = self;
    return _fetchedResultsController;
}

The second tab contains just the "Name" information. There is a plus button and when the user presses that, they're presented with a Modal View to add in the Name, Occasion, Amount and Date. They press save and it saves it to the Core Data Database. So the second tab displays only the names (and I had to do a lot of work to get this bit working so it's not duplicated) but the fetchedResultsController for the second Table View is:

- (NSFetchedResultsController *)fetchedResultsController
{
    NSManagedObjectContext *managedObjectContext = [self managedObjectContext];

    if (_fetchedResultsController != nil)
    {
        return _fetchedResultsController;
    }
    NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
    NSEntityDescription *entity = [NSEntityDescription entityForName:@"Person" inManagedObjectContext:managedObjectContext];
    fetchRequest.entity = entity;
    NSSortDescriptor *sort = [[NSSortDescriptor alloc] initWithKey:@"name" ascending:NO];
    fetchRequest.sortDescriptors = [NSArray arrayWithObject:sort];
    NSFetchedResultsController *theFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:managedObjectContext sectionNameKeyPath:nil cacheName:nil];
    self.fetchedResultsController = theFetchedResultsController;        _fetchedResultsController.delegate = self;
    return _fetchedResultsController;
}

When I select a row, the prepareForSegue method gets called:

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{

    NSIndexPath *indexPath = [self.personTableView indexPathForCell:sender];
    Transaction *selectedTransaction = [self.fetchedResultsController objectAtIndexPath:indexPath];
    if ([segue.identifier isEqualToString:@"Selected Person Segue"])
    {
        EnvylopeSelectedPersonTableViewController *selectedPerson = [segue destinationViewController];

        [selectedPerson setTransaction:selectedTransaction];
    }
}

So in the SelectedTableView, I obviously have a property to hold that:

@property (nonatomic, strong) Transaction *transaction;

With the setter which sets it's title to be the selected name.

What I want in this new Table view is to show a history of just that person - so similar to the original first tab, but just tailored to this person, which is why I thought I'd use predicates.

The selected table view fetchedResultsController is:

- (NSFetchedResultsController *)fetchedResultsController
{
    NSManagedObjectContext *managedObjectContext = [self managedObjectContext];

    if (_fetchedResultsController != nil)
    {
        return _fetchedResultsController;
    }

    NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];

    NSEntityDescription *entity = [NSEntityDescription entityForName:@"Transaction" inManagedObjectContext:managedObjectContext];
    fetchRequest.entity = entity;


    NSSortDescriptor *sort = [[NSSortDescriptor alloc] initWithKey:@"occasion.dateOfEvent" ascending:NO];

    fetchRequest.sortDescriptors = [NSArray arrayWithObject:sort];
    fetchRequest.fetchBatchSize = 20;

    fetchRequest.predicate = [NSPredicate predicateWithFormat:@"whoBy.name = %@", self.transaction.whoBy.name];

    NSFetchedResultsController *theFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:managedObjectContext sectionNameKeyPath:@"occasion.dateOfEvent" cacheName:nil];
    self.fetchedResultsController = theFetchedResultsController;
    _fetchedResultsController.delegate = self;
    return _fetchedResultsController;
}

This is creating an exception:

Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[Person whoBy]: unrecognized selector sent to instance 0x8b550b0'

I understand why this is happening; I am passing a Transaction and displaying a Transaction, but I cannot use a Transaction in the Person Table View because it creates duplicates for the person (that was the big problem I faced here), so how I can somehow caste this? The reason I need to use a Transaction fetchRequest in the Selected Table View is because I need to reference the date, the title and the amount, all of which are related to the Transaction and not part of the Person.

Any help on this would be massively appreciated!

1
Do you pass a Transaction or a Person to the new view controller? Your code and your explaining text do not match. - Martin R
Sorry but this did not become clearer as I read on. Do you have relationships setup for your core data? - Bot
A screenshot of the Core Data Model would be useful as well. - Martin R
@MartinR oops sorry - I will put a picture, but I am passing a Person object to the new controller, but in the new controller, I need to use a Transaction Entity in the fetchResultsController to reference other relationships - pic to follow - amitsbajaj
@amitsbajaj: But in prepareForSegue, you pass a Transaction to the new view controller, not a Person. - Martin R

1 Answers

1
votes

In prepareForSegue:, the fetched results controller fetches Person objects, not Transaction objects. Therefore

Transaction *selectedTransaction = [self.fetchedResultsController objectAtIndexPath:indexPath];

is actually a Person (casting it to Transaction does not change this fact). So you should change this to

Person *selectedPerson = [self.fetchedResultsController objectAtIndexPath:indexPath];
EnvylopeSelectedPersonTableViewController *selectedPersonVC = [segue destinationViewController];
selectedPersonVC.person = selectedPerson;

where person is a property of EnvylopeSelectedPersonTableViewController:

@property (nonatomic, strong) Person *person;

The predicate to display all transaction for this person would then simply be

fetchRequest.predicate = [NSPredicate predicateWithFormat:@"whoBy = %@", self.person];