Reputation: 21808
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
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).
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