I have a problem I can't solve for some hours now...
I have multiple UICollectionViews with different numbers of cells and different cellsizes. The CollectionViews are created programmatically and the delegates and datasources are set.
The collectionviews are created like this:
UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
[layout setScrollDirection:UICollectionViewScrollDirectionHorizontal];
collectionViewOne = [[UICollectionView alloc] initWithFrame:CGRectMake(0,0,320,150) collectionViewLayout:layout];
[collectionViewOne setTag:99];
[collectionViewOne setDataSource:self];
[collectionViewOne setDelegate:self];
[collectionViewOne registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:#"cell"];
[collectionViewOne setBackgroundColor:bgColor];
[collectionViewOne setShowsHorizontalScrollIndicator:NO];
[collectionViewOne setBounces:YES];
[collectionViewOne setAlwaysBounceHorizontal:YES];
[collectionViewOne setScrollEnabled:YES];
[collectionViewOne setRestorationIdentifier:#"collectionViewOne"];
[scrollView addSubview:collectionViewOne];
My functions are like these:
- (NSInteger)collectionView:(UICollectionView*)collectionView numberOfItemsInSection:(NSInteger)section {
NSLog(#"restorationIdentifier: %#", collectionView.restorationIdentifier);
if (collectionView == collectionViewOne)
{
return 3;
}
else if (collectionView == collectionViewTwo)
{
return 4;
}
else
{
return 1;
}
}
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath {
NSLog(#"restorationIdentifier: %#", collectionView.restorationIdentifier);
if (collectionView == collectionViewOne)
{
return CGSizeMake(100, 150);
}
else if (collectionView == collectionViewTwo)
{
return CGSizeMake(200, 150);
}
else
{
return CGSizeMake(200, 150);
}
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
NSLog(#"restorationIdentifier: %#", collectionView.restorationIdentifier);
if (collectionView == collectionViewOne)
{
UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)];
imageView.image = [UIImage imageNamed:#"image.png"];
[cell addSubview:imageView];
}
else if (collectionView == collectionViewTwo)
{
//create cell of type 2
}
return cell;
}
In my log I get the following output (for example):
restorationIdentifier in numberOfItemsInSection: (null)
restorationIdentifier in sizeForItemAtIndexPath : (null)
restorationIdentifier cellForItemAtIndexPath: collectionViewOne
collectionViewOne is the restorationIdentifier on collectionViewOne. So why is it not recognized in the first two methods?
Ofcourse the result is that I get crashes because the cellsizes and number of cells in the different CollectionViews are not properly set. The error:
*** Assertion failure in -[UICollectionViewData validateLayoutInRect:], /SourceCache/UIKit_Sim/UIKit-2903.2/UICollectionViewData.m:341
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'UICollectionView recieved layout attributes for a cell with an index path that does not exist: <NSIndexPath: 0x8c31c10> {length = 2, path = 0 - 2}'
How can I fix this?
This is solved. The problem was the fact that I used the same UICollectionViewFlowLayout for both UICollectionViews. This caused the exception.
In the end I used different classes for the two controllers, this solved the problem of selecting the right CollectionView in the delegate's methods.
Thanks all for your input!
You could use the tag property of the collection view to identify which collection view is which. Like this:
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
NSLog(#"restorationIdentifier: %#", collectionView.restorationIdentifier);
if (collectionView.tag == collectionViewOneTag)
{
//create cell of type 1
}
else if (collectionView.tag == collectionViewTwoTag)
{
//create cell of type 2
}
return cell;
}
Where collectionViewOneTag and collectionViewTwoTag are integers that can be defined in code or in the xib file.
Hope that helps.
Related
I have two UICollectionViews in a single UIViewController. I am separating them by tag number so that I can use the data source and delegate methods for both. However, when I run the code it crashes with the Exception:
Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'UICollectionView received layout attributes for a cell with an index path that does not exist: <NSIndexPath: 0xc000000000200016> {length = 2, path = 0 - 1}'.
I looked this up in the forum and most people say you need to invalidate then reload the UIControllerView but in my case this is not working.
Anyone have idea how to fix this issue ?
Here is my code:
-(void)viewDidLoad {
self.socialMediaGrayIcons = [[NSMutableArray alloc] initWithObjects:[UIImage imageNamed:#"fb-gray.png"],
[UIImage imageNamed:#"twitter-gray.png"],
[UIImage imageNamed:#"insta-gray.png"],
[UIImage imageNamed:#"sms-gray.png"],
[UIImage imageNamed:#"email-gray.png"], nil];
// setup collection view
self.avatarCollectionView.tag = 200;
self.socialMediaCollectionView.tag = 201;
UINib *cellNib = [UINib nibWithNibName:#"NibCell" bundle:nil];
[self.avatarCollectionView registerNib:cellNib forCellWithReuseIdentifier:#"cvCell"];
[self.socialMediaCollectionView registerNib:cellNib forCellWithReuseIdentifier:#"smCell"];
// setup collection view layout
UICollectionViewFlowLayout *flowLayout = [[UICollectionViewFlowLayout alloc] init];
[flowLayout setItemSize:CGSizeMake(40, 40)];
[flowLayout setScrollDirection:UICollectionViewScrollDirectionHorizontal];
[self.avatarCollectionView setCollectionViewLayout:flowLayout];
[self.socialMediaCollectionView setCollectionViewLayout:flowLayout];
[self.avatarCollectionView reloadData];
[self.avatarCollectionView.collectionViewLayout invalidateLayout];
[self.socialMediaCollectionView reloadData];
[self.socialMediaCollectionView.collectionViewLayout invalidateLayout];
}
....
#pragma mark UICollectionView DataSource and Delegate mathods
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView
{
return 1;
}
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
{
if (collectionView.tag == 200)
{
return self.children.count;
} else if (collectionView.tag == 201){
return self.socialMediaGrayIcons.count;
}
return 1;
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
UICollectionViewCell *cell;
if (collectionView.tag == 200)
{
static NSString *cellIdentifier = #"cvCell";
cell = [collectionView dequeueReusableCellWithReuseIdentifier:cellIdentifier forIndexPath:indexPath];
Child *currentChild = [self.children objectAtIndex:indexPath.row];
UIImage *curImage = [UIImage imageWithData:currentChild.thumbnail];
UIImageView *thumbView = (UIImageView *)[cell viewWithTag:100];
if (curImage != nil)
{
[thumbView setImage:curImage];
}
} else if (collectionView.tag == 201){
static NSString *cellIdentifier = #"smCell";
cell = [collectionView dequeueReusableCellWithReuseIdentifier:cellIdentifier forIndexPath:indexPath];
UIImage *curImage = (UIImage*) [self.socialMediaGrayIcons objectAtIndex:indexPath.row];
UIImageView *thumbView = (UIImageView *)[cell viewWithTag:101];
if (curImage != nil)
{
[thumbView setImage:curImage];
}
}
return cell;
}
Taking #Paulw's good advice looks like this:
#property(weak,nonatomic) IBOutlet UICollectionView *collectionViewA;
#property(weak,nonatomic) IBOutlet UICollectionView *collectionViewB;
Your datasource methods have to be religious about always dividing in two branches of a conditional based on the collection view they were passed, and always using one datasource array in one and the other in the other.
You can enforce this religion by always getting your datasource via a convenience method, like this...
- (NSArray *)datasourceForCollectionView:(UICollectionView *)collectionView {
if (collectionView == self.collectionViewA) {
return self.children;
} else { // NOTICE - no else-if, there's no other valid condition
return self.socialMediaGrayIcons;
}
}
Use it everywhere, as in...
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
return [self datasourceForCollectionView:collectionView].count;
}
I'm simply trying to call a method with a custom UICollectionViewCell and it's crashing with SIGABRT
-(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
if([_leaderboardDictionary count] > 0 && indexPath.row == 0)
{
return [self insertLeaderBoardCellAtIndexPath:indexPath];
}
else
{
return [self insertDetailGraphicCellAtIndexPath:indexPath];
}
}
-(CampaignDetailGraphic2Cell *)insertDetailGraphicCellAtIndexPath:(NSIndexPath *)indexPath
{
CampaignDetailGraphic2Cell *cell = (CampaignDetailGraphic2Cell *)[_topCollectionView dequeueReusableCellWithReuseIdentifier:#"detailWebCell" forIndexPath:indexPath];
if(cell == nil)
{
if(is3_5Inches)
{
cell = [[CampaignDetailGraphic2Cell alloc] initWithFrame:CGRectMake(0, 0, 300, 172)];
}
else
if(is4Inches)
{
cell = [[CampaignDetailGraphic2Cell alloc] initWithFrame:CGRectMake(0, 0, 300, 220)];
}
else
if(isIphone6)
{
cell = [[CampaignDetailGraphic2Cell alloc] initWithFrame:CGRectMake(0, 0, 355, 290)];
}
else
if(isIphone6Plus)
{
cell = [[CampaignDetailGraphic2Cell alloc] initWithFrame:CGRectMake(0, 0, 394, 356)];
}
}
[cell setupCell];
...
}
This method exists within my CampaignDetailGraphic2Cell class.
Any idea how I can call methods and set properties of a cell ... ?
You shouldn't create UICollectionViewCells with alloc init. You need to register a class or nib file when you set up the collection and then dequeue the cell. Once you do that you can call the methods on your cell.
ie
[self.collectionView registerClass:[CampaignDetailGraphic2Cell class] forCellWithReuseIdentifier:#"CampaignCell"];
then
-(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
CampaignDetailGraphic2Cell* cell = (CampaignDetailGraphic2Cell*)[collectionView dequeueReusableCellWithReuseIdentifier:#"CampaignCell" forIndexPath:indexPath];
...
return cell;
}
I'm very new to UICollectionViews. I'm making a universal app. What I want is that on the iPad there are 2 cells on each row. But on the iPhone there is only one cell on each row. I was able to get the iPad version set correctly. But I can't get my head around the iPhone settings.
Here are the settings so far. And has you can see on my storyboard it looks oké. But when I run it I see that there are still two cells on each row.
Can someone help me with this ?
Kind regards
- (void)viewDidLoad
{
[super viewDidLoad];
[self.collectionView registerClass:[CVCell class] forCellWithReuseIdentifier:#"cvCell"];
// Configure layout
UICollectionViewFlowLayout *flowLayout = [[UICollectionViewFlowLayout alloc] init];
[flowLayout setItemSize:CGSizeMake(100, 100)];
[flowLayout setScrollDirection:UICollectionViewScrollDirectionHorizontal];
[self.collectionView setCollectionViewLayout:flowLayout];
}
-(void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
deviceModel = (NSString*)[UIDevice currentDevice].model;
NSLog(#"device is = %#",deviceModel);
}
-(NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView {
if ([deviceModel isEqualToString:#"iPhone Simulator"]) {
return 1;
}else
{
return 2;
}
}
-(NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
return 3;
}
-(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
// Setup cell identifier
static NSString *cellIdentifier = #"cvCell";
/* Uncomment this block to use subclass-based cells */
CVCell *cell = (CVCell *)[collectionView dequeueReusableCellWithReuseIdentifier:cellIdentifier forIndexPath:indexPath];
if (indexPath.section == 0) {
cell.backgroundColor = [UIColor redColor];
}
if (indexPath.section == 1) {
cell.backgroundColor = [UIColor blueColor];
}
cell.titleLabel.text = [NSString stringWithFormat:#"item %ld",(long)indexPath.section];
// Return the cell
return cell;
}
Check this code which will give you one item in iPhone cell and two item in iPad..
When I perform [self.collectionView reloadData];, only my first item is reloaded.
Even though - (NSInteger)collectionView:(UICollectionView *)view numberOfItemsInSection:(NSInteger)section; is called and stores, for example, two items, then (UICollectionViewCell *)collectionView:(UICollectionView *)cv cellForItemAtIndexPath:(NSIndexPath *)indexPath is only called once for the first item.
I have verified that in numberOfItemsInSection the bounds are frame's sizes are big enough to store at least three of my items.
numberOfItemsInSection:2 frame: {{0, 0}, {400, 150}} and bounds {{0, 0}, {400, 150}}
And every UICollectionViewCell is {0, 0}, {400,45} and are stored vertically, as specified in the init method.
I have read there are several bugs with UICollectionView and ios7 but none of the previously described solutions worked for my case. I have already tried with reloadItemsAtIndexPaths and reloadSections but that did not work and sometimes gave me exceptions.
I have also tired to programatically add the items with no success:
[self.collectionView insertItemsAtIndexPaths:#[[NSIndexPath indexPathForItem: (self.currentPeripherals.count -1) inSection:0]]];
This is how my UICollectionViewController looks like:
- (instancetype)init
{
UICollectionViewFlowLayout *aFlowLayout = [[UICollectionViewFlowLayout alloc] init];
[aFlowLayout setItemSize:CGSizeMake(400, 150)];
[aFlowLayout setScrollDirection:UICollectionViewScrollDirectionVertical];
self = [self initWithCollectionViewLayout:aFlowLayout];
if (self) {
self.view.frame=CGRectMake(0,0,400,150);
self.view.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
self.collectionView.backgroundView=[[UIView alloc] initWithFrame:CGRectZero];;
self.view.layer.backgroundColor=[UIColor clearColor].CGColor;
self.collectionView.backgroundColor=[UIColor clearColor];
self.collectionView.delegate = self;
self.collectionView.dataSource = self;
}
return self;
}
- (void)sharedInit {
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(handleBTPOSHostDidUpdatePeripheralsNotification:) name:BTPOSHostDidUpdatePeripheralsNotification object:nil];
}
-(NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView {
return 1;
}
- (NSInteger)collectionView:(UICollectionView *)view numberOfItemsInSection:(NSInteger)section
{
return self.currentPeripherals.count;
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)cv cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
DeviceCell *deviceCell = [cv dequeueReusableCellWithReuseIdentifier:CellID forIndexPath:indexPath];
if(!deviceCell.label)
{
[deviceCell prepareForReuse];
PeripheralTracking *peripheral=self.currentPeripherals[indexPath.item];
[deviceCell configureLabelWithPeripheralTracking:peripheral forItem:indexPath.item];
[deviceCell.contentView addSubview:deviceCell.label];
}
return deviceCell;
}
- (void)handleBTPOSHostDidUpdatePeripheralsNotification:(NSNotification *)notification
{
NSUInteger oldCount = self.currentPeripherals.count;
NSDictionary *trackedPeripherals = notification.userInfo[BTPOSHostDidUpdatePeripheralsNotificationPeripheralsKey];
[self sortPeripheralsDictionary:trackedPeripherals];
[self.collectionView reloadData];
}
And this is how my peripheral devices are sorted:
-(void)sortPeripheralsDictionary:(NSDictionary*)peripheralsDictionary
{
NSMutableArray *dictValues = [[peripheralsDictionary allValues] mutableCopy];
if(dictValues.count>1)
{
[dictValues sortUsingComparator: (NSComparator)^(PeripheralTracking *a, PeripheralTracking *b)
{
NSNumber *RSSI1 = 0;
NSNumber *RSSI2 = 0;
NSComparisonResult result = 0;
if(a.averageRSSI)
{
PeripheralTrackingRSSI doubleRSSIa = a.averageRSSI;
RSSI1 = [NSNumber numberWithDouble:doubleRSSIa];
}
if(b.averageRSSI)
{
PeripheralTrackingRSSI doubleRSSIb = b.averageRSSI;
RSSI2 = [NSNumber numberWithDouble:doubleRSSIb];
}
if(RSSI1 && RSSI2)
{
result = [RSSI2 compare:RSSI1];
}
return result;
}
];
}
self.currentPeripherals = dictValues;
}
You're setting the height of each cell to 150:
[aFlowLayout setItemSize:CGSizeMake(400, 150)]
Try changing that to 45:
[aFlowLayout setItemSize:CGSizeMake(400, 45)]
Or you can implement collectionView:layout:sizeForItemAtIndexPath: if your cell size varies from cell to cell. The reason you're only seeing one call to collectionView:cellForItemAtIndexPath: is that your collection view only has room to display one cell. If you scroll it, you should see the other one.
Try using this :
- (UICollectionViewCell *)collectionView:(UICollectionView *)cv cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
KKMDeviceCell *deviceCell = [cv dequeueReusableCellWithReuseIdentifier:kCellID forIndexPath:indexPath];
if(!deviceCell.label)
{
[deviceCell prepareForReuse];
[deviceCell.contentView addSubview:deviceCell.label];
}
PeripheralTracking *peripheral=self.currentPeripherals[indexPath.item];
[deviceCell configureLabelWithPeripheralTracking:peripheral forItem:indexPath.item];
return deviceCell;
}
I have an UICollectionView with a header of size non-zero. It seems whenever insertItemsAtIndexPaths is called, if the header is on screen, the program crashes with the following message:
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** setObjectForKey: object cannot be nil (key: <_UICollectionViewItemKey: 0xa38c910> Type = SV Kind = UICollectionElementKindSectionHeader IndexPath = <NSIndexPath 0xa38c7c0> 2 indexes [0, 0])'
When the header size is zero, or when the header is not on screen, calling insertItemsAtIndexPaths works fine. Does anyone know how to fix this? Thanks!
The class is a sub class of UICollectionViewController. Here is the code related to UICollectionView:
- (id)init {
// Collection view layout
UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
layout.itemSize = CGSizeMake(100, 100);
layout.headerReferenceSize = CGSizeMake(320, 50); // Left for the switch
layout.minimumInteritemSpacing = 5;
layout.minimumLineSpacing = 5;
layout.scrollDirection = UICollectionViewScrollDirectionVertical;
layout.sectionInset = UIEdgeInsetsMake(5, 5, 5, 5);
if (self = [super initWithCollectionViewLayout:layout]) {
// Collection view setup
[self.collectionView registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:#"ID"];
self.collectionView.frame = CGRectMake(0, -20, 320, 480-20-44-49);
self.collectionView.backgroundView = nil;
self.collectionView.backgroundColor = [UIColor clearColor];
...
}
return self;
}
Then I implemented two delegate methods:
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
return [blogs count];
}
and
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionViewArg cellForItemAtIndexPath:(NSIndexPath *)indexPath {
UICollectionViewCell *cell = [collectionViewArg dequeueReusableCellWithReuseIdentifier:#"ID" forIndexPath:indexPath];
/* Some code to get blogView */
[cell.contentView addSubview:blogView];
return cell;
}
The problem is there said in log that the header cannot be nil.So give some valid input there and you can avoid the crash.
Like if the header section needs no input give it an view with clearclolor
For implementhing the header section you must implement the following datasource method
- (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath;