无效更新:UICollectionView上的项目数无效

时间:2013-10-05 16:26:33

标签: ios objective-c uitableview uicollectionview

我很难过。这是我的情景。在我的Appdelegate中,我正在创建

  1. 将以模态方式呈现以从用户收集两条数据的视图控制器实例
  2. tableView控制器
  3. 我使用tableview控制器作为其根视图初始化的导航控制器
    控制器
  4. 一个collectionviewController,请注意我也在这里注册了单元格的类。
  5. 我将导航控制器添加到第一个视图和中的UITabBarController collectionview作为第二个视图..将TabBarCOntorller设置为窗口的根视图。
  6. 以下是代码:

     - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:   
    (NSDictionary *)launchOptions
    {
    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    self.evc = [[WTAEntryControllerViewController alloc] init];
    WTATableView *tv = [[WTATableView alloc] initWithNibName:@"WTATableView" bundle:nil];
    UINavigationController *nav = [[UINavigationController alloc]    
    initWithRootViewController:tv];
    nav.tabBarItem.title = NSLocalizedString(@"Birthday List", nil);
    nav.tabBarItem.image = [UIImage imageNamed:@"birthdaycake"];
    WTACollectionViewController *cv = [[WTACollectionViewController alloc] 
    initWithCollectionViewLayout:[[UICollectionViewFlowLayout alloc] init]];
    [cv.collectionView registerClass:[UICollectionViewCell class] 
    forCellWithReuseIdentifier:CellIdentifier];
    cv.collectionView.backgroundColor = [UIColor whiteColor];
    NSLog(@"cv instance %@", cv);
    UITabBarController *tabController = [[UITabBarController alloc] init];
    tabController.viewControllers = @[nav, cv];
    self.window.rootViewController = tabController;
    [self.window makeKeyAndVisible];
    return YES
    }
    

    tableview有一个导航按钮,用于显示模型视图控制器。模态视图控制器有一个按钮,该按钮从textField和datepicker获取值,并使用UserInfo字典中的值将其发布到默认通知中心。 tableView控制器订阅通知,将UserInfo字典放入MutableArray并更新表。这很好。

    问题出在CollectionVIew上。 collectionVIew接收通知并调用目标方法来处理通知。我试图从通知中获取UserInfo字典并将新单元格添加到collectionView。

    但是...

    当collectionview收到通知时,我收到错误:

    '无效更新:第0部分中的项目数无效。更新后的现有部分中包含的项目数(1)必须等于更新前该部分中包含的项目数(1),再加上或减去从该部分插入或删除的项目数量(插入1个,删除0个)以及加入或减去移入或移出该部分的项目数量(0移入,0移出)。“

    以下是CollectionView的代码:

    #import "WTACollectionViewController.h"
    #import "UIColor+WTAExtensions.h"
    #import "WTAAppDelegate.h"
    #import "WTAEntryControllerViewController.h"
    
    @interface WTACollectionViewController () <UICollectionViewDelegateFlowLayout>
    @property (strong, nonatomic) NSMutableArray *birthdays;
    @end
    
    @implementation WTACollectionViewController
    
    -(id)initWithCollectionViewLayout:(UICollectionViewLayout *)layout{
    
    NSLog(@"Calling init on Collection");
    if(!(self = [super initWithCollectionViewLayout:layout])){
        return nil;
    }
    
    self.birthdays = [NSMutableArray array];
    
    NSLog(@"Count of Birthdays at init %ld", (long)[self.birthdays count] );
    self.title = NSLocalizedString(@"Birthday Grid", nil);
    self.tabBarItem.image = [UIImage imageNamed:@"package"];
    NSLog(@"cv instance getting notif %@", self);
    [[NSNotificationCenter defaultCenter]
     addObserver:self
     selector:@selector(handleNotification:)
     name:@"BirthdayAdded"
     object:nil];
     return self;
    
    }
    
    
    -(void)handleNotification:(NSNotification *)notification
    {
    [self.birthdays addObject:[notification userInfo]];
    NSLog(@"Count of birthdays when the notification is received: %ld", (long)         
    [self.birthdays count]);
    [self.collectionView insertItemsAtIndexPaths:@[[NSIndexPath indexPathForItem:   
    (self.birthdays.count -1) inSection:0]]];
    }
    
    - (void)viewDidLoad
    {
    [super viewDidLoad];
    
    NSLog(@"Calling View Did Load on Collection");
    }
    
    - (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView {
    
    return 1;
    
    }
    
    - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:   
    (NSInteger)section{
    
    NSLog(@"Count of birthdays in  the number of items in section: %ld", (long)  
    [self.birthdays count]);
    return [self.birthdays count];
    }
    
    - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView    
    cellForItemAtIndexPath:(NSIndexPath *)indexPath {
    
    NSLog(@"Calling the cell for index path method");
    
    NSDictionary *dict = [[NSMutableDictionary alloc] init];
    dict = self.birthdays[indexPath.item];
    
    UICollectionViewCell *cell = [collectionView   
    dequeueReusableCellWithReuseIdentifier:CellIdentifier forIndexPath:indexPath];
    NSAssert(cell != nil, @"Expected a Cell");
    
    cell.contentView.backgroundColor = [UIColor randomColor];
    return cell;
    
    }
    
    #define GAP (1.0f)
    
    -(CGSize)collectionView:(UICollectionView *)collectionView layout:   
    (UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath   
    *)indexPath {
    
    
    CGFloat edgeLength = self.collectionView.frame.size.width / 4.0f - GAP;
    return (CGSize) {.width = edgeLength, .height = edgeLength};
    }
    
    -(CGFloat)collectionView:(UICollectionView *)collectionView layout:   
    (UICollectionViewLayout *)collectionViewLayout minimumLineSpacingForSectionAtIndex:    
    (NSInteger)section{
    
    return GAP;
    }
    
    -(CGFloat)collectionView:(UICollectionView *)collectionView layout:   
    (UICollectionViewLayout *)collectionViewLayout minimumInteritemSpacingForSectionAtIndex:  
    (NSInteger)section{
    
    return GAP;
    }
    @end
    

    我非常感谢能在这里指出任何事情的人......

7 个答案:

答案 0 :(得分:58)

在空insertItemsAtIndexPaths上使用UICollectionView是一个错误。就这样做:

if (self.birthdays.count == 1) {
    [self.collectionView reloadData];
} else {
    [self.collectionView insertItemsAtIndexPaths:@[[NSIndexPath indexPathForItem:   (self.birthdays.count -1) inSection:0]]];
}

(不能相信这在iOS8中仍然存在。)

答案 1 :(得分:19)

即使在iOS 11中它也是UICollectionView中的一个错误,但在UITableView中不存在,很容易重现: 假设以下SWIFT代码正在运行:

addItemInDataSource()
collectionView!.insertItems(at: [IndexPath(item: position, section: 0)])

将其更改为:

collectionView!.reloadData()    // <-- This new line will make UICollection crash...

addItemInDataSource()
collectionView!.insertItems(at: [IndexPath(item: position, section: 0)])

它会崩溃......

在某些情况下,UICollectionView会丢失原始项目数,只需在insertItems命令之前添加一个虚拟行就可以避免这种崩溃:

collectionView!.reloadData()

collectionView!.numberOfItems(inSection: 0) //<-- This code is no used, but it will let UICollectionView synchronize number of items, so it will not crash in following code.
addItemInDataSource()
collectionView!.insertItems(at: [IndexPath(item: position, section: 0)])

希望它对您的情况有所帮助。

答案 2 :(得分:12)

当我在仅包含一个部分的collectionView中插入元素时,我遇到了同样的错误。

我已完成Timothy的修复,但这还不够。 实际上,当我尝试插入新元素时,collectionView已经刷新了它的数据。 使用collectionView numberOfItemsInSection可以解决问题。

[self.collectionView performBatchUpdates:^{
        NSMutableArray *arrayWithIndexPaths = [NSMutableArray array];
        for (NSUInteger i = [self.collectionView numberOfItemsInSection:0]; i < self.elements.count ; i++)
        {
            [arrayWithIndexPaths addObject:[NSIndexPath indexPathForRow:i inSection:0]];
        }

        [self.collectionView insertItemsAtIndexPaths:arrayWithIndexPaths];
    } completion:nil];

答案 3 :(得分:3)

在我的情况下,numberOfItemsInSectionself.performBatchUpdates被调用两次(一次用于BEFORE号码,一次用于AFTER号码)因为我试图在视图控制器之前插入一些东西到collectionView中装完了。

解决方案是在调用guard isViewLoaded else { return }之前添加self.performBatchUpdates

答案 4 :(得分:0)

至于我,问题与索引集有关 - 在哪里插入部分。  1.我已经插入了0 ..&lt; 10(10个部分)  2.我试图将Sections插入IndexSet 9 ..&lt; 19。但是将新对象附加到dataSource数组的末尾。正确的索引集应为(10 ..&lt; 20)

因此,最后一节仍然期望第9节中的行数。但是在数据源中 - 它在索引19处。

collectionView?.insertSections(IndexSet(integersIn:startIndex ..&lt;(startIndex + series.count)))

答案 5 :(得分:0)

我不练习Obj-c,所以我用Swift写答案。

假设生日是日期数组,如String

birthdays = [String]()

func addDate() {
   birthdays.append("your_new_date")
   collectionView.insertItems(at: [IndexPath(item: birthdays.count - 1, section: 0)] ) 
}

func removeDate() {
   if birthdays.count > 0 {
      birthdays.removeLast() // use remove(at: Int) instead if u want
      collectionView.deleteItems(at: [IndexPath(item: birthdays.count, section: 0)] )
   }
}

您应该避免此错误:

当collectionview收到通知时,我收到错误消息:

'无效更新:第0节中的无效项数。更新(1)之后现有节中包含的项数必须等于更新(1)前该节中包含的项数,再加上或减去从该部分插入或删除的项目数(插入1,删除0),再加上或减去移入或移出该部分的项目数(移入0,移出0)。'

答案 6 :(得分:0)

  1. 由于collectionView异步重新加载数据,因此当您调用[collectionView reloadData]方法时,可能会在一段时间后重新加载dataSource。您可以调用[collectionView layoutIfNeeded]强制立即重新加载collectionView。
  2. 并且当您在[collectionView reloadData]上调用[collectionView insertItemsAtIndexPaths:@ [indexPaths ...]]时,有时项目数可能不相等,您可以在调用[collectionView insertItemsAtSectionPaths]之前作为一种技巧来调用[collectionView numberOfItemsInSection:]。 :@ [indexPaths ...]]。
  3. 当dataSource为空时,插入将崩溃,您应该调用[collectionView reloadData]而不是[collectionView numberOfItemsInSection:]

如上所述,为避免此问题,您可以这样做:

        static void Main(string[] args)
        {
            BlockingCollection<long> objectPool = new BlockingCollection<long>();

            Task.Factory.StartNew(() =>
            {
                long counter = 0;
                try
                {
                    while (counter < long.MaxValue)
                    {
                        if (++counter % 1000 == 0)
                        {
                            objectPool.TryAdd(counter);
                        }
                    }
                    objectPool.CompleteAdding();
                }
                catch
                {
                    //after completeadding is called by this task, 
                    //the another task will break the foreach statement.
                    objectPool.CompleteAdding();
                }
            });


            Task.Factory.StartNew(() =>
            {
               //Will block and wait until there are consumable objects.
               foreach(var obj in objectPool.GetConsumingEnumerable())
               {
                    Console.WriteLine(obj);//consume the object
               }
            });
        }