ios 无效更新:UICollectionView 上的项目数无效
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/19199985/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Invalid update: invalid number of items on UICollectionView
提问by Tommy Alexander
I am stumped on this. Here is my scenario. In my Appdelegate, I am creating
我被这个难住了。这是我的场景。在我的 Appdelegate 中,我正在创建
- An instance of a view controller that will be presented modally to collect two pieces of data from the user
- A tableView controller
- A navigation controller that I init with the tableview controller as its rootview
controller - A collectionviewController, note that I am registering the class for the cell here as well.
- A UITabBarController that I add the navigation controller to as the first view and the
collectionview to as the second view.. The TabBarCOntorller is set as the rootview of the window.
- 一个视图控制器的实例,它将以模态呈现以从用户那里收集两条数据
- 一个 tableView 控制器
- 我使用 tableview 控制器作为其 rootview
控制器初始化的导航控制器 - 一个 collectionviewController,请注意,我也在此处为单元格注册了类。
- 一个 UITabBarController,我将导航控制器添加到作为第一个视图,将
collectionview 添加到作为第二个视图。 TabBarCOntorller 被设置为窗口的根视图。
Here is the code:
这是代码:
- (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
}
The tableview has a navbar button that presents the model view controller. The modal view controller has a button that takes the values from a textField and a datepicker and posts that to the default notification center with the values in the UserInfo dictionary. The tableView contorller subscribes to the notification, puts the UserInfo dictionary in a MutableArray and updates the table. That works fine.
tableview 有一个导航栏按钮,用于显示模型视图控制器。模态视图控制器有一个按钮,它从 textField 和 datepicker 中获取值,并将其与 UserInfo 字典中的值一起发布到默认通知中心。tableView 控制器订阅通知,将 UserInfo 字典放入 MutableArray 并更新表。这很好用。
The problem is with the CollectionVIew. The collectionVIew receives the notification and calls a target method to handle the notification. I am trying to take the UserInfo dictionary from the notification and add a new cell to the collectionView.
问题在于 CollectionVIew。collectionVIew 接收通知并调用目标方法来处理通知。我正在尝试从通知中获取 UserInfo 字典并向 collectionView 添加一个新单元格。
BUT...
但...
When the collectionview receives the notification I get the error:
当 collectionview 收到通知时,我收到错误消息:
'Invalid update: invalid number of items in section 0. The number of items contained in an existing section after the update (1) must be equal to the number of items contained in that section before the update (1), plus or minus the number of items inserted or deleted from that section (1 inserted, 0 deleted) and plus or minus the number of items moved into or out of that section (0 moved in, 0 moved out).'
'无效更新:第 0 节中的项目数无效。更新 (1) 后现有节中包含的项目数必须等于更新前该节中包含的项目数 (1),加上或减去从该部分插入或删除的项目数(插入 1 个,删除 0 个),加上或减去移入或移出该部分的项目数(0 个移入,0 个移出)。
Here is the code for the CollectionView:
这是 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
I would so much appreciate anyone who can point anything out here.....
我非常感谢任何能在这里指出任何事情的人......
回答by Timothy Moose
It's a bug with using insertItemsAtIndexPaths
on an empty UICollectionView
. Just do this:
它是用一个错误insertItemsAtIndexPaths
的空白UICollectionView
。只需这样做:
if (self.birthdays.count == 1) {
[self.collectionView reloadData];
} else {
[self.collectionView insertItemsAtIndexPaths:@[[NSIndexPath indexPathForItem: (self.birthdays.count -1) inSection:0]]];
}
(Can't believe this is still broken in iOS8.)
(不敢相信这在 iOS8 中仍然被破坏。)
回答by Michael Su
It is a bug in UICollectionView even in iOS 11, but not exist in UITableView, very easy to reproduce: Assume the following SWIFT code is working:
即使在 iOS 11 中,它也是 UICollectionView 中的一个错误,但在 UITableView 中不存在,很容易重现:假设以下 SWIFT 代码正在运行:
addItemInDataSource()
collectionView!.insertItems(at: [IndexPath(item: position, section: 0)])
Change it to:
将其更改为:
collectionView!.reloadData() // <-- This new line will make UICollection crash...
addItemInDataSource()
collectionView!.insertItems(at: [IndexPath(item: position, section: 0)])
It will crash...
会崩溃...
UICollectionView will lose track of the original number of items in some condition, just add a dummy line before insertItems command will avoid this kind of crash:
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)])
Hope it could help in your situation.
希望它可以帮助您的情况。
回答by vmeyer
I had the same error when I've inserted elements in a collectionView with only one section.
当我在只有一个部分的 collectionView 中插入元素时,我遇到了同样的错误。
I've done the Timothy fix, but it was not enough. In fact, the collectionView had already refreshed its data when I tried to insert new elements. Using collectionView numberOfItemsInSection solves the problem.
我已经完成了 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];
回答by p-sun
In my case, numberOfItemsInSection
was being called twice from self.performBatchUpdates
(once for the BEFORE number, once for the AFTER number) because I was trying to insert something into the collectionView before the view controller had finished loading.
在我的情况下,numberOfItemsInSection
被调用两次self.performBatchUpdates
(一次是 BEFORE 编号,一次是 AFTER 编号),因为我试图在视图控制器完成加载之前将某些内容插入到 collectionView 中。
The solution is to add guard isViewLoaded else { return }
before calling self.performBatchUpdates
.
解决方法是guard isViewLoaded else { return }
在调用之前添加self.performBatchUpdates
。
回答by Naloiko Eugene
As for me the issue was connected to the Index Set - where to insert sections. 1. I've inserted Sections 0..<10 (10 sections) 2. I was trying to insert Sections to IndexSet 9..<19. But appending the new objects to the end of the dataSource array. Correct Index set should be (10..<20)
至于我,问题与索引集有关 - 在何处插入部分。1. 我已经插入了 Sections 0..<10(10 个部分) 2. 我试图将 Sections 插入到 IndexSet 9..<19。但是将新对象附加到 dataSource 数组的末尾。正确的索引集应该是 (10..<20)
Thus the last section was still expecting the number of rows from Section 9. But in data source - it was at index 19.
因此,最后一节仍然期望来自第 9 节的行数。但在数据源中 - 它位于索引 19。
collectionView?.insertSections(IndexSet(integersIn: startIndex..<(startIndex + series.count)))
collectionView?.insertSections(IndexSet(integersIn: startIndex..<(startIndex + series.count)))
回答by Giuseppe Mazzilli
I'm not practice with Obj-c so I write my answer in Swift.
我没有用 Obj-c 练习,所以我用 Swift 写了我的答案。
supposing birthdays is an array of dates as 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)] )
}
}
You should avoid this error:
你应该避免这个错误:
When the collectionview receives the notification I get the error:
当 collectionview 收到通知时,我收到错误消息:
'Invalid update: invalid number of items in section 0. The number of items contained in an existing section after the update (1) must be equal to the number of items contained in that section before the update (1), plus or minus the number of items inserted or deleted from that section (1 inserted, 0 deleted) and plus or minus the number of items moved into or out of that section (0 moved in, 0 moved out).'
'无效更新:第 0 节中的项目数无效。更新 (1) 后现有节中包含的项目数必须等于更新前该节中包含的项目数 (1),加上或减去从该部分插入或删除的项目数(插入 1 个,删除 0 个),加上或减去移入或移出该部分的项目数(0 个移入,0 个移出)。
回答by george_luofz
- As the collectionView reload data asynchronously, when you call [collectionView reloadData] method, the dataSource may be reloaded after a while. you can call [collectionView layoutIfNeeded] to force the collectionView reload at once.
- And when you call [collectionView insertItemsAtIndexPaths:@[indexPaths...]] upon [collectionView reloadData], sometimes the number Of items maybe not equal, you can call [collectionView numberOfItemsInSection:] as a trick before call [collectionView insertItemsAtIndexPaths:@[indexPaths...]].
- when the dataSource is empty, the insertion will crash, you should call [collectionView reloadData] instead of [collectionView numberOfItemsInSection:]
- 由于collectionView异步重载数据,当你调用[collectionView reloadData]方法时,dataSource可能会在一段时间后重载。您可以调用 [collectionView layoutIfNeeded] 强制立即重新加载 collectionView。
- 当您在 [collectionView reloadData] 上调用 [collectionView insertItemsAtIndexPaths:@[indexPaths...]] 时,有时项目的数量可能不相等,您可以在调用 [collectionView insertItemsAtIndexPaths:@[] 之前调用 [collectionView numberOfItemsInSection:] 作为技巧索引路径...]]。
- 当 dataSource 为空时,插入会崩溃,你应该调用 [collectionView reloadData] 而不是 [collectionView numberOfItemsInSection:]
As the above, to avoid this issue, you can do this:
如上所述,为了避免这个问题,你可以这样做:
1. when reload collectionView:
[self.collectionView reloadData];
[self.collectionView layoutIfNeeded];
2. when invoke the batch updates:
[self.collectionView performBatchUpdates:^{
...
[self.dataSource addDataSource:moreDatas];
NSMutableArray *addIndexPaths;
...
if (self.dataSource.count == 1 ||
[self.collectionView numberOfItemsInSection:0] == self.dataSource.count){
[self.collectionView reloadData];
}else{
[self.collectionView insertItemsAtIndexPaths:addIndexPaths];
}
} completion:^(BOOL finished) {}];