0

I am developing small application in iOS (I am complete beginner). I have made a menu Core Data entity where I have categoryName and imageNamed saved. I want my UICollectionView in sections. But I am stuck in defining different indexPaths for different sections.

I also feel that my way of accessing data is very poor and seems inefficient as I am defining two NSFetchedResultsController instances. How can I access the data store better?

I get this error:

14-03-17 10:02:19.974 citiliving[1149:70b] * Terminating app due to uncaught exception 'NSRangeException', reason: '* -[__NSArrayM objectAtIndex:]: index 1 beyond bounds [0 .. 0]'

Error seems is out of bound so section is accessing something that is not in array boundaries.

Code to access data in viewDidLoad:

- (void)viewDidLoad
{
    [super viewDidLoad];

    testAppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];
    NSManagedObjectContext *managedObjectContext = [appDelegate managedObjectContext];

    NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:@"Menu"];
    NSString *cacheName = [@"Menu" stringByAppendingString:@"Cache"];

    NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:@"categoryName" ascending:YES];
    NSPredicate* pred = [NSPredicate predicateWithFormat:@"section = %@", @"dining"];

    fetchRequest.predicate = pred; // req is the NSFetchRequest we're configuring
    [fetchRequest setSortDescriptors:@[sortDescriptor]];

    self.fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:managedObjectContext sectionNameKeyPath:nil cacheName:cacheName ];

    NSError *error;
    if (![self.fetchedResultsController performFetch:&error])
    {
        NSLog(@"Fetch failed: %@", error);
    }
    else
    {
        NSLog(@"Records found: number %lu", (unsigned long)self.fetchedResultsController.fetchedObjects.count);     
    }

    NSFetchRequest *fetchRequestMenuTwo = [NSFetchRequest fetchRequestWithEntityName:@"Menu"];
    NSString *cacheNameTwo = [@"Menu" stringByAppendingString:@"Cache"];

    NSSortDescriptor *sortDescriptorTwo = [NSSortDescriptor sortDescriptorWithKey:@"categoryName" ascending:YES];
    NSPredicate* predTwo = [NSPredicate predicateWithFormat:@"section = %@", @"information"];

    fetchRequestMenuTwo.predicate = predTwo; // req is the NSFetchRequest we're configuring
    [fetchRequestMenuTwo setSortDescriptors:@[sortDescriptorTwo]];

    self.fetchedResultsControllerTwo = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequestMenuTwo managedObjectContext:managedObjectContext sectionNameKeyPath:nil cacheName:cacheNameTwo ];

    //NSError *error;
    if (![self.fetchedResultsControllerTwo performFetch:&error])
    {
        NSLog(@"Fetch failed: %@", error);
    }
    else
    {
        NSLog(@"Records found at 2nd fetch: number %lu", (unsigned long)self.fetchedResultsControllerTwo.fetchedObjects.count);
    }
}

Note: I wanted to create object in variable which I can easily access in further code but I tried all variable could not do it.

Code used to get sections:

