I'm trying to get a simple collection view to work. Basically I would like the collection view to be exactly like a table in a database with rows and columns where I can scroll horizontally. Something like this:
Player 1 cell 1 cell 2 cell 3 cell 4 ... cell 20
Player 2 cell 1 cell 2 cell 3 cell 4 ... cell 20
<----------- scroll --------------------------->
I'm using a collection view so I can determine exactly what cell the player is clicking and it would seem that it would give me more flexibility overall as opposed to a tableview.
The problem is I cannot for the life of me get the collection view to display like I want. The cells are being stacked vertically... example (no way to make them stay in one row and scroll horizontally to see the other columns of the row. example...
Player 1 cell 1 cell 2 cell3
cell 4 cell 6 cell 7 ...
Player 2 cell 1 cell 2 cell4
cell 4...
I'm starting to think that maybe collection view is not a good api to use and maybe I should be simply using a tableview. Any help would be greatly appreciated.
Here's my attempt to do this. It works, but as this is my first attempt at a custom layout, I'm sure there are things that could be done to improve it. I made a subclass of UICollectionViewFlowLayout called MultipleLineLayout with this code:
#implementation MultpleLineLayout {
NSInteger itemWidth;
NSInteger itemHeight;
}
-(id)init {
if (self = [super init]) {
itemWidth = 60;
itemHeight = 60;
}
return self;
}
-(CGSize)collectionViewContentSize {
NSInteger xSize = [self.collectionView numberOfItemsInSection:0] * (itemWidth + 20) + 60;
NSInteger ySize = [self.collectionView numberOfSections] * (itemHeight + 20) ;
return CGSizeMake(xSize, ySize);
}
- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)path {
UICollectionViewLayoutAttributes* attributes = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:path];
NSInteger xValue;
if (path.row == 0) {
itemWidth = 120;
attributes.size = CGSizeMake(itemWidth,itemHeight);
xValue = itemWidth/2 + path.row * (itemWidth +20);
}else{
itemWidth = 60;
attributes.size = CGSizeMake(itemWidth,itemHeight);
xValue = itemWidth/2 + path.row * (itemWidth +20) + 60;
}
NSInteger yValue = itemHeight + path.section * (itemHeight +20);
attributes.center = CGPointMake(xValue, yValue);
return attributes;
}
-(NSArray*)layoutAttributesForElementsInRect:(CGRect)rect {
NSInteger minRow = (rect.origin.x > 0)? rect.origin.x/(itemWidth +20) : 0; // need to check because bounce gives negative values for x.
NSInteger maxRow = rect.size.width/(itemWidth +20) + minRow;
NSMutableArray* attributes = [NSMutableArray array];
for(NSInteger i=0 ; i < self.collectionView.numberOfSections; i++) {
for (NSInteger j=minRow ; j < maxRow; j++) {
NSIndexPath* indexPath = [NSIndexPath indexPathForItem:j inSection:i];
[attributes addObject:[self layoutAttributesForItemAtIndexPath:indexPath]];
}
}
return attributes;
}
This is the code in the view controller:
#import "ViewController.h"
#import "MultpleLineLayout.h"
#interface ViewController ()
#property (strong,nonatomic) UICollectionView *collectionView;
#property (strong,nonatomic) NSArray *theData;
#end
#implementation ViewController
- (void)viewDidLoad {
self.theData = #[#[#"Player 1",#"1",#"2",#"3",#"4",#"5",#"6",#"7",#"8",#"9",#"10",#"11",#"12",#"13",#"14",#"15",#"16",#"17",#"18",#"19",#"20"], #[#"Player 2",#"1",#"2",#"3",#"4",#"5",#"6",#"7",#"8",#"9",#"10",#"11",#"12",#"13",#"14",#"15",#"16",#"17",#"18",#"19",#"20"],#[#"Player 3",#"1",#"2",#"3",#"4",#"5",#"6",#"7",#"8",#"9",#"10",#"11",#"12",#"13",#"14",#"15",#"16",#"17",#"18",#"19",#"20"],#[#"Player 4",#"1",#"2",#"3",#"4",#"5",#"6",#"7",#"8",#"9",#"10",#"11",#"12",#"13",#"14",#"15",#"16",#"17",#"18",#"19",#"20"]];
MultpleLineLayout *layout = [[MultpleLineLayout alloc] init];
self.collectionView = [[UICollectionView alloc] initWithFrame:self.view.bounds collectionViewLayout:layout];
self.collectionView.dataSource = self;
self.collectionView.delegate = self;
layout.scrollDirection = UICollectionViewScrollDirectionHorizontal;
self.view.backgroundColor = [UIColor blackColor];
[self.view addSubview:self.collectionView];
[self.collectionView registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:#"Cell"];
[self.collectionView reloadData];
}
- (NSInteger)collectionView:(UICollectionView *)view numberOfItemsInSection:(NSInteger)section {
return [self.theData[section] count];
}
- (NSInteger)numberOfSectionsInCollectionView: (UICollectionView *)collectionView {
return [self.theData count];
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:#"Cell" forIndexPath:indexPath];
[cell.contentView.subviews.lastObject removeFromSuperview]; //removes the label from a dequeued cell so we can make a new one of the right size.
UILabel *label;
if (indexPath.row == 0) {
label = [[UILabel alloc]initWithFrame:CGRectMake(10, 15, 100, 30)];
}else{
label = [[UILabel alloc]initWithFrame:CGRectMake(10, 15, 40, 30)];
}
label.backgroundColor = [UIColor yellowColor];
label.textAlignment = NSTextAlignmentCenter;
label.text = self.theData[indexPath.section][indexPath.row];
[cell.contentView addSubview:label];
cell.backgroundColor = [UIColor orangeColor];
return cell;
}
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath {
UICollectionViewCell *item = [collectionView cellForItemAtIndexPath:indexPath];
NSLog(#"%#",[(UILabel *)item.contentView.subviews.lastObject text]);
}
So, my data is set up as an array of arrays, with each subarray being one player and his scores. This layout does seem to keep each players cells in a horizontal line. I made the first cell of each line bigger to hold the players name -- I'm not sure what's the best way to deal with this one larger cell. I've hard coded some numbers, but that's probably not the best way.
I welcome any suggestions for improvement, or fixes.
I haven't used this personally, but it seems to be recommended on other SO posts:
https://github.com/TheVole/HorizontalTable
Perhaps this might work better than a UICollectionView ?
(See this post also: How to make a horizontal UI table view on iPhone?)
Good luck!
UICollectionView is so powerfull and easy to extend because it adds a new feature: UICollectionViewFlowLayout. I'm afraid you cannot get perfect solution for what you need by default implement in Cocoa, but it is so esay to extend the UICollectionViewFlowLayout to a new layout in your case maybe a snake layout.
If any help, maybe you can see this github url: KMCollectionViewSnakeLayout to get some tips. It's not the style like:
Player 1 cell 1 cell 2 cell3
cell 4 cell 6 cell 7 ...
Player 2 cell 1 cell 2 cell4
cell 4...
but is a style like:
Player 1 cell 1 cell 2 | Player 2 cell 1 cell 2
cell3 cell 4 cell 6 | cell3 cell 4 cell6
cell 7 ... | cell7
Take a while to read it or the Apple Doc, you will got a lot!
Related
I want to create a UICollectionView that looks like this:
It won't be scrollable or editable. I'm currently wondering how to write the layout for this. I'm guessing it won't be a subclass of UICollectionViewFlowLayout. I can think of a number of ways, but was curious if there was any "right" way. The cells will be animate-able.
Should each row or column be its own section?
I've done something like what you want with a subclass of UICollectionViewFlowLayout. It looks like this,
#implementation MultpleLineLayout {
NSInteger itemWidth;
NSInteger itemHeight;
}
-(id)init {
if (self = [super init]) {
itemWidth = 80;
itemHeight = 80;
}
return self;
}
-(CGSize)collectionViewContentSize {
NSInteger xSize = [self.collectionView numberOfItemsInSection:0] * (itemWidth + 2); // the 2 is for spacing between cells.
NSInteger ySize = [self.collectionView numberOfSections] * (itemHeight + 2);
return CGSizeMake(xSize, ySize);
}
- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)path {
UICollectionViewLayoutAttributes* attributes = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:path];
NSInteger xValue;
attributes.size = CGSizeMake(itemWidth,itemHeight);
xValue = itemWidth/2 + path.row * (itemWidth +2);
NSInteger yValue = itemHeight + path.section * (itemHeight +2);
attributes.center = CGPointMake(xValue, yValue);
return attributes;
}
-(NSArray*)layoutAttributesForElementsInRect:(CGRect)rect {
NSInteger minRow = (rect.origin.x > 0)? rect.origin.x/(itemWidth +2) : 0; // need to check because bounce gives negative values for x.
NSInteger maxRow = rect.size.width/(itemWidth +2) + minRow;
NSMutableArray* attributes = [NSMutableArray array];
for(NSInteger i=0 ; i < self.collectionView.numberOfSections; i++) {
for (NSInteger j=minRow ; j < maxRow; j++) {
NSIndexPath* indexPath = [NSIndexPath indexPathForItem:j inSection:i];
[attributes addObject:[self layoutAttributesForItemAtIndexPath:indexPath]];
}
}
return attributes;
}
The data is arranged as an array of arrays where each inner array supplies the data for one horizontal row. With the values I have in there now, and using your data as an example, the view looked like this,
This is the code I haven the view controller,
#interface ViewController ()
#property (strong,nonatomic) UICollectionView *collectionView;
#property (strong,nonatomic) NSArray *theData;
#end
#implementation ViewController
- (void)viewDidLoad {
self.theData = #[#[#"",#"A",#"B",#"C"],#[#"1",#"115",#"127",#"132"],#[#"2",#"",#"",#"153"],#[#"3",#"",#"199",#""]];
MultpleLineLayout *layout = [[MultpleLineLayout alloc] init];
self.collectionView = [[UICollectionView alloc] initWithFrame:self.view.bounds collectionViewLayout:layout];
self.collectionView.dataSource = self;
self.collectionView.delegate = self;
layout.scrollDirection = UICollectionViewScrollDirectionHorizontal;
self.view.backgroundColor = [UIColor blackColor];
[self.view addSubview:self.collectionView];
[self.collectionView registerNib:[UINib nibWithNibName:#"CustomDataCell" bundle:nil] forCellWithReuseIdentifier:#"DataCell"];
}
- (NSInteger)collectionView:(UICollectionView *)view numberOfItemsInSection:(NSInteger)section {
return [self.theData[section] count];
}
- (NSInteger)numberOfSectionsInCollectionView: (UICollectionView *)collectionView {
return [self.theData count];
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
DataCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:#"DataCell" forIndexPath:indexPath];
cell.label.text = self.theData[indexPath.section][indexPath.row];
return cell;
}
You can do it with UICollectionViewFlowLayout but if you go that way you need to count carefully and get the padding (i.e. the top left, and other "empty" cells) right. If you miscount it's obvious because the flow ruins everything quickly and you will find your header cells halfway down your columns. (I have done a similar thing)
I only did it that way because of a fear of custom layouts - but in fact it is as easy as UITableView. If your layout does not scroll at all then yours will be particularly simple as your only real work will be calculating frame values for cells, to be returned in layoutAttributesForItemAtIndexPath. Since your whole view fits in the visible area, layoutAttributesForElementsInRect will mean you simply iterate through all the cells in the view. collectionViewContentSize will be the size of your view.
Looking at your sample picture you might find it convenient to organise your data as a dictionary of arrays, one per column. You can get a column array by name ("A", "B" etc.) and the position in the array corresponds to the value in the leftmost column, which you might name "index".
There are many more methods you can use but those are the basics, and will get your basic display up and running.
Since I have been struggling for 3 days with this problem and have asked about it twice already, but maybe was not clear, I had decided to investigate the issue & found a buggy behavior with this view.
I will show the entire simple code, so anyone can reproduce the bug (iPad Air).
I am setting a collectionView flowlayout that subclasses the layout to get a constant spacing between cells, and here is the start:
TopAlignedCollectionViewFlowLayout *layout = [[TopAlignedCollectionViewFlowLayout alloc] init];
CGRect size = CGRectMake(0, 0, 900, 1200);
self.GridView = [[UICollectionView alloc] initWithFrame:size
collectionViewLayout:layout];
[self.GridView registerClass:[GridCell class] forCellWithReuseIdentifier:#"Cell"];
[self.GridView setDelegate:self];
[self.GridView setDataSource:self];
[self.view addSubview:self.GridView];
Then setting my delegates is as simple as that : (height is dynamic )
#pragma grid- main functions
-(NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView
{
return 1;
}
-(NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
{
return 80;
}
//cell size
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout
sizeForItemAtIndexPath:(NSIndexPath *)indexPath;
{
//a random dynamic height of a cell
int a = arc4random()%300;
CGSize size = CGSizeMake( 340, 240+a );
return size;
}
-(UICollectionViewCell*)collectionView:(UICollectionView *)collectionView
cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *cellIdentifier=#"Cell";
GridCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:cellIdentifier
forIndexPath:indexPath];
cell.textL.text=[NSString stringWithFormat:#"%d",indexPath.row];
NSLog(#"%d",indexPath.row);
return cell;
}
Now the subclass, to get a constant spacing : (TopAlignedCollectionViewFlowLayout)
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
{
NSArray* attributesToReturn = [super layoutAttributesForElementsInRect:rect];
for (UICollectionViewLayoutAttributes* attributes in attributesToReturn) {
if (nil == attributes.representedElementKind) {
NSIndexPath* indexPath = attributes.indexPath;
attributes.frame = [self layoutAttributesForItemAtIndexPath:indexPath].frame;
}
}
return attributesToReturn;
}
#define numColumns 2
- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath
{
UICollectionViewLayoutAttributes* currentItemAttributes = [super layoutAttributesForItemAtIndexPath:indexPath];
if (indexPath.item < numColumns) {
CGRect f = currentItemAttributes.frame;
f.origin.y = 0;
currentItemAttributes.frame = f;
return currentItemAttributes;
}
NSIndexPath* ipPrev = [NSIndexPath indexPathForItem:indexPath.item-numColumns
inSection:indexPath.section];
CGRect fPrev = [self layoutAttributesForItemAtIndexPath:ipPrev].frame;
CGFloat YPointNew = fPrev.origin.y + fPrev.size.height + 10;
CGRect f = currentItemAttributes.frame;
f.origin.y = YPointNew;
currentItemAttributes.frame = f;
return currentItemAttributes;
}
Anyone can check and see that after you scroll for a while, you get a strange effect of blank spaces that are filled lately by their cells,something like :
1 2
3 4
6
8
NOTE: 5-7 are loaded in later.
EDIT1:
Removing the random height from the cell size delegate method, set it to be constant height, solves this issue.
Problem is: Cell's height must be dynamic.
EDIT2:
Setting the random height (int a) to be smaller, makes also the problem to disappear,(<100), means that the smaller the distance height between cells, more likely the problem will not occur .
EDIT3 !
I have managed to set a constant distance between cells, not with subclass of the layout, but with my own memory by saving the previous cell origin and height, so i have got the constant spacing but the problem is back again ! seems that if the cells are in some certain structure, it makes the callback method that create cells, to not being called in time ! wow , i am really wondering how no one had seen this before ..
here is my implementation to create spacing with no subclassing,that also cause the problem:
-(UICollectionViewCell*)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *cellIdentifier=#"Cell";
GridCell *cell=[collectionView dequeueReusableCellWithReuseIdentifier:cellIdentifier forIndexPath:indexPath];
cell.textL.text=[NSString stringWithFormat:#"%ld",(long)indexPath.row];
NSLog(#"%d",indexPath.row);
if(indexPath.row>1)
{
NSIndexPath* ipPrev = [NSIndexPath indexPathForItem:indexPath.item-2 inSection:indexPath.section];
float prey=[[[NSUserDefaults standardUserDefaults] objectForKey:[NSString stringWithFormat:#"y:%ld",(long)ipPrev.row]] floatValue];
float preh=[[[NSUserDefaults standardUserDefaults] objectForKey:[NSString stringWithFormat:#"h:%ld",(long)ipPrev.row]] floatValue];
cell.frame=CGRectMake(cell.frame.origin.x, preh+prey+10, cell.frame.size.width, cell.frame.size.height);
[[NSUserDefaults standardUserDefaults] setFloat:cell.frame.origin.y forKey:[NSString stringWithFormat:#"y:%ld",(long)indexPath.row]];
[[NSUserDefaults standardUserDefaults] setFloat:cell.frame.size.height forKey:[NSString stringWithFormat:#"h:%ld",(long)indexPath.row]];
[[NSUserDefaults standardUserDefaults] synchronize];
NSLog(#"this index:%d",indexPath.row);
NSLog(#"this cell y:%f",cell.frame.origin.y);
NSLog(#"this cell height:%f",cell.frame.size.height);
NSLog(#"previous index:%ld",(long)ipPrev.row);
NSLog(#"previous cell y: %#",[[NSUserDefaults standardUserDefaults] objectForKey:[NSString stringWithFormat:#"y:%ld",(long)ipPrev.row]]);
NSLog(#"previous cell height: %#",[[NSUserDefaults standardUserDefaults] objectForKey:[NSString stringWithFormat:#"h:%ld",(long)ipPrev.row]]);
NSLog(#"------------------------");
}
return cell;
}
Seems its a serious bug in UICollectionView, when images are bigger than screen size.
1 . UICollectionView fall back to UIScrollView
2.Large UICollectionViewCell's disappearing with custom layout
3.http://stripysock.com.au/blog/2013/3/14/working-around-a-bug-in-uicollectionview
So much work and time, for this stupid bug of apple with the reusable cells that causes nothing but headache with so many strange behaviours.
For developers who didn't have the problem i can say- just try to set dynamic height images, and iPad simulator, and you will see bugs that are just unbelievable.
So no answer for me, i will have to implement the whole thing by my self with a scrollview ,since i dont want to be depended again on things such PSTCollectionView
I was trying to subclass collection view layout , in order to get a constant spacing between vertical cells. i have 2 columns and many rows, with dynamic cells height .
The goal is Pinterest like grid.
So i have subclassed the layout class, and now has a constant space between cells, but there is a serious problem caused by that .
When i scroll down, the left cells are not being loaded in time= there are many "holes" so that there is blank space of 3-4 cells, and than- they suddenly appears at once -lately.
so i have this :
1 2
3 4
6
8
than 5 and 7 appears when i scroll down more . i just can't get rid of this !
EDIT: seems that when all cells are in the same size, this will not happens ,so when i return here a constant height :
//cell size
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath;
{
CGSize size=CGSizeMake( imageWidth, scale*height+[Globals sharedGlobals].gridViewStripHeight );
return size;
My subclass(which when not using it, also solves the problem )
//the subclass
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
{
NSArray* arr = [super layoutAttributesForElementsInRect:rect];
for (UICollectionViewLayoutAttributes* atts in arr)
{
if (nil == atts.representedElementKind)
{
NSIndexPath* ip = atts.indexPath;
atts.frame = [self layoutAttributesForItemAtIndexPath:ip].frame;
}
}
return arr;
}
- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath
{
UICollectionViewLayoutAttributes* atts =[super layoutAttributesForItemAtIndexPath:indexPath];
if (indexPath.item == 0 || indexPath.item == 1) // degenerate case 1, first item of section
return atts;
NSIndexPath* ipPrev =
[NSIndexPath indexPathForItem:indexPath.item-2 inSection:indexPath.section];
CGRect fPrev = [self layoutAttributesForItemAtIndexPath:ipPrev].frame;
CGFloat rightPrev = fPrev.origin.y + fPrev.size.height + 50;
if (atts.frame.origin.y <= rightPrev) // degenerate case 2, first item of line
return atts;
CGRect f = atts.frame;
f.origin.y = rightPrev;
atts.frame = f;
return atts;
}
To use it i have :
UICollectionViewFlowLayout *layout=[[TopAlignedCollectionViewFlowLayout alloc] init]; subclass
CGRect size=CGRectMake( ([UIScreen mainScreen].bounds.size.width-collectionWidth)/2,
upperLineMargin, collectionWidth, [UIScreen mainScreen].bounds.size.height-upperLineMargin);
self.GridView=[[UICollectionView alloc] initWithFrame:size collectionViewLayout:layout];
[self.GridView registerClass:[GridCell class] forCellWithReuseIdentifier:#"Cell"];
Working in iOS 7, how does one specify where the header & footer boxes go in a UICollectionView?
I have a custom UICollectionViewFlowLayout. I have overwritten
-(void)prepareLayout
-(NSArray*) layoutAttributesForElementsInRect:(CGRect)rect
-(UICollectionViewLayoutAttributes*) layoutAttributesForSupplementaryViewOfKind: (NSString*)kind atIndexPath:(NSIndexPath*)indexPath
My problem is, I'm not sure how to specify header location. I have already specified that a header exists in prepareLayout:
-(void)prepareLayout
{
[super prepareLayout];
boundsSize = self.collectionView.bounds.size;
midX = boundsSize.width / 2.0f;
curIndex = 0;
self.headerReferenceSize = CGSizeMake(CELL_SIZE, TITLE_HEIGHT);
self.footerReferenceSize = CGSizeMake(0, 0);
self.scrollDirection = UICollectionViewScrollDirectionHorizontal;
self.sectionInset = UIEdgeInsetsMake(TOP_INSET, LEFT_INSET, BOTTOM_INSET, RIGHT_INSET);
self.minimumLineSpacing = LINE_SPACING;
self.minimumInteritemSpacing = INTERIM_SPACING;
self.itemSize = CGSizeMake(CELL_SIZE, CELL_SIZE);
}
I just don't know the right property of my custom FlowLayout to set, as there doesn't seem to be something like "HeaderLocation" to set, either as a LayoutAttributes or in the layout object itself. Right now, it is appearing to the side/between my images, when I'd like them to be appearing above each image (horizontal scroll).
I have tried the following:
-(UICollectionReusableView*) collectionView: (UICollectionView*)collectionView viewForSupplementaryElementOfKind:(NSString*)kind atIndexPath:(NSIndexPath*)indexPath
{
NSLog(#"**ViewForSupplementaryElementOfKind called***");
CGFloat centerX = collectionView.center.x;
CGFloat centerY = collectionView.center.y;
CGFloat titleWidth = [MyLayout titleWidth];
CGFloat titleHeight = [MyLayout titleHeight];
MyTitleView* titleView = [collectionView dequeueReusableSupplementaryViewOfKind:kind withReuseIdentifier:ImageTitleIdentifier forIndexPath:indexPath];
titleView.frame = CGRectMake(centerX - titleWidth/2.0,
0.0,
titleWidth,
titleHeight);
return titleView;
}
This doesn't work. The title appears above overlapped with a bunch of other titles, then the moment I start scrolling (horizontally), they jump back into the wrong place, horizontally between the images rather than above.
PS> Please do not suggest anything that has to do with NIB or XIB placement. I am using a UICollectionView, NOT a UICollectionViewController, so I actually have no prototypical cell to work with. The layout is being done entirely programatically -- from code alone -- so I can't simply open a XIB file and adjust the location of a text box.
Amending the attributes returned by -layoutAttributesForElementsInRect is the right approach, but if you want to alter the position of offscreen headers and footers, you may need to fetch the supplementary view attributes yourself.
For example, in your UICollectionViewFlowLayout subclass:
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
{
NSMutableArray *attributesArray = [[super layoutAttributesForElementsInRect:rect] mutableCopy];
// the call to super only returns attributes for headers that are in the bounds,
// so locate attributes for out of bounds headers and include them in the array
NSMutableIndexSet *omittedSections = [NSMutableIndexSet indexSet];
for (UICollectionViewLayoutAttributes *attributes in attributesArray) {
if (attributes.representedElementCategory == UICollectionElementCategoryCell) {
[omittedSections addIndex:attributes.indexPath.section];
}
}
for (UICollectionViewLayoutAttributes *attributes in attributesArray) {
if ([attributes.representedElementKind isEqualToString:UICollectionElementKindSectionHeader]) {
[omittedSections removeIndex:attributes.indexPath.section];
}
}
[omittedSections enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop) {
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:0 inSection:idx];
UICollectionViewLayoutAttributes *attributes = [self layoutAttributesForSupplementaryViewOfKind:UICollectionElementKindSectionHeader
atIndexPath:indexPath];
[attributesArray addObject:attributes];
}];
for (UICollectionViewLayoutAttributes *attributes in attributesArray) {
if ([attributes.representedElementKind isEqualToString:UICollectionElementKindSectionHeader]) {
// adjust any aspect of each header's attributes here, including frame or zIndex
}
}
return attributesArray;
}
CollectionView Header height is set below Collectionview delegate
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout referenceSizeForHeaderInSection:(NSInteger)section
And Set view in Collectionview Header in Below Delegate
- (UICollectionReusableView*)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath
{
UICollectionReusableView * view = nil;
if ([kind isEqualToString:UICollectionElementKindSectionHeader])
{
ColorSectionHeaderView *header = [collectionView dequeueReusableSupplementaryViewOfKind:UICollectionElementKindSectionHeader
withReuseIdentifier:NSStringFromClass([ColorSectionHeaderView class])
forIndexPath:indexPath];
header.sectionIndex = indexPath.section;
header.hideDelete = collectionView.numberOfSections == 1; // hide when only one section
header.delegate = self;
view = header;
}
return view;
}
Ragistred Class in ViewDidLoad
-(void)ViewDidLoad
{
[collectionView registerNib:[UINib nibWithNibName:NSStringFromClass([ColorSectionFooterView class]) bundle:nil]
forSupplementaryViewOfKind:UICollectionElementKindSectionFooter
withReuseIdentifier:NSStringFromClass([ColorSectionFooterView class])];
[Super ViewDidLoad];
}
Am I able to create a UILabel that is layouted upon many UITableViewCells?
I'm trying to make something like (that is just one section of my UITableView, each section can have one or more rows):
---------------------------------------------
| Multi-lined label | row1 values |
| with some useless | row2 values |
| text | row3 values |
---------------------------------------------
I managed to create a UILabel (in the first row of a section) that is multi-lined and is not clipping to bounds. That works really well (it was a bit tricky to count each sections row heights, but doable) besides one case: when I'm scrolling UITableView from bottom to top - UITableView renders last row (without UILabel) so it has "no evidence" of having UILabel (because it is maintained in the first row of section). Can I force some kind of relayouting first cell in section? I tried reloadRowsAtIndexPaths:withRowAnimation: with first row in each section whenever I layouted not first cell in section but it gave me layouting errors that I really do not understand. Or maybe there is another idea to do so?
-- EDITED
To be clear: I have a custom UITableViewCell with an IB view, it has a few labels that each row consist of and a label named labelName that I want to be "multi-lined" along rows in that section. LabelName.text is empty for each row besides first one in each section.
I am adding somescreenshots:
Good screenshot - when I am scrolling to bottom I'm getting proper effect:
Bad screenshot - when I am scrolling up, UITableView renders last row of section firstly, and afterwards renders upper rows - that gives effect of cut label (because multi-line label is in the first row)
I am not sure if code here will add anything to question - it is rather simple and almost whole logic is in tableView:heightForRowAtIndexPath. I can only present how do I create custom UITableViewCell:
CustomTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:[CustomTableViewCell reuseIdentifier]];
if (cell == nil) {
cell = [[CustomTableViewCell alloc] initWithOwner:self];
cell.clipsToBounds = NO;
cell.labelName.clipsToBounds = NO;
cell.contentView.superview.clipsToBounds = NO;
}
-- EDIT 2
Here is most of the code:
- (void) reloadData
{
NSUInteger index = 0;
for (NSDictionary *object in self.list) {
CGFloat height = [[object objectForKey:#"name"] sizeWithFont:self.labelFont constrainedToSize:self.labelSize].height;
[self.labelHeights addObject:NSNumberFloat(ceilf(height))];
index++;
}
[self.tableView reloadData];
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSDictionary *object = [self.list objectAtIndex:indexPath.section];
CGFloat height = [[self.labelHeights objectAtIndex:indexPath.section] floatValue];
NSUInteger count = [[object objectForKey:#"list"] count];
CGFloat cellHeight = 30.f;
if((indexPath.row + 1) == count){
cellHeight = MAX(8.f + height - 30.f * indexPath.row, 30.f);
}
return cellHeight;
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return [self.list count];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [[[self.list objectAtIndex:section] objectForKey:#"list"] count];
}
- (UITableViewCell *) tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSDictionary *person = [self.list objectAtIndex:indexPath.section];
NSDictionary *object = [[person objectForKey:#"list"] objectAtIndex:indexPath.row];
CustomTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:[CustomTableViewCell reuseIdentifier]];
if (cell == nil) {
cell = [[CustomTableViewCell alloc] initWithOwner:self];
cell.backgroundColor = [UIColor clearColor];
cell.userInteractionEnabled = NO;
cell.clipsToBounds = NO;
cell.labelName.clipsToBounds = NO;
[cell.contentView.superview setClipsToBounds:NO];
}
if(indexPath.row == 0){
cell.labelName.text = [person objectForKey:#"name"];
CGFloat height = [[self.labelHeights objectAtIndex:indexPath.section] floatValue];
cell.labelName.numberOfLines = (int)(height / self.fontSizeHeight);
cell.labelName.frame = CGRectChangeHeight(cell.labelName.frame, height);
}
else{
cell.labelName.text = #"";
}
CGFloat cellHeight = [self tableView:self.tableView heightForRowAtIndexPath:indexPath];
cell.borderTop.hidden = YES;
cell.borderBottom.hidden = YES;
cell.borderBottomSmall.hidden = NO;
if(indexPath.row == 0){
cell.borderTop.hidden = NO;
}
if(indexPath.row + 1 == [[person objectForKey:#"list"] count]){
cell.borderBottom.hidden = NO;
cell.borderBottom.frame = CGRectChangeY(cell.borderBottom.frame, cellHeight - 1.f);
cell.borderBottomSmall.hidden = YES;
}
cell.labelDate.text = [object objectForKey:#"date"];
cell.labelPremium.text = [[object objectForKey:#"premium"];
return cell;
}
-- PARTIAL ANSWER
I managed to create a hack, that makes multi-line UILabel visibile when scrolling bottom to up at some point:
- (void) scrollViewDidScroll:(UIScrollView *)scrollView
{
NSArray *cells = [self.tableView visibleCells];
UITableViewCell *cell = [cells objectAtIndex:0];
[cell.superview bringSubviewToFront:cell];
}
I noticed that the part of the UILabel is covered by a row thats below of the UILabels row and that hack makes it would be properly displayed. But it has a drawback, when scrolling slowly from bottom to top it generates a flicker when label is created (part of it should be visible before real creation of UILabel).
Up mentioned answers are not solutions, but "hacks".
In the cell == nil block should be only the initialization.
You should not add any subviews in cell in cellForRowAtIndexPath.
The reason is simple: I will reuse a cell with some labels already added and add a new label.
Either use the default cell.textLabel, either create a subclass for UITableViewCell, with a
-(void)setData:(dictionary or string)object;
and in implementation just set the proper data to proper UI controls.
Add/create controls either in init method in the subclass, or in IB/Storyboard.
Call the dictionary or string should be picked in correspondence to indexPath, so you will always get proper data for proper cell at proper indexPath.
Try This
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *cellId = #"cellId";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellId];
if (cell==nil)
{
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellId];
}
for (UIView *subview in [cell.contentView subviews]) {
[subview removeFromSuperview];
}
/// your UI on cell goes here
return cell;
}