UICollectionView shows wrong number of items - ios

I am using a UICollectionView to display a horizontally scrollable list of items. Unfortunately this is not working the way I expect it - specifically it displays the wrong number of items.
My data source for the collection view is a simple NSArray of NSStrings and my UICollectionViewDataSource methods look like this:
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
{
NSMutableArray *tagNames = self.tags;
return tagNames.count;
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
NSMutableArray *tagNames = self.tags;
TokenCollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:kTokenCellID
forIndexPath:indexPath];
NSString *associatedTagName = [tagNames objectAtIndexedSubscript:indexPath.item];
//configure cell...
return cell;
}
However the UICollectionView is displaying exactly one less item then what is returned from numberOfItemsInSection.
I would be happy about any advice regarding this issue and how to solve it!
Thanks in advance!
Edit as requested by posted comments
-(void)viewDidLoad
{
[super viewDidLoad];
//smaller font size
[self.navigationItem.rightBarButtonItem setTitleTextAttributes:#{NSFontAttributeName : [UIFont systemFontOfSize:14] }
forState:UIControlStateNormal];
}
Data source (populated for testing):
self.tags = #[#"First Tag", #"Second Tag", #"Third Tag", #"Fourth Tag", #"Fith Tag"];

Related

How to fix UICollectionView header issue?

