Advertise here




Advertise here

Howdy, Stranger!

It looks like you're new here. If you want to get involved, click one of these buttons!

Using Fetched Results Controller in Coredata One - Many Relationship

ichanduuichanduu HYDERABADPosts: 8New Users Noob
edited April 2014 in iOS SDK Development
am using One - Many relationship in core data.

Entity - Customer Transaction

Attributes - customerName ->> monthNumber,
amountDeposited
I able to save and retrieve the data but am not able to categorize them into sections using NSFetchedResultsController and show them in a table View.

I have two tables and in one table I want to show customerName in the section and in another one I want to show the monthNumber.

Here is the code I tried so far.
- (NSFetchedResultsController *)fetchedResultsController {
NSManagedObjectContext *context = [[self appDelegate] managedObjectContext];
if (_fetchedResultsController != nil) {
    return _fetchedResultsController;
}

NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:@"Customer" inManagedObjectContext:context];
[fetchRequest setEntity:entity];
NSSortDescriptor *sort = [[NSSortDescriptor alloc]initWithKey:@"customerName" ascending:YES];
[fetchRequest setSortDescriptors:[NSArray arrayWithObject:sort]];
[fetchRequest setFetchBatchSize:20];
NSFetchedResultsController *theFetchedResultsController =
[[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
                                    managedObjectContext:context sectionNameKeyPath:nil
                                               cacheName:@"Root"];
self.fetchedResultsController = theFetchedResultsController;
_fetchedResultsController.delegate = self;
return _fetchedResultsController;

}


- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
NSLog(@"number of sections %lu",[_fetchedResultsController sections].count); //I am getting 1 where I should get 6.(6 customerNames Saved)
return [_fetchedResultsController sections].count;
 }


- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
id <NSFetchedResultsSectionInfo> sectionInfo = [[[self fetchedResultsController] sections] objectAtIndex:section];
NSLog(@"number of rows %lu",[sectionInfo numberOfObjects]); //I am getting 6 here where I should get 3 (3 transactions added).
return [sectionInfo numberOfObjects];
}


- (void)configureCell:(UITableViewCell *)cell atIndexPath:(NSIndexPath *)indexPath {
NSLog(@"index is %ld",(long)indexPath.row);
XXPerson *person = [[self.fetchedResultsController fetchedObjects]objectAtIndex:indexPath.row];
NSLog(@"%@",person.customerName);
NSArray *array = [[person.transaction allObjects] valueForKey:@"amountDeposited"];
NSLog(@"%@",array);
cell.textLabel.text = [array objectAtIndex:indexPath.row]; //giving an error.
}


- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = @"customerCell";
UITableViewCell *cell =[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
// Set up the cell...
[self configureCell:cell atIndexPath:indexPath];
return cell;
}
Post edited by Stick on

