0

I have a probem when trying to reload a UICollectionview in iOS.

I am using it to display levels in a game.

The collectionview consists of 10 cells. The content of the cells depends if a level is unlocked. If the level is unlocked the cell displays the level (a custom UIView), else it displays an image. I had to create individual cell identifiers for this to work, and everything displays perfectly on load.

My problem is when a user is playing an unlocked level and then unlocks the next level. When the user navigates back from the game view to the level selection view, the cells are not reloaded correctly (just shows up empty where the custom views should be, the images display correctly).

I have tried to unload the array with levels in viewWillAppear and then calling [collectionview reloadData];, then loading the levels and reloading the collectionview again, but that does not help.

How can I empty the entire collectionview and recreate the cell identifiers when the view is displayed?

Thanks

-EDIT! UPDATED WITH CODE -

-(void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:YES];

levelsArray = nil;
puzzlesArray = nil;

levelsArray = [[NSMutableArray alloc]init];
puzzlesArray = [[NSMutableArray alloc]init];

[collectionView reloadData];

NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSData *data = [defaults objectForKey:@"puzzles"];
puzzlesArray = [NSKeyedUnarchiver unarchiveObjectWithData:data];

if ([puzzlesArray count] == 0) {
    [self.navigationController popViewControllerAnimated:YES];
}
else {
    NSLog(@"%i puzzles loaded", [puzzlesArray count]);

    //Get alle puzzles for the current category
    for (Puzzle *puzzle in puzzlesArray) {

        if ([[[NSUserDefaults standardUserDefaults]objectForKey:@"Category"] isEqualToString:[puzzle categoryName]]) {
            [levelsArray addObject:puzzle];
        }

    }

}

NSLog(@"View will appear");
[collectionView reloadData];

}

And in the cell for item at index path

    - (UICollectionViewCell *)collectionView:(UICollectionView *)cv cellForItemAtIndexPath:(NSIndexPath *)indexPath; {

    BOOL isUnlocked = [self isPuzzleUnlocked:[indexPath row]];

    if ([[NSUserDefaults standardUserDefaults]boolForKey:@"u"] == YES) {
        isUnlocked = YES;
    }

    [self.collectionView registerNib:[UINib nibWithNibName:@"CVCell" bundle:nil] forCellWithReuseIdentifier:[NSString stringWithFormat:@"%@%d", kCellReuseIdentifier, indexPath.row]];

    CVCell *cell = (CVCell *)[collectionView dequeueReusableCellWithReuseIdentifier:[NSString stringWithFormat:@"%@%d", kCellReuseIdentifier, indexPath.row] forIndexPath:indexPath];

[cell setPuzzleInfo:[levelsArray objectAtIndex:[indexPath row]] unlocked:isUnlocked];

        return cell;

}
CCDEV
  • 371
  • 1
  • 5
  • 16
  • 1
    You say the "custom views" are empty, but that "images display correctly". I don't know how to reconcile those two statements. But in answer to your question, `reloadData` _is_ the correct technique. I'd put in log statements or debug breakpoints in your various `UICollectionViewDataSource` and confirm that the correct values are being reported. Did this collection view originally work correctly, but not correctly after "unlocking" a level? – Rob Jun 20 '13 at 11:52
  • Thanks for your comment Rob. Sorry for any confusion, but it is like the cells that are suppose to change on reload are not changing correctly. I have tried to NSLog on the data and it is correct. But is seems like that when the cell is created once it is not "recreated" on view WillAppear. They appear correctly when exiting the view and opening it again. So it seems like the problem is that the cell is still in memory and not being reloaded? – CCDEV Jun 20 '13 at 12:17
  • Generally you would write code in your `viewWillAppear` or `viewDidAppear` that would call `reloadData` (after calling `super`). To repeat myself, I'd put log statements in `numberOfItemsInSection` and `cellForItemAtIndexPath` so you can confirm that the `reloadData` is being called correctly when the view reappears and so you can check the various values of your model structure at that point. – Rob Jun 20 '13 at 12:35

2 Answers2

1

In line with Rob's suggestion.

-(void)viewWillAppear {
   [super viewWillAppear];
   [self.collectionView reloadData]; 
}

In cellForItemAtIndexPath you should simply check if the item is unlocked or not and display the proper image or custom cell.