I'm facing an issue where the header in a collection view is hiding part of the cell when an index title is selected.
It's important to indicate that the headers on the collection view are sticky.
For example, when "0" is selected, the following appears:
While I expect the following:
Notice that on the first screenshot the "0" section header is hiding the first line of the cell (which is "---- Cell Title ----").
I uploaded a sample app to Github that clearly demonstrates this issue. See https://github.com/yoasha/CollectionViewDemo
To reproduce, run the project with any iPhone simulator, select the "Collection View" option on the main page, and then tap the "0" or "1" index title.
For comparison with table view behavior, the sample app allows to select "Table View" on the main page. After that, selecting any index title will show a proper behavior of the table view in contrast to the collection view.
Any suggestions on how to fix this?
Following is the entire implementation of my collection view, which is also available on the above Github link.
#implementation MyCollectionViewController
static NSString * const reuseIdentifier = #"Cell";
- (void)viewDidLoad {
[super viewDidLoad];
UICollectionLayoutListConfiguration *config = [[UICollectionLayoutListConfiguration alloc] initWithAppearance:UICollectionLayoutListAppearancePlain];
config.headerMode = UICollectionLayoutListHeaderModeSupplementary;
self.collectionView.collectionViewLayout = [UICollectionViewCompositionalLayout layoutWithListConfiguration:config];
// Register cell classes
[self.collectionView registerClass:[UICollectionViewListCell class] forCellWithReuseIdentifier:reuseIdentifier];
[self.collectionView registerClass:[MyCollectionReusableViewHeader class]
forSupplementaryViewOfKind:UICollectionElementKindSectionHeader
withReuseIdentifier:#"header"];
}
#pragma mark <UICollectionViewDataSource>
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView {
return self.dataModel.count;
}
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
return self.dataModel.count;
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:reuseIdentifier forIndexPath:indexPath];
UIListContentConfiguration *config = [((UICollectionViewListCell *)cell) defaultContentConfiguration];
config.text = #"----- Cell Title -----\nsome info\nsome info\nsome info";
cell.contentConfiguration = config;
return cell;
}
- (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath {
if ([kind isEqualToString:UICollectionElementKindSectionHeader]) {
MyCollectionReusableViewHeader *header = [self.collectionView dequeueReusableSupplementaryViewOfKind:UICollectionElementKindSectionHeader
withReuseIdentifier:#"header"
forIndexPath:indexPath];
header.text = self.dataModel[indexPath.section];
return header;
}
return nil;
}
- (NSArray<NSString *> *)indexTitlesForCollectionView:(UICollectionView *)collectionView {
return self.dataModel;
}
- (NSIndexPath *)collectionView:(UICollectionView *)collectionView indexPathForIndexTitle:(NSString *)title atIndex:(NSInteger)index {
return [NSIndexPath indexPathWithIndex:index];
}
#end
self.dataModel is NSArray<NSString *> * assigned with #[#"0", #"1", #"2", #"3", #"4"]
I also found a report of this issue at UICollectionView: How can you make the index fast-scroll to the section header instead of the first section item?

How to avoid UICollectionView repeating cells?

Hello I have a UICollectionView. I load data from a NSMutableArray and it has 4 objects. But my problem is its repeating the same cells again.
`
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
return mutArrayArtists.count;
}
- (NSInteger)numberOfSectionsInCollectionView: (UICollectionView *)collectionView {
return 2;
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath{
static NSString *identifier = #"Cell";
UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:identifier forIndexPath:indexPath];
artist = [mutArrayArtists objectAtIndex:indexPath.row];
UIImageView *recipeImageView = (UIImageView *)[cell viewWithTag:100];
recipeImageView.image = artist.artistImage;
UILabel *lblArtistname=(UILabel *)[cell viewWithTag:102];
lblArtistname.text=artist.artistName;
UILabel *lblSongCount=(UILabel *)[cell viewWithTag:101];
lblSongCount.text=artist.numberofSongs;
return cell;
}
`
But the result is like this
How can I avoid this? Please help me
Thanks
- (NSInteger)numberOfSectionsInCollectionView: (UICollectionView *)collectionView {
return 1; //instead of 2
}
The section here does not mean the number of columns. It means that the collection view will contain 2 sections vertically above each others
This is the problem:
- (NSInteger)numberOfSectionsInCollectionView: (UICollectionView *)collectionView {
return 2;
}
You have two sections to the collection view, and in your cell provider, you don't distinguish the sections. Change it to say return 1; (assuming you do want just one section in the collection) or update the cellForItemAtIndexPath function (by inspecting indexPath.section) to split the sections out as you intend.
This might be helpful
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView
{
return 1;
}

UICollectionView allowsMultipleSelelections not working

I am trying to make some photoPicker with CollectionView.
Have
allowsMultipleSelection = YES
Using following method
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath {
selectedPictures = [NSMutableArray array];
[selectedPictures addObject:[imagesArray objectAtIndex:indexPath.item]];
NSLog(#"Selected list:\n %#", selectedPictures);
NSLog(#"Objects in Array %i", selectedPictures.count);
}
While I am selecting cells, it's always adding to MutableArray only one object according it's indexPath. What could be an issue?
Why don't u keep the selectedPictures as a member variable
in your code
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath {
selectedPictures = [NSMutableArray array]; //keep on creation the new array on each selection
[selectedPictures addObject:[imagesArray objectAtIndex:indexPath.item]]; //adding the selected images means single image
NSLog(#"Selected list:\n %#", selectedPictures);
NSLog(#"Objects in Array %i", selectedPictures.count);
}
try this
put his in viewDidLoad
- (void)viewDidLoad
{
selectedPictures = [[NSMutableArray alloc]init]; //initilise hear
}
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath {
// selectedPictures = [NSMutableArray array]; //keep on creation the new array on each selection
[selectedPictures addObject:[imagesArray objectAtIndex:indexPath.item]]; //adding the selected images means single image to already initialised array
NSLog(#"Selected list:\n %#", selectedPictures);
NSLog(#"Objects in Array %i", selectedPictures.count);
}
Hope this helps u .. :)
it may be caused by not calling super. While the documentation for UICollectionReusableView fails to mention this, the documentation for UITableViewCell, which has the same method, does.
- (void)prepareForReuse
{
[super prepareForReuse]
// Your code here.
}
Old Answer:
This may be a bug with the UICollectionView.
What's happening is cells that were previously selected are being reused and maintain the selected state. The collection view isn't setting selected to "NO".
The solution is to reset the the selected state in prepareForReuse of the cell:
- (void)prepareForReuse
{
self.selected = NO;
}
If the reused cell is selected, the collection view will set selected to "YES" after prepareForReuse is called.
This is something the UICollectionView should be doing on it's own. Thankfully the solution is simple. Unfortunately I spent a ton of time working around this bug by tracking my own select state. I didn't realize why it was happening until I was working on another project with smaller cells.
Also Try this
I'm not seeing why this would take place. I do not believe the issue is the use of row vs item, though you really should use item. I can imagine, though, if your collection view has more than one section, that only looking at row/item but ignoring section would be a problem (i.e. it would select the same item number in every section).
To cut the Gordian knot, I'd suggest saving the NSIndexPath of the selected item, and then using that for the basis of comparison. That also makes it easy to render an optimization in didSelectItemAtIndexPath. Anyway, first define your property:
#property (nonatomic, strong) NSIndexPath *selectedItemIndexPath;
And then implement cellForItemAtIndexPath and didSelectItemAtIndexPath:
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *cellIdentifier = #"Cell";
CollectionCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:cellIdentifier forIndexPath:indexPath];
cell.imageView.image = ...
if (self.selectedItemIndexPath != nil && [indexPath compare:self.selectedItemIndexPath] == NSOrderedSame) {
cell.imageView.layer.borderColor = [[UIColor redColor] CGColor];
cell.imageView.layer.borderWidth = 4.0;
} else {
cell.imageView.layer.borderColor = nil;
cell.imageView.layer.borderWidth = 0.0;
}
return cell;
}
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath
{
// always reload the selected cell, so we will add the border to that cell
NSMutableArray *indexPaths = [NSMutableArray arrayWithObject:indexPath];
if (self.selectedItemIndexPath)
{
// if we had a previously selected cell
if ([indexPath compare:self.selectedItemIndexPath] == NSOrderedSame)
{
// if it's the same as the one we just tapped on, then we're unselecting it
self.selectedItemIndexPath = nil;
}
else
{
// if it's different, then add that old one to our list of cells to reload, and
// save the currently selected indexPath
[indexPaths addObject:self.selectedItemIndexPath];
self.selectedItemIndexPath = indexPath;
}
}
else
{
// else, we didn't have previously selected cell, so we only need to save this indexPath for future reference
self.selectedItemIndexPath = indexPath;
}
// and now only reload only the cells that need updating
[collectionView reloadItemsAtIndexPaths:indexPaths];
}
Check also this
Your observation is correct. This behavior is happening due to the reuse of cells. But you dont have to do any thing with the prepareForReuse. Instead do your check in cellForItem and set the properties accordingly. Some thing like..
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:#"cvCell" forIndexPath:indexPath];
if (cell.selected) {
cell.backgroundColor = [UIColor blueColor]; // highlight selection
}
else
{
cell.backgroundColor = [UIColor redColor]; // Default color
}
return cell;
}
-(void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath {
UICollectionViewCell *datasetCell =[collectionView cellForItemAtIndexPath:indexPath];
datasetCell.backgroundColor = [UIColor blueColor]; // highlight selection
}
-(void)collectionView:(UICollectionView *)collectionView didDeselectItemAtIndexPath:(NSIndexPath *)indexPath {
UICollectionViewCell *datasetCell =[collectionView cellForItemAtIndexPath:indexPath];
datasetCell.backgroundColor = [UIColor redColor]; // Default color
}
I solved my issue;
The problem was very simple, I should have initialise MutableArray not in the Method didSelectItemAtIndexPath, but in the ViewDidLoad. Now it adding pictures one by one

Collection View old icon doesn't get deselected

And I have to ask this as well. Been looking through the code for hours now and tried everything, but why does my old picture not get deselected? The user should be able to select one icon only. The selected icon is saved in Core Database. So this icon is also preselected when opening this view. However this item doesn't get deselected when he selects a new icon..why?
#import "IconSelectionCollectionViewController.h"
#import "IconSelectionCell.h"
#interface IconSelectionCollectionViewController ()
#property (nonatomic, strong) NSArray *icons;
#end
#implementation IconSelectionCollectionViewController
#synthesize mainCategory = _mainCategory;
#pragma mark Initialize model
- (void)setMainCategory:(MainCategory *)mainCategory
{
//TODO
_mainCategory = mainCategory;
self.title = mainCategory.name;
}
#pragma mark View setup
- (void)viewDidLoad
{
[super viewDidLoad];
[self.collectionView registerClass:IconSelectionCell.class forCellWithReuseIdentifier:#"IconCell"];
// Do any additional setup after loading the view.
self.icons = [NSArray arrayWithObjects:#"DefaultIcon", #"Car", #"Diploma", #"Earth", #"Flight", #"Home", #"Pen", #"Scooter", #"Ship", #"Train", nil];
self.collectionView.allowsSelection = YES;
self.collectionView.allowsMultipleSelection = NO;
}
#pragma mark Data source delegate methods
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
{
return self.icons.count;
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *identifier = #"IconCell";
IconSelectionCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:identifier forIndexPath:indexPath];
cell.iconImageView.image = [UIImage imageNamed:[self.icons objectAtIndex:indexPath.row]];
if([self.mainCategory.icon isEqualToString:[self.icons objectAtIndex:indexPath.row]]){
cell.selected = YES;
}
return cell;
}
-(NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView
{
return 1;
}
#pragma mark Collection View Delegate methods
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath
{
self.mainCategory.icon = [self.icons objectAtIndex:indexPath.row];
}
#pragma mark – UICollectionViewDelegateFlowLayout
- (CGSize)collectionView:(UICollectionView *)collectionView layout:
(UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath {
CGSize itemSize;
itemSize.height = 62;
itemSize.width = 62;
return itemSize;
}
- (UIEdgeInsets)collectionView:
(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout insetForSectionAtIndex:(NSInteger)section {
return UIEdgeInsetsMake(10, 10, 10, 10);
}
#end
I am not incredibly familiar with collection views, but they are very similar to table views and this is a problem that sometimes occurs with UITableviews, so I will work through a couple possible solutions.
One way to do this would be to call [self.collectionView reloadData] after the selection code. This should cause the collectionView to redraw the images, setting only one of them to be the selected image.
However, I'm not confident this would solve the problem, since dequeue reusable cells could cause the "selection" value to be set for the reused cell. So alternatively, I'd imagine you could grab both cells using UICollectionView's method cellForItemAtIndexPath: and then set their selected value in the way you set them in the main method. That may just work, but if it does not, try doing that and calling reload data again.

UITableView in UICollectionViewCell

I have a UICollectionView whose all cells contains a UITableView. My UICollectionView delegate / datasource methods are handled by a view controller (let's say TTCollectionViewController), and tableViews are handled directly by collection view cells (TTCollectionViewCell).
In order to change some label colors, I need to keep track of each selected rows on my tableViews so i did something like that :
//
// TTCollectionViewCell.m
//
[...]
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
// keep track of selected drugs
if (![self.selectedDrugIndexPaths containsObject:indexPath])
{
[self.selectedDrugIndexPaths addObject:indexPath];
}
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
TTTableViewCell *contentCell = [tableView dequeueReusableCellWithIdentifier:#"test"];
if (contentCell == nil)
contentCell = [[TTTableViewCell alloc] init];
[...]
// test
if ([self.selectedDrugIndexPaths containsObject:indexPath]) {
contentCell.drugLabel.textColor = [UIColor grayColor];
} else {
contentCell.drugLabel.textColor = [UIColor blackColor];
}
}
[...]
But here's the thing : with the dequeue reusable thing, my collectionView is messing up all my tracks (on scrolling, selected rows on table 1 become selected on table 3 for example).
I know it's a bad idea to don't use dequeuereusable (i don't even know if it's possible to not use it), but is there something similar that i can do ?
Do anyone have already implement this kind of case ?
Thx in advance.
EDIT :
Here's my cellForRow... methods of my collectionView as you asked for :
///
/// TTCollectionController.m
///
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
TTCollectionCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:#"cell" forIndexPath:indexPath];
cell.data = [self.data objectAtIndex:indexPath.row];
return cell;
}

Resources