【问题标题】:UICollectionView insertItemsAtIndexPaths: throws exceptionUICollectionView insertItemsAtIndexPaths:抛出异常
【发布时间】:2012-11-15 06:12:50
【问题描述】:

UICollectionView:我做错了。我只是不知道怎么做。

我的设置

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

我的目标

我有一个表格视图,其中一个部分专门用于图像:

当用户点击“添加图片...”单元格时,系统会提示他们从照片库中选择一张图片或使用相机拍摄一张新图片。应用程序的这一部分似乎运行良好。

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

集合视图应该显示他们添加的图像,而我在这里遇到了麻烦。 (我知道随着图像数量的增加,我将不得不跳过一些障碍来让表格视图单元格自行放大。我还没有那么远。)

当我尝试将一个项目插入到集合视图中时,它会引发异常。稍后会详细介绍。

我的代码

我有负责表格视图的 UITableViewController 也充当集合视图的委托和数据源。这是相关代码(我省略了我认为与此问题无关的控制器位,包括-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
  • ...创建一个新的图像视图来保存图像并将其添加到数据源数组和集合视图中。这是产生异常的行。
  • UICollectionView 数据源方法依赖于 imageViews 数组。

例外

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

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

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

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

结论

  1. 当我尝试将项目添加到新创建和插入的集合视图时遇到一个奇怪的异常...除非我在尝试插入之前调用 -numberOfItemsInSection:。
  2. 即使我设法通过一个隐蔽的解决方法克服了异常,这些项目仍然不会显示在集合视图中。

对不起,小说的问题。有什么想法吗?

【问题讨论】:

  • 我唯一能建议的就是在[imageViews addObject:imageView]; 和-numberOfItemsInSection 之后添加一个NSLog 语句。这将准确地告诉您您的数组是否是它应该是的,以及是否在正确的时刻调用了 numberOfItemsInSection。

标签: ios uiimageview uicollectionview


【解决方案1】:

很遗憾,接受的答案不正确(尽管它在正确的轨道上);问题是当您应该刚刚插入项目时,您正在调用 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-cell 集合视图中不是什么大问题,但对于更大的数据集来说是一个大问题),并避免像这样的崩溃你看到了,两种方法都试图修改集合视图(其中一种方法——reloadData——与其他方法不兼容)。

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

【讨论】:

  • 太棒了!我遇到了“在 reloadData 导致异常后立即调用插入”的问题。花了很多时间试图避免 UICollectionView 中那个丑陋的错误,但无济于事。通过用 reloadSections 替换 reloadData,现在一切正常,谢谢!
【解决方案2】:

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

【讨论】:

    【解决方案3】:

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

    【讨论】:

      【解决方案4】:

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

      长话短说,移动

      [cv reloadData];
      

      等待

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

      【讨论】:

      • 顺便说一句,你根本不需要使用insertItemsAtIndexPaths。只是做reloadData 应该会更新所有内容,尽管没有动画。
      • 有趣。让我想知道该方法的用途是什么。
      • 无论如何,事实证明我的图像没有显示,因为我在 UIImageViews 上调用了 setBounds 而不是 setFrame。接受你关于不使用 insertItemsAtIndexPaths 的建议,我很高兴。谢谢!
      • insertItemsAtIndexPaths 的目的是最大限度地减少完成的工作量,同时为更改设置动画。调用 reloadData 通常不是您想要做的,因为您不是只重绘受影响的单元格,而是强制控制器重新加载并重绘所有内容。这可能会导致对您的应用产生中等但可衡量的负面影响,以及对应用程序造成严重影响的 UI 冻结缺陷——具体取决于您的收藏可能包含多少项目。
      • 重新加载所有数据的性能远不如仅插入新项目。
      猜你喜欢
      • 1970-01-01
      • 2015-10-22
      • 1970-01-01
      • 1970-01-01
      • 2013-05-24
      • 2011-05-30
      • 1970-01-01
      • 2011-02-25
      相关资源
      最近更新 更多