That's all. It is definitely not necessary to recreate the collection view.

You might want to honour the MVC (model-view-controller) design pattern by giving both your game controller and the level controller access to the data that models the levels. When what is happening in the game unlocks a new level, it should change this data. The collection view should reload itself based on this data just before it appears.

EDIT:

If you get the same cells/items as before even though you reloadData, this means that your configuration is not set up correctly. The key is to set both states of unlocked explicitly - reloadData will then update the item if it has changed.

EDIT2:

Both your itemForRowAtIndexPath methods are messed up. The call to registerNib does not belong there at all! It is supposed to be called during the initialization process. If you use storyboard, it is not necessary at all.

Here is a simple framework that you should use:

- (UICollectionViewCell *)collectionView:(UICollectionView *)cv 
                  cellForItemAtIndexPath:(NSIndexPath *)indexPath; {

   PuzzleInfo *puzzleObject = [dataArray objectAtIndex:indexPath.row]; 
   // you can use your more complicated data structure here, but 
   // have one object, dictionary, array item, etc. that has the info you need

   NSString *identifier = puzzleObject.unlocked ? kUnlockedCell : kLockedCell;
   CVCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:identifier
           forIndexPath:indexPath];

   if (puzzleObject.unlocked) {
      // configure the unlocked cell 
   }
   else {
      // configure the locked cell
   }

   return cell;
}
Mundi
  • 79,884
  • 17
  • 117
  • 140
  • Thanks for your reply Mundi, but I am already doing that. I have added code to my original question to show what I am doing. I think this might be the problem " CVCell *cell = (CVCell *)[collectionView dequeueReusableCellWithReuseIdentifier:[NSString stringWithFormat:@"%@%d", kCellReuseIdentifier, indexPath.row] forIndexPath:indexPath];" because it remembers the cell identifier and therefore is not reloaded properly? – CCDEV Jun 21 '13 at 06:14
-3

I fixed it!

- (UICollectionViewCell *)collectionView:(UICollectionView *)cv cellForItemAtIndexPath:(NSIndexPath *)indexPath; {
NSLog(@"CALLED");
BOOL isUnlocked = [self isPuzzleUnlocked:[indexPath row]];

if ([[NSUserDefaults standardUserDefaults]boolForKey:@"u"] == YES) {
    isUnlocked = YES;
}
int rand = arc4random() % 99001;
[self.collectionView registerNib:[UINib nibWithNibName:@"CVCell" bundle:nil] forCellWithReuseIdentifier:[NSString stringWithFormat:@"%@%d%i", kCellReuseIdentifier, indexPath.row, rand]];

CVCell *cell = (CVCell *)[collectionView dequeueReusableCellWithReuseIdentifier:[NSString stringWithFormat:@"%@%d%i", kCellReuseIdentifier, indexPath.row, rand] forIndexPath:indexPath];
[cell setPuzzleInfo:[levelsArray objectAtIndex:[indexPath row]] unlocked:isUnlocked];

    return cell;

}

Not a very memory effecient solution (or elegant), but it works. Adding a random number to each cell forces it to be recreated each time. As long as I only have 10 cells in a collectionview I think it will be ok..

Thanks for all your help :]

CCDEV
  • 371
  • 1
  • 5
  • 16
  • This is not a good solution, least of all a "fix". Even if it seems to work now, it is bound to break. You are creating random cell identifiers at hoc in cellForItem... See my revised answer for the explanation. – Mundi Jun 21 '13 at 09:56
  • Hi Mundi, yes I know its now an ideal solution. The problem is that your answer is not solving the problem. Don't know if it is a collectionview bug, but the data is correct, but is displayed wrong. This fixes it because the cell identifier is recreated. When I do a NSLog of the locked Unlocked data, it shows correctly, but is not reloaded in [collectiionview reloadData]; I can't see why this would break? I am using ARC and recreating the cells every time should not be a problem? Performance is the only issue, but with only 10-15 cells that is not a problem. – CCDEV Jun 23 '13 at 09:32
  • In this case it is still absurd to create random cell identifiers. Just don't dequeue and you have the same effect. – Mundi Jun 23 '13 at 09:36
  • Hi, can you give me "isPuzzleUnlocked" method.@CCDEV – Ravi Jun 10 '15 at 14:01