UICollectionView insertItemsAtIndexPaths:抛出exception

UICollectionView:我做错了。 我只是不知道如何。

我的设置

我在带有iOS 6.0.1的iPhone 4S上运行它。

我的目标

我有一个表视图,其中一个部分专门用于图像: 表视图部分的屏幕截图

当用户点击“添加图像…”单元格时,系统会提示他们从照片库中选择图像或使用相机拍摄新照片。 应用程序的这一部分似乎工作正常。

当用户首次添加图像时,将从第二个表格单元格中删除“无图像”标签,并在其位置添加以编程方式创建的UICollectionView。 那部分似乎也运作良好。

集合视图应该显示他们添加的图像,它就在我遇到麻烦的地方。 (我知道随着图像数量的增长,我将不得不跳过一些箍来让桌面视图单元扩大。我还没那么远。)

当我尝试将项目插入到集合视图中时,它会抛出exception。 稍后会详细介绍。

我的代码

我有UITableViewController负责表视图也充当集合视图的委托和数据源。 这是相关代码(我省略了我认为与此问题无关的控制器位,包括-viewDidLoad方法中的-viewDidLoad 。我也省略了大部分图像采集代码,因为我认为它不相关) :

 #define ATImageThumbnailMaxDimension 100 @interface ATAddEditActivityViewController () { UICollectionView* imageDisplayView; NSMutableArray* imageViews; } @property (weak, nonatomic) IBOutlet UITableViewCell *imageDisplayCell; @property (weak, nonatomic) IBOutlet UILabel *noImagesLabel; @end @implementation ATAddEditActivityViewController - (void)viewDidLoad { [super viewDidLoad]; UICollectionViewFlowLayout* flowLayout = [[UICollectionViewFlowLayout alloc] init]; flowLayout.scrollDirection = UICollectionViewScrollDirectionVertical; imageDisplayView = [[UICollectionView alloc] initWithFrame:CGRectMake(0, 0, 290, 120) collectionViewLayout:flowLayout]; // The frame rect still needs tweaking imageDisplayView.delegate = self; imageDisplayView.dataSource = self; imageDisplayView.backgroundColor = [UIColor yellowColor]; // Just so I can see the actual extent of the view imageDisplayView.opaque = YES; [imageDisplayView registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:@"Cell"]; imageViews = [NSMutableArray array]; } #pragma mark - UIImagePickerDelegate - (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info { /* ...code defining imageToSave omitted... */ [self addImage:imageToSave toCollectionView:imageDisplayView]; [self dismissModalViewControllerAnimated:YES]; } #pragma mark - UICollectionViewDelegate - (BOOL)collectionView:(UICollectionView *)collectionView shouldShowMenuForItemAtIndexPath:(NSIndexPath *)indexPath { return YES; } #pragma mark - UICollectionViewDatasource - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath { UICollectionViewCell* cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"Cell" forIndexPath:indexPath]; [[cell contentView] addSubview:imageViews[indexPath.row]]; return cell; } - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section { return [imageViews count]; } #pragma mark - UICollectionViewDelegateFlowLayout - (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath { return ((UIImageView*)imageViews[indexPath.item]).bounds.size; } #pragma mark - Image Handling - (void)addImage:(UIImage*)image toCollectionView:(UICollectionView*)cv { if ([imageViews count] == 0) { [self.noImagesLabel removeFromSuperview]; [self.imageDisplayCell.contentView addSubview:cv]; } UIImageView* imageView = [[UIImageView alloc] initWithImage:image]; /* ...code that sets the bounds of the image view omitted... */ [imageViews addObject:imageView]; [cv insertItemsAtIndexPaths:@[[NSIndexPath indexPathForItem:[imageViews count]-1 inSection:0]]]; [cv reloadData]; } @end 

总结一下:

  • 集合视图在-viewDidLoad方法中实例化和配置
  • 接收所选图像的UIImagePickerDelegate方法调用-addImage:toCollectionView
  • …创建一个新的图像视图来保存图像并将其添加到数据源数组和集合视图中。 这是产生exception的行。
  • UICollectionView数据源方法依赖于imageViews数组。

例外

由于未捕获的exception’NSInternalInconsistencyException’而终止应用程序,原因:’无效更新:第0节中的项目无效。更新后的现有部分中包含的项目数(1)必须等于该项目中包含的项目数更新前的部分(1),加上或减去从该部分插入或删除的项目数(插入1个,删除0个),加上或减去移入或移出该部分的项目数量(0移入,0移动)出)。”

如果我正在解析这个权利,这告诉我的是(全新的!)集合视图认为它是用单个项目创建的。 所以,我在-addImage:toCollectionView添加了一个日志来测试这个理论:

 NSLog(@"%d", [cv numberOfItemsInSection:0]); 

有了那条线,exception永远不会被抛出! 对-numberOfItemsInSection:的调用-numberOfItemsInSection:必须强制集合视图查询其数据源并意识到它没有项目。 或者其他的东西。 我猜想在这里。 但是,好吧,无论如何:集合视图在这一点上仍然没有显示任何项目,所以我仍然做错了什么,我不知道是什么。

结论是

  1. 当我尝试将一个项目添加到新建和插入的集合视图时,我得到一个奇怪的例外…除非我在尝试插入之前调用-numberOfItemsInSection:
  2. 即使我设法通过阴暗的解决方法超越exception,项目仍然不会显示在集合视图中。

对不起,小问题。 有任何想法吗?

只是猜测:当您插入第一个图像时,集合视图可能尚未加载其数据。 但是,在exception消息中,集合视图声称“知道”插入之前的项目数(1)。 因此,它可能在insertItemsAtIndexPaths:延迟加载其数据insertItemsAtIndexPaths:并将结果作为“之前”状态。 此外,插入后无需重新加载数据。

长话短说,移动了

 [cv reloadData]; 

得到

 if ([imageViews count] == 0) { [self.noImagesLabel removeFromSuperview]; [self.imageDisplayCell.contentView addSubview:cv]; [cv reloadData]; } 

不幸的是,接受的答案是不正确的(尽管它在正确的轨道上); 问题是,当你刚刚插入项目时,你正在调用reloadData和insertItems。 所以代替:

 [imageViews addObject:imageView]; [cv insertItemsAtIndexPaths:@[[NSIndexPath indexPathForItem:[imageViews count]-1 inSection:0]]]; [cv reloadData]; 

做就是了:

 [imageViews addObject:imageView]; [cv insertItemsAtIndexPaths:@[[NSIndexPath indexPathForItem:[imageViews count]-1 inSection:0]]]; 

这不仅会给你一个很好的动画,它会阻止你低效地使用tableview(在1单元集合视图中不是很大的问题,但是对于更大的数据集来说是个大问题),并避免像你看到的那样崩溃,其中两个方法都试图修改集合视图(其中一个 – reloadData – 与其他方法不兼容)。

顺便说一句,reloadData不是非常友好的UICollectionView; 如果你确实有一个相当大的和/或复杂的集合,并且在调用reloadData之前或之后很快就会发生插入,那么插入可能会在reloadData完成之前完成 – 这将可靠地导致“无效数量的项目”崩溃(同样如此)删除)。 调用reloadSections而不仅仅是reloadData似乎有助于避免这个问题。

面对同样的问题,但我的原因是我忘了将集合视图数据源连接到视图控制器

这是因为[单元格数]不等于[实际索引计数] + [插入索引]。 有时dispatch_async不包含数组插入数据块和insertItemsAtIndexPaths。 我遇到了somtimes崩溃的问题。 每次崩溃都不是原因。