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?
Related
I want to add load more cell in UICollectionview ? can anyone tell me how can i add load button ? i have created collectionview that works fine but i want to add Load more button in bottom of collection view cell like this
Here's my collection view
#pragma mark <UICollectionViewDataSource>
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView {
return 3;
}
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
switch (section) {
case 0: return 66;
case 1: return 123;
}
return 31;
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
NSUInteger row = [indexPath row];
NSUInteger count = [self.stockImages count];
if (row == count) {
//Add load more cell
}
DemoCellView *cell = [collectionView dequeueReusableCellWithReuseIdentifier:[DemoCellView reuseIdentifier] forIndexPath:indexPath];
// Configure the cell
cell.titleLabel.text = [NSString stringWithFormat:#"%ld", (long)indexPath.item + 1];
NSLog(#"%#", self.stockImages[indexPath.item % self.stockImages.count]);
cell.imageView.image = self.stockImages[indexPath.item % self.stockImages.count];
return cell;
}
#pragma mark <DemoLayoutDelegate>
- (void) collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath {
// Do something
// Insert new cell after clicking load more data
}
- (CGFloat)collectionView:(UICollectionView *)collectionView layout: (UICollectionViewLayout *)collectionViewLayout heightForHeaderInSection:(NSInteger)section {
return kFMHeaderFooterHeight;
}
- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout heightForFooterInSection:(NSInteger)section {
return kFMHeaderFooterHeight;
}
You can Add a footer
- (UICollectionReusableView *)collectionView:(JSQMessagesCollectionView *)collectionView
viewForSupplementaryElementOfKind:(NSString *)kind
atIndexPath:(NSIndexPath *)indexPath
{
if ([kind isEqualToString:UICollectionElementKindSectionFooter]) {
//load your footer you have registered earlier for load more
[super dequeueReusableSupplementaryViewOfKind:UICollectionElementKindSectionFooter
withReuseIdentifier:#“load more footer”
forIndexPath:indexPath];
}
return nil;
}
In your cellForItemAtIndexPath method you can check this:
-(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
ImageCollectionViewCell *cellImage;
AddMoreCollectionViewCell *addMoreCell;
if(indexPath.row < [_dataSource numberOfItems]){
cellImage = [collectionView dequeueReusableCellWithReuseIdentifier:ImageCollectionCellIdentifier
forIndexPath:indexPath];
[cellImage configureForMediaViewModel:[_dataSource mediaViewModelForItemIndex:indexPath.row] delegate:self];
return cellImage;
}else{
addMoreCell = [collectionView dequeueReusableCellWithReuseIdentifier:AddMoreCollectionCellIdentifier
forIndexPath:indexPath];
addMoreCell.delegate = self;
return addMoreCell;
}
}
where ImageCollectionViewCell is the main kind of cells and AddMoreCollectionViewCell is a cell with a plus ('+') symbol and other stuff.
With this method, AddMoreCollectionViewCell always add at end of your collection view.
Hope it helps!
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"];
I tried to use two UICollectionViews in a single ViewController. Application crashed with the following error
reason: 'could not dequeue a view of kind: UICollectionElementKindCell with identifier ItemCell - must register a nib or a class for the identifier or connect a prototype cell in a storyboard
This is my implementation how looks like
HomeViewController.h
UICollectionView *colloctionViewItems;
UICollectionView *colloctionViewCats;
#property (nonatomic, retain) IBOutlet UICollectionView *colloctionViewItems;
#property (nonatomic, retain) IBOutlet UICollectionView *colloctionViewCats;
HomeViewController.m
#synthesize colloctionViewCats, colloctionViewItems;
- (void)viewDidLoad{
[super viewDidLoad];
// Do any additional setup after loading the view from its nib.
// read ItemList plist
AppDelegate *appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
NSDictionary *dicCats = [appDelegate readPlistIntoDictWithFile:#"CatagoryList.plist"];
// arrCategories
arrCats = [[NSArray alloc]initWithArray:[dicCats objectForKey:#"Catagories"]];
[self.colloctionViewItems registerClass:[ItemCell class] forCellWithReuseIdentifier:#"ItemCell"];
[self.colloctionViewCats registerClass:[CatCell class] forCellWithReuseIdentifier:#"CatCell"];
[colloctionViewItems reloadData];
[colloctionViewCats reloadData];
}
#pragma mark for Collection View Delegate methods
- (NSInteger)numberOfSectionsInCollectionView: (UICollectionView *)collectionView
{
return 1;
}
- (NSInteger)collectionView:(UICollectionView *)view numberOfItemsInSection:(NSInteger)section
{
if(view == colloctionViewItems){
return [arrCats count];
}
if(view == colloctionViewCats){
return [arrCats count];
}
return 0;
}
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout referenceSizeForHeaderInSection:(NSInteger)section
{
return CGSizeZero;
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)cv cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
ItemCell *iCell = [cv dequeueReusableCellWithReuseIdentifier:#"ItemCell" forIndexPath:indexPath];
CatCell *cCell = [cv dequeueReusableCellWithReuseIdentifier:#"CatCell" forIndexPath:indexPath];
if(cv == colloctionViewItems){
iCell.lblBtnTitle.text = [arrCats objectAtIndex:indexPath.row];
return iCell;
}
if(cv == colloctionViewCats){
cCell.lblBtnTitle.text = [arrCats objectAtIndex:indexPath.row];
return cCell;
}
return iCell;
}
If I removed one UICollectionView, all works fine. Any ideas please.
FYI: I have created Custom Collection Cell class as "ItemCell" and "CatCell" correctly (without using nibs)
It's because you dequeue both cell at the same time, even if the collection view hasn't contain the right cell. You should first check which collection view is asking about cell and after that dequeue the right one:
- (UICollectionViewCell *)collectionView:(UICollectionView *)cv cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
if(cv == colloctionViewItems){
// Item collection view contain item cell so you can safety dequeue it.
// if you try to dequeue CatCell it will fail this is why you had this issue before
ItemCell *iCell = [cv dequeueReusableCellWithReuseIdentifier:#"ItemCell" forIndexPath:indexPath];
iCell.lblBtnTitle.text = [arrCats objectAtIndex:indexPath.row];
return iCell;
}
if(cv == colloctionViewCats){
CatCell *cCell = [cv dequeueReusableCellWithReuseIdentifier:#"CatCell" forIndexPath:indexPath];
cCell.lblBtnTitle.text = [arrCats objectAtIndex:indexPath.row];
return cCell;
}
return nil;
}
I have confused with datasource of UIColectionView and I really need help here.
I have array with objects [#"1", #"2", #"3", #"4", #"5", #"6"]
So I need to display them
1 2 3
4 5 6
I have found this code
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView
{
return noOfItem/ noOfSection;
}
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
{
return noOfSection;
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"cellRecipe";
collectionCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:CellIdentifier forIndexPath:indexPath];
;
Employee *emp = [array_ objectAtIndex:indexPath.section * noOfSection + indexPath.row];
cell.name.text = emp.name;
cell.image.image = emp.thumbImg;
return cell;
}
but I think it is not good use noOfItem/ noOfSection
so for example if I have 20 objects and 3 items per section
I got this error
-[__NSArrayM objectAtIndex:]: index 20 beyond bounds [0 .. 19]'
because each my section has 3 items
In my opinion you aren't quite approaching UICollectionView in the correct way.
Sections are meant to be separate sections of content, not the number of rows.
Items in section is meant to return the number of items for a given section.
In your example I would do it like this:
/**
* Called after the controller’s view is loaded into memory.
*/
- (void)viewDidLoad
{
[super viewDidLoad];
self.employees = #[[[Employee alloc] init], [[Employee alloc] init], [[Employee alloc] init], [[Employee alloc] init], [[Employee alloc] init]];
}
/**
* Asks the data source for the number of sections in the collection view.
*
* #param collectionView An object representing the collection view requesting this information.
*
* #return The number of sections in collectionView.
*/
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView
{
return 1;
}
/**
* Asks the data source for the cell that corresponds to the specified item in the collection view.
*
* #param collectionView An object representing the collection view requesting this information.
* #param indexPath The index path that specifies the location of the item.
*
* #return A configured cell object. You must not return nil from this method.
*/
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"cellRecipe";
collectionCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:CellIdentifier forIndexPath:indexPath];
Employee *employee = self.items[indexPath.item];
cell.name.text = employee.name;
cell.image.image = employee.thumbImg;
return cell;
}
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
{
return self.items.count;
}
With reference to an array containing [#"1", #"2", #"3", #"4", #"5", #"6"]
You don't need this method :
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView
Now do this :
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
{
return [self.arrayName count];
}
Now you need to set the size of cell so that each row can have 3 items. You can either set the size of cell using storyboard or you can use this method:
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath
True. You don't really need numberOfSections... as you can solve this with sizing of cells (autolayout is your friend). If using the numberOfSectionsInCollectionView, you need to catch that the last section only has two items. You didn't include how you calculate noOfSection.
In the numberOfItems... method, you can do this:
if (section == noOfSection) {
return noOfItem % noOfSection; // Finding the remainder with modulo
} else {
return noOfItem / noOfSection;
}
I haven't tested this, but believe it should work.
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.