-(NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
{
    switch (section) {
        case 0:
            return self.fetchedResultsController.fetchedObjects.count;
        case 1:
            return self.fetchedResultsControllerTwo.fetchedObjects.count;
        default:
            return 0;
    }
}

-(NSString *)nameForSection:(NSInteger)index
{
    switch(index)
    {
        case 0:
            return @"Section One";
        case 1:
            return @"Section Two";
        default:
            return @"Unknown";
    }
}

Finally getting everything in indexPath (here I am getting error as soon as I put 2nd section coding otherwise first section running ok:

-(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *identifier = @"Cell";

    UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:identifier forIndexPath:indexPath];

    switch (indexPath.section) {
        case 0:
        {
            Menu *menu = (Menu *)[self.fetchedResultsController objectAtIndexPath:indexPath];
            UIImageView *cityLivingImageView = (UIImageView *)[cell viewWithTag:100];
            UILabel *cityLivingLabel = (UILabel *)[cell viewWithTag:200];
            cityLivingLabel.text = menu.categoryName;
            cityLivingImageView.image    = [UIImage imageNamed:menu.imageName];

            NSLog(@"Record found: %@", menu.categoryName );

            return cell;
        }
        case 1:
        {
            Menu *menu = (Menu *)[self.fetchedResultsControllerTwo objectAtIndexPath:indexPath];
            UIImageView *cityLivingImageView = (UIImageView *)[cell viewWithTag:100];
            UILabel *cityLivingLabel = (UILabel *)[cell viewWithTag:200];
            cityLivingLabel.text = menu.categoryName;
            cityLivingImageView.image    = [UIImage imageNamed:menu.imageName];

            NSLog(@"Record found: %@", menu.categoryName );

            return cell;        
        }
        default:
        break;
    }

    return cell;    
}

I have only two sections and one has 5 rows and other have 6 rows. I wanted to make this one controller to access data and put everything in variables so later I can use variables. My database is categoryName | categoryImage | section

where section identify section one and two so I use predicate to make two different section using section field value.

Kamal Panhwar
  • 2,345
  • 3
  • 21
  • 37

2 Answers2

0

First question is how many different values do your Menu objects have for the section key? Generally you should a single fetched results controller (FRC) and use section as the sectionNameKeyPath.

Another issue with your FRC setup is that both use a cache with the same name. This won't work properly as the FRCs each have different predicates. Ensure the cache names are unique or remove the cache names and set to nil.

Your crash comes because you are using 2 FRCs but you treat the second one like you only have 1. You are creating a collection view with 2 sections, but each FRC only understands one section. Your first section works because the section number (zero) matches what the first FRC expects. But, the second section has a section number of one, which doesn't match, so you get a range exception.

You should change your code to only use one FRC if you can (if you want one section for each different value of section in the data store). If not, you can fix the crash by changing your code to:

NSIndexPath *fabricatedIndexPath = [NSIndexPath indexPathForItem:indexPath.item inSection:0];
Menu *menu = (Menu *)[self.fetchedResultsControllerTwo objectAtIndexPath:fabricatedIndexPath];

(bit messy really)

Wain
  • 118,658
  • 15
  • 128
  • 151
  • Wain, suggestion of about cachename work form. Now my it is working. But I really want your suggestion how to make it clean as it is really messy. Second I again got problem as when I do objectAtIndex:selectedIndexPath.row] I get 2nd section indexPath, as I have to code that where they click its content come up. – Kamal Panhwar Mar 17 '14 at 14:02
  • So, can you use a single FRC with `sectionNameKeyPath:@"section"` ? – Wain Mar 17 '14 at 14:07
  • Wain, Thanks for quick reply, I have searched a lot about sectionNameKeyPath, but found nothing how can I implement it? if I use one FRC with section field how will I sort out two sections by using database values. is there a way to use one FRC and in the coding of indexPath distinguish sections as per field? – Kamal Panhwar Mar 17 '14 at 14:20
  • the index path provides `section` and `item` numbers to you. this is what `sectionNameKeyPath` is for. try using it – Wain Mar 17 '14 at 14:28
  • Honestly as I am new so still not clear how can I use it? do I need to do changes in my database? please can your help to correct references site where I can understand exactly how to use it! Sorry I acknowledge my beginning in development but need to give full product, and learn everything in self study. – Kamal Panhwar Mar 18 '14 at 06:51
  • http://www.raywenderlich.com/999/core-data-tutorial-for-ios-how-to-use-nsfetchedresultscontroller – Wain Mar 18 '14 at 08:28
0

I finally made everything correct, although wish can make my code cleaner but no idea how to make from two FRCs to one and use sectionNameKeyPath, but as I have to submit application to management so I have done following changes.

  1. I have made two different caches as Mr. Wain suggested and it resolved error problem.

But after getting two section I came in another problem of selecting index. As I was clicking on one section but getting same indexPath so I used if loop to see what exact section is and used two converted FRCs to object and than assign section exact path. My segue to select section is following. I know it is messy but time constraint made me to do it.

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([[segue identifier] isEqualToString:@"showCategoriesMenu"])
{
    NSIndexPath *selectedIndexPath = [[self.collectionView indexPathsForSelectedItems] objectAtIndex:0];
    SubMenuTableViewController *subMenuViewController = [segue destinationViewController];

    NSString *clickedCell;

    if (selectedIndexPath.section == 1)
    {
    Menu *menu = (Menu *)[itemsMenuTwo  objectAtIndex:selectedIndexPath.row];
        clickedCell = menu.categoryName;
    }
    else
    {
    Menu *menu = (Menu *)[itemsMenuOne  objectAtIndex:selectedIndexPath.row];
        clickedCell = menu.categoryName;
    }
    subMenuViewController.title = clickedCell;
    subMenuViewController.categoryName = clickedCell;
}
}

Thanks for support and I really appreciate it.

Kamal Panhwar
  • 2,345
  • 3
  • 21
  • 37