Replies

  • BrianSlickBrianSlick Treadmill Desk Ninja Posts: 10,689Tutorial Authors, Registered Users @ @ @ @ @ @ @ @
    edited April 2014
    1. Use code tags

    2. Show an example of the output that you want. Then show an example of what you are currently seeing.

    3. This is wrong:
    - (NSFetchedResultsController *)fetchedResultsController {
    NSManagedObjectContext *context = [[self appDelegate] managedObjectContext];
    if (_fetchedResultsController != nil) {
    return _fetchedResultsController;
    }
    

    Notice that you never do anything with "context". You probably have an "unused variable" warning right there, too. And if _fetchedResultsController is nil, you don't return anything from this method.
    Post edited by BrianSlick on
    Professional iOS App Development. Available for hire.
    BriTer Ideas LLC - WWW | Facebook | Twitter | LinkedIn

    BTIKit | BTICoreDataKit | SlickShopper 2 | Leave a PayPal donation
  • ichanduuichanduu HYDERABADPosts: 8New Users Noob
    - (NSFetchedResultsController *)fetchedResultsController {
    NSManagedObjectContext *context = [[self appDelegate] managedObjectContext];
          if (_fetchedResultsController != nil) {
              return _fetchedResultsController;
             }
        NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
        NSEntityDescription *entity = [NSEntityDescription entityForName:@"Customer" inManagedObjectContext:context];
        [fetchRequest setEntity:entity];
        NSSortDescriptor *sort = [[NSSortDescriptor alloc]initWithKey:@"customerName" ascending:YES];
        [fetchRequest setSortDescriptors:[NSArray arrayWithObject:sort]];
        NSFetchedResultsController *theFetchedResultsController =
        [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
                                        managedObjectContext:context sectionNameKeyPath:@"customerName"
                                                   cacheName:@"Root"];
        self.fetchedResultsController = theFetchedResultsController;
        _fetchedResultsController.delegate = self;
        return _fetchedResultsController;
    }
    
    
    - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
        NSLog(@"number of sections %lu",[_fetchedResultsController sections].count);
        return [_fetchedResultsController sections].count;
     }
    
    
      - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
          id  sectionInfo = [[_fetchedResultsController sections] objectAtIndex:section];
          NSLog(@"number of rows %lu",(unsigned long)[sectionInfo numberOfObjects]); //here i need to get 3 as i added 3 transactions but i am getting only one.
          return [sectionInfo numberOfObjects];
     }
    
    
    - (void)configureCell:(UITableViewCell *)cell atIndexPath:(NSIndexPath *)indexPath {
        XXPerson *person = [self.fetchedResultsController objectAtIndexPath:indexPath];
        NSArray *array = [[person.transaction allObjects] valueForKey:@"amountDeposited"];
        NSLog(@"%ld",(long)indexPath.row);
        NSLog(@"%@",array);
       cell.textLabel.text =  [NSString stringWithFormat:@"%@", [array objectAtIndex:indexPath.row]];
    }
    

    I want it like this.


    CUS1 - section header

    20 - row 1
    40 - row 2
    60 - row 3

    and so on...
  • BrianSlickBrianSlick Treadmill Desk Ninja Posts: 10,689Tutorial Authors, Registered Users @ @ @ @ @ @ @ @
    Ok, the lack of code formatting confused me, so you can disregard what I said earlier about the code being wrong.

    You are not doing an adequate job of explaining the problem. What is 20? What is 40? Where does it come from? And I asked you for an example of what you are actually seeing right now, but you did not provide that.
    Professional iOS App Development. Available for hire.
    BriTer Ideas LLC - WWW | Facebook | Twitter | LinkedIn

    BTIKit | BTICoreDataKit | SlickShopper 2 | Leave a PayPal donation
  • ichanduuichanduu HYDERABADPosts: 8New Users Noob
    Sorry about not being clear.I added transactions to the customer which is a to-many relationship.For instance i have added 6 customers and 3 transactions for each customer. I want the customer name in the header and the transactions in the rows.

    With the code I provided.I am getting only one row and only one transaction is being shown.


    CUS1 - section header
    20 - row


    This is the output for the fetch request i used
    2014-04-24 19:56:27.461 CoreDataExample[7257:60b] Name CUS1
    2014-04-24 19:56:27.464 CoreDataExample[7257:60b] {(
        110,
        10,
        20
    )}
    2014-04-24 19:56:27.465 CoreDataExample[7257:60b] Name CUS2
    2014-04-24 19:56:27.466 CoreDataExample[7257:60b] {(
        30,
        40,
        310
    )}
    2014-04-24 19:56:27.466 CoreDataExample[7257:60b] Name CUS3
    2014-04-24 19:56:27.466 CoreDataExample[7257:60b] {(
        60,
        510,
        50
    )}
    2014-04-24 19:56:27.467 CoreDataExample[7257:60b] Name CUS4
    2014-04-24 19:56:27.467 CoreDataExample[7257:60b] {(
        710,
        70,
        80
    )}
    2014-04-24 19:56:27.468 CoreDataExample[7257:60b] Name CUS5
    2014-04-24 19:56:27.468 CoreDataExample[7257:60b] {(
        90,
        100,
        910
    )}
    2014-04-24 19:56:27.469 CoreDataExample[7257:60b] Name CUS6
    2014-04-24 19:56:27.469 CoreDataExample[7257:60b] {(
        1110,
        120,
        110
    )}
    
  • BrianSlickBrianSlick Treadmill Desk Ninja Posts: 10,689Tutorial Authors, Registered Users @ @ @ @ @ @ @ @
    This is not related to your problem, but you need to improve your coding consistency.
    return [_fetchedResultsController sections].count;
    ...
    id sectionInfo = [[_fetchedResultsController sections] objectAtIndex:section];
    ...
    XXPerson *person = [self.fetchedResultsController objectAtIndexPath:indexPath];
    

    Notice that line 1 and 3 access the FRC the same way. Then you do it differently for 5. These should all be the same.

    In this particular case, it matters which way you do it, and #5 is the only correct one. #5 is the only one that calls your method here:
    - (NSFetchedResultsController *)fetchedResultsController { ... }
    

    The first two do not call this method. Since this method must be called in order to create the FRC in the first place, failing to call it means the FRC does not get created. You must be calling it from somewhere else, because in the code you have shown here, I'm surprised you are getting any results at all.

    This is the difference between instance variables and properties. And it is a difference that you absolutely must understand. This is not optional.

    Lastly, your use of bracket syntax vs dot syntax:
    return [_fetchedResultsController sections].count;
    

    This is horrible style. For one thing, be consistent. Dot syntax is only supposed to be used for accessing properties. In this case, you have it exactly backwards. sections is a property, but count is not. So if you must use dot syntax, the correct way to do so would be:
    return [_fetchedResultsController.sections count];
    

    Even better, use the property as I just said:
    return [self.fetchedResultsController.sections count];
    
    Professional iOS App Development. Available for hire.
    BriTer Ideas LLC - WWW | Facebook | Twitter | LinkedIn

    BTIKit | BTICoreDataKit | SlickShopper 2 | Leave a PayPal donation
  • BrianSlickBrianSlick Treadmill Desk Ninja Posts: 10,689Tutorial Authors, Registered Users @ @ @ @ @ @ @ @
    I don't believe you can achieve your goal using only an FRC. An FRC depends on a fetch request, and a fetch request can only fetch a single kind of entity. In this case, the entity that you want is the transaction. You would then be relying on the sort descriptor to adequately sort and section these transactions, but I don't think you can do that with a relationship.

    I think the easiest thing to do will be to continue to use the FRC with the customer as you are now, but then you will have to manually sort the transactions. You will also have to map between index paths in the table and index paths in the FRC, because they will not match any more.

    Change the FRC so that it is not sectioning the results, simply sorting the customers as you want. This will result in a list of customers in the order that you want, but a single list, not grouped. Then you'll need to adjust how you populate the cell accordingly.

    For example:
    - (void)configureCell:(UITableViewCell *)cell
     atIndexPath:(NSIndexPath *)indexPath
    {
       // The section in the table view will need to be converted to a row in the FRC
       NSIndexPath *adjustedIndexPath = [NSIndexPath indexPathForRow:[indexPath section] inSection:0];
    
       // Now retrieve a person using the adjusted index path
       XXPerson *person = [[self fetchedResultsController] objectAtIndexPath:adjustedIndexPath];
    
       // Retrieve and sort the transactions
       NSSet *unsortedTransactions = [person transaction];
       // By the way, to-many relationships should be named with a plural, to indicate that there will be many objects.
       // to-one should be singular.
       // So this property name should instead be "transactions".
       
       NSArray *sortedTransactions = [unsortedTransactions sortedArrayUsingDescriptors: ... ];
       // I recommend making a property for the descriptor array, since you will be using it a lot
    
       // Now we need to select the appropriate transaction for this row
       XXTransaction *transaction = [sortedTransactions objectAtIndex:[indexPath row]];
    
       // Populate the cell with the transaction
       cell.textLabel.text =  [NSString stringWithFormat:@"%@", transaction];
    }
    

    Number of sections will be the number of customers, and number of rows in each section will be the count of transactions for each customer. You will need to adjust the index path as I have shown here in order to provide that value.

    So you're still mostly using the FRC, but you're having to do some manual additional work. I believe I have tried to solve a similar problem in the past, but was not able to do so using the FRC alone.
    Professional iOS App Development. Available for hire.
    BriTer Ideas LLC - WWW | Facebook | Twitter | LinkedIn

    BTIKit | BTICoreDataKit | SlickShopper 2 | Leave a PayPal donation
  • C6Silver05C6Silver05 SeattlePosts: 632New Users @ @ @
    Trying to best understand the exact issue as well. However, what I am getting is that you have two tables with a "to many" relationship. It appears that your first table may be the customer and the second transactions. Of course one customer can have multiple transactions. You want a table that uses the customer's name from the customer table as the section header and then you want to use the appropriate transactions from the relationship to populate the rows. Is this correct?

    Assuming the above you are indicating that you are only getting a single row returned (transaction) where you expected 3 to be returned (assume 3 transactions). It appears to me that you are not looking into the relationship to ascertain this though. You are looking at the section header of customer which would show a single row as there is only 1 customer record. You need to look into the NSSet of the customer record that actually contains the relationship (i.e. transactions) you are interested in. The set will contain the correct number of objects related to the customer's transactions (in this case 3).
  • C6Silver05C6Silver05 SeattlePosts: 632New Users @ @ @
    edited April 2014
    To take my points above a little deeper. I don't know the name of the attribute in your customer entity that contains the relationship to transactions. So for the purpose of this example I will call it "myTransactions". So your customer entity has an attribute called myTransactions that is itself an NSSet containing the relationship. I will also use Customer as the class name for the customer data.
    Customer *customer = [self.fetchedController objectAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:section]];
            return [customer.myTransactions count];
    

    Your one customer will return one row and so you can directly at that record in row 0 of the appropriate section. Now you use count to determine the number of rows for the section. The code above is contained in the numerberOfRowsInSection method.
  • ichanduuichanduu HYDERABADPosts: 8New Users Noob

    Thank you very much for helping me.

    Are there any extra modifications I need to make?.I used the code and I am getting an error.

    Here is the code I modified.
    - (void)configureCell:(UITableViewCell *)cell atIndexPath:(NSIndexPath *)indexPath {
        NSIndexPath *adjustedIndexPath = [NSIndexPath indexPathForRow:[indexPath section] inSection:0];
        NSLog(@"Section: %ld, Row: %ld", (long)adjustedIndexPath.section, (long)adjustedIndexPath.row);
        XXPerson *person = [[self fetchedResultsController] objectAtIndexPath:adjustedIndexPath];
        NSSet *unsortedTransactions = [person transaction];
        NSSortDescriptor *sort = [[NSSortDescriptor alloc]initWithKey:@"monthNumber" ascending:YES];
        self.transactions = [unsortedTransactions sortedArrayUsingDescriptors: [NSArray arrayWithObjects: sort, nil]];
        XXTransaction *transaction = [self.transactions objectAtIndex:[indexPath row]];
        NSString *amountDepositedStr = [transaction valueForKey:@"amountDeposited"];
        cell.textLabel.text =  [NSString stringWithFormat:@"%@", amountDepositedStr];
        NSLog(@"%@",[NSString stringWithFormat:@"%@", amountDepositedStr]);
    }
    


    and this is the error I am getting.
    2014-04-25 12:02:32.469 CoreDataExample[1958:60b] number of sections 6
    2014-04-25 12:02:32.478 CoreDataExample[1958:60b] number of sections 6
    2014-04-25 12:02:32.479 CoreDataExample[1958:60b] number of rows 3
    2014-04-25 12:02:32.480 CoreDataExample[1958:60b] number of rows 3
    2014-04-25 12:02:32.480 CoreDataExample[1958:60b] number of rows 3
    2014-04-25 12:02:32.481 CoreDataExample[1958:60b] number of rows 3
    2014-04-25 12:02:32.481 CoreDataExample[1958:60b] number of rows 3
    2014-04-25 12:02:32.482 CoreDataExample[1958:60b] number of rows 3
    2014-04-25 12:02:32.483 CoreDataExample[1958:60b] Section: 0, Row: 0
    2014-04-25 12:02:32.484 CoreDataExample[1958:60b] 20
    2014-04-25 12:02:32.486 CoreDataExample[1958:60b] Section: 0, Row: 0
    2014-04-25 12:02:32.486 CoreDataExample[1958:60b] 10
    2014-04-25 12:02:32.487 CoreDataExample[1958:60b] Section: 0, Row: 0
    2014-04-25 12:02:32.487 CoreDataExample[1958:60b] 110
    2014-04-25 12:02:32.488 CoreDataExample[1958:60b] Section: 0, Row: 1
    2014-04-25 12:02:32.489 CoreDataExample[1958:60b] 40
    2014-04-25 12:02:32.490 CoreDataExample[1958:60b] Section: 0, Row: 1
    2014-04-25 12:02:32.490 CoreDataExample[1958:60b] 30
    2014-04-25 12:02:32.491 CoreDataExample[1958:60b] Section: 0, Row: 1
    2014-04-25 12:02:32.491 CoreDataExample[1958:60b] 310
    2014-04-25 12:02:32.492 CoreDataExample[1958:60b] Section: 0, Row: 2
    2014-04-25 12:02:32.655 CoreDataExample[1958:60b] *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'no object at index 2 in section at index 0'
    *** First throw call stack:
    (
    	0   CoreFoundation                      0x0000000101cbd495 __exceptionPreprocess + 165
    	1   libobjc.A.dylib                     0x0000000101a1c99e objc_exception_throw + 43
    	2   CoreData                            0x000000010035dc08 -[NSFetchedResultsController objectAtIndexPath:] + 408
    	3   CoreDataExample                     0x0000000100003617 -[XXCustomerTableViewController configureCell:atIndexPath:] + 279
    	4   CoreDataExample                     0x0000000100003a5e -[XXCustomerTableViewController tableView:cellForRowAtIndexPath:] + 142
    	5   UIKit                               0x000000010069df8a -[UITableView _createPreparedCellForGlobalRow:withIndexPath:] + 348
    	6   UIKit                               0x0000000100683d5b -[UITableView _updateVisibleCellsNow:] + 2337
    	7   UIKit                               0x0000000100695721 -[UITableView layoutSubviews] + 207
    	8   UIKit                               0x0000000100629993 -[UIView(CALayerDelegate) layoutSublayersOfLayer:] + 354
    	9   QuartzCore                          0x00000001049df802 -[CALayer layoutSublayers] + 151
    	10  QuartzCore                          0x00000001049d4369 _ZN2CA5Layer16layout_if_neededEPNS_11TransactionE + 363
    	11  QuartzCore                          0x00000001049d41ea _ZN2CA5Layer28layout_and_display_if_neededEPNS_11TransactionE + 24
    	12  QuartzCore                          0x0000000104947fb8 _ZN2CA7Context18commit_transactionEPNS_11TransactionE + 252
    	13  QuartzCore                          0x0000000104949030 _ZN2CA11Transaction6commitEv + 394
    	14  UIKit                               0x00000001005c8024 _UIApplicationHandleEventQueue + 10914
    	15  CoreFoundation                      0x0000000101c4cd21 __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 17
    	16  CoreFoundation                      0x0000000101c4c5f2 __CFRunLoopDoSources0 + 242
    	17  CoreFoundation                      0x0000000101c6846f __CFRunLoopRun + 767
    	18  CoreFoundation                      0x0000000101c67d83 CFRunLoopRunSpecific + 467
    	19  GraphicsServices                    0x0000000103ceef04 GSEventRunModal + 161
    	20  UIKit                               0x00000001005c9e33 UIApplicationMain + 1010
    	21  CoreDataExample                     0x0000000100007553 main + 115
    	22  libdyld.dylib                       0x00000001023555fd start + 1
    )
    libc++abi.dylib: terminating with uncaught exception of type NSException
    
  • ichanduuichanduu HYDERABADPosts: 8New Users Noob
    ya I figured it out. thank you.
  • BrianSlickBrianSlick Treadmill Desk Ninja Posts: 10,689Tutorial Authors, Registered Users @ @ @ @ @ @ @ @
    self.transactions = [unsortedTransactions sortedArrayUsingDescriptors: [NSArray arrayWithObjects: sort, nil]];
    

    You can't store the results in a property. Well, I suppose you could, but it doesn't make sense. These will be different for each person, meaning you will have more than 1 set of transactions, so it doesn't make sense to only hold 1 set of transactions.
    NSSortDescriptor *sort = [[NSSortDescriptor alloc]initWithKey:@"monthNumber" ascending:YES];
    self.transactions = [unsortedTransactions sortedArrayUsingDescriptors: [NSArray arrayWithObjects: sort, nil]];
    

    As I said, make a property for this. Don't ignore advice. If you don't understand the advice then ask for clarification, but don't ignore it. I didn't type that out just because I felt like typing more. You are going to bounce off of this code for every single row in your table. This code needs to be fast. That means minimizing the amount of work done, and the number of objects created. So make a property for it:
    @property (nonatomic, strong) NSArray *transactionDescriptors;
    ...
    - (NSArray *)transactionDescriptors
    {
       if (_transactionDescriptors == nil)
       {
          NSSortDescriptor *monthSort = [[NSSortDescriptor alloc] initWithKey:@"monthNumber" ascending:YES];
          _transactionDescriptors = @[ monthSort ];
       }
       return _transactionDescriptors;
    }
    
    ...
    
    // NSSortDescriptor *sort = [[NSSortDescriptor alloc]initWithKey:@"monthNumber" ascending:YES];
    NSArray *sortedTransactions = [unsortedTransactions sortedArrayUsingDescriptors:[self transactionDescriptors]];
    
    Professional iOS App Development. Available for hire.
    BriTer Ideas LLC - WWW | Facebook | Twitter | LinkedIn

    BTIKit | BTICoreDataKit | SlickShopper 2 | Leave a PayPal donation
  • ichanduuichanduu HYDERABADPosts: 8New Users Noob
    @BrianSlick .Thanks for the suggestion.I changed my code.
  • ichanduuichanduu HYDERABADPosts: 8New Users Noob
    @BrianSlick‌ Can you help in getting the adjusted indexPath in prepareForSegue.I want to have a feature where the transaction can be edited.

    If the user taps a cell.the transaction value in that cell will be appeared in another another view controller where we can edit and save it.

    I have used this code.
    -(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
    {
        if([segue.identifier  isEqualToString: @"scoreEdit"])
        {
            UINavigationController *navController = (UINavigationController *)segue.destinationViewController;
            XXScoreEditViewController *scoreEditViewController = (XXScoreEditViewController *)navController.topViewController;
            NSIndexPath *selectedIndexPath = [self.tableView indexPathForCell:sender];
            NSLog(@"Section: %ld, Row: %ld", (long)selectedIndexPath.section, (long)selectedIndexPath.row);
            UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:selectedIndexPath];
            XXPerson *person = (XXPerson *)[self.fetchedResultsController objectAtIndexPath:selectedIndexPath];
            scoreEditViewController.score = cell.textLabel.text;
            scoreEditViewController.person = person;
        }
    }
    

    I have three transactions/three rows in each section.Its working fine for the first two rows but throwing an error when clicked on the third row.


    Error:
    *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'no object at index 2 in section at index 0'
  • BrianSlickBrianSlick Treadmill Desk Ninja Posts: 10,689Tutorial Authors, Registered Users @ @ @ @ @ @ @ @
    It's the same thing you are doing in configureCell.
    Professional iOS App Development. Available for hire.
    BriTer Ideas LLC - WWW | Facebook | Twitter | LinkedIn

    BTIKit | BTICoreDataKit | SlickShopper 2 | Leave a PayPal donation
Sign In or Register to comment.