Andrey Chernukha
Andrey Chernukha

Reputation: 21808

UICollectionView relodItemsAtIndexPaths reloads all items the collection view

Please consider this simple code:

@interface ViewController ()<UICollectionViewDelegate, UICollectionViewDataSource>

@property (nonatomic, strong) IBOutlet UICollectionView* collectionView;

@end

@implementation ViewController

- (void)viewDidLoad {
     [super viewDidLoad];

     self.view.backgroundColor = [UIColor redColor];

     [self.collectionView registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:@"Hello"];

     dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(20 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{

         [self.collectionView reloadItemsAtIndexPaths:@[ [NSIndexPath indexPathForItem:0 inSection:0]]];
});
}

- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
{
    return 20;
}

- (__kindof UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
    NSLog(@"RELOADING %ld", indexPath.row);

    NSString* identifier = @"Hello";

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

    NSArray* colors = @[[UIColor redColor], [UIColor blueColor]];

    cell.backgroundColor = colors[indexPath.row % colors.count];

    return cell;
}

@end

So there is a very basic UICollectionView here which is created in a storyboard. There's nothing especial about it. So what I'm doing is trying to reload just one item. And when I call reloadItemsAtIndexPaths it reloads all the items first and immediately reloads the one that I actually want to be reloaded. It is obviously wrong. I need to avoid doing all this extra work. Why does it even behave like that? Is it an iOS bug? Can anything be done to make it work correctly?

Upvotes: 0

Views: 74

Answers (1)

DonMag
DonMag

Reputation: 77672

If your UICollectionView is using a layout which may have dynamic-sized cells, the layout will be reevaluated every time a cell is updated. That means cellForItemAtIndexPath will be called for lots of cells.

Note, though, that with your code only item 0,0 actually gets updated.

Change your code to this (note, I changed your dispatch time to 3 seconds so we don't have to wait so long to see the update):

@interface WithCollectionViewController ()<UICollectionViewDelegate, UICollectionViewDataSource>

@property (nonatomic, strong) IBOutlet UICollectionView* collectionView;
@property (nonatomic, strong) NSArray *colors;

@end

@implementation WithCollectionViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    // start with colors array of red and blue
    self.colors = @[[UIColor redColor], [UIColor blueColor]];

    [self.collectionView registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:@"Hello"];
    [self.collectionView setDataSource:self];
    [self.collectionView setDelegate:self];

    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"calling RELOAD");
        // change colors array to green and yellow
        self.colors = @[[UIColor greenColor], [UIColor yellowColor]];
        [self.collectionView reloadItemsAtIndexPaths:@[ [NSIndexPath indexPathForItem:0 inSection:0]]];
    });

}

- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
    return 20;
}

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

    NSLog(@"RELOADING %ld", indexPath.row);

    NSString* identifier = @"Hello";

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

    cell.backgroundColor = self.colors[indexPath.row % self.colors.count];

    return cell;

}

@end

You will first get a red/blue pattern. When reloadItemsAtIndexPaths is triggered, the colors array will have been changed to green/yellow... you will see debug output for all 20 cells, but only the first cell will change color (to green).

enter image description here

If you don't want the repeated calls, give your layout a fixed item size:

@interface WithCollectionViewController ()<UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout>

@property (nonatomic, strong) IBOutlet UICollectionView* collectionView;
@property (nonatomic, strong) NSArray *colors;

@end

@implementation WithCollectionViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    // start with colors array of red and blue
    self.colors = @[[UIColor redColor], [UIColor blueColor]];

    [self.collectionView registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:@"Hello"];
    [self.collectionView setDataSource:self];
    [self.collectionView setDelegate:self];

    UICollectionViewFlowLayout *layout = [UICollectionViewFlowLayout new];
    layout.itemSize = CGSizeMake(50, 50);
    [self.collectionView setCollectionViewLayout:layout];

    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"calling RELOAD");
        // change colors array to green and yellow
        self.colors = @[[UIColor greenColor], [UIColor yellowColor]];
        [self.collectionView reloadItemsAtIndexPaths:@[ [NSIndexPath indexPathForItem:0 inSection:0]]];
    });

}

- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
    return 20;
}

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

    NSLog(@"RELOADING %ld", indexPath.row);

    NSString* identifier = @"Hello";

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

    cell.backgroundColor = self.colors[indexPath.row % self.colors.count];

    return cell;

}

@end

Same result, but cellForItemAtIndexPath is only called for your specified 0,0 index path.

Upvotes: 2

Related Questions