iOS UICollectionView header & footer location - ios

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];
}

Related

Cell disappears after reloading when using estimatedSize

I'm having a weird issue, when using a collection view with dynamic sizes, this issue doesn't happens while using fixed sizes.
After a reload the first cell of each section disappears, but only if they are out of the screen. After a few tests I realize that the cell didn't disappear, but its hidden bellow the section header.
Do you have any idea what is causing this?
Collection without reloading:
Collection after reloading with cell visible:
Collection after reloading with cell out of screen:
3D view of the cell after reloading:
The code:
#pragma mark - UICollectionViewDataSource
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView {
return self.sections.count;
}
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)sectionIndex {
Section *section = [self.sections objectAtIndex:sectionIndex];
return section.items.count;
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
Section *section = [self.sections objectAtIndex:indexPath.section];
Item *item = [section.items objectAtIndex:indexPath.row];
if (self.editing) {
EditingCollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:#"cell-editing" forIndexPath:indexPath];
cell.item = item;
return cell;
} else {
BasicCollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:#"cell" forIndexPath:indexPath];
cell.item = item;
return cell;
}
}
- (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath {
if ([kind isEqualToString:UICollectionElementKindSectionHeader]) {
HeaderCollectionReusableView *header = [collectionView dequeueReusableSupplementaryViewOfKind:kind withReuseIdentifier:#"header" forIndexPath:indexPath];
Section *section = [self.sections objectAtIndex:indexPath.section];
header.title = section.title;
return header;
} else {
UICollectionReusableView *footer = [collectionView dequeueReusableSupplementaryViewOfKind:kind withReuseIdentifier:#"footer" forIndexPath:indexPath];
return footer;
}
}
#implementation DetailCollectionViewLayout
- (instancetype)init {
if (self = [super init]) {
[self initialize];
}
return self;
}
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
if (self = [super initWithCoder:aDecoder]) {
[self initialize];
}
return self;
}
- (void)prepareLayout {
CGFloat cellWidth = (isIPAD) ? 288 : CGRectGetWidth(self.collectionView.bounds);
CGFloat headerWidth = CGRectGetWidth(self.collectionView.bounds);
// CGFloat ratio = (isIPAD) ? 0.33 : 0.66;
self.estimatedItemSize = CGSizeMake(cellWidth, 53);
self.headerReferenceSize = CGSizeMake(headerWidth, 50);
self.footerReferenceSize = CGSizeMake(headerWidth, 1 + self.minimumInteritemSpacing);
[super prepareLayout];
}
- (void)initialize {
self.minimumLineSpacing = 0;
self.minimumInteritemSpacing = (isIPAD) ? 5 : 10;
self.estimatedItemSize = CGSizeZero;
self.scrollDirection = UICollectionViewScrollDirectionVertical;
self.sectionInset = UIEdgeInsetsZero;
}
-(BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds {
return YES;
}
#end
I made a simple sample project and record a video: http://sendvid.com/330uo5jm
It looks like the issue is the position from the first cell.
UICollectionViewCell autosizing can be a little, uh... interesting even at the best of times. I've had this exact issue in the past, and similar issues too.
Use a different value for .estimatedItemSize, the closer to the actual item size the better. I noticed you're using a size of CGRectZero at first. I wouldn't recommend doing that. Just set it once, at the start, with a value close to your size. Try a few values, see what works for you. For me, it took a bit of fine tuning.
For anyone developing for iOS 10 (at the time of writing this hasn't been released) there is a new collection view property that lets the collection view determine the estimated size itself. Set the itemSize to UICollectionViewFlowLayoutAutomaticSize, you shouldn't need to set .estimatedItemSize explicitly.
make sure your estimatedItemSize in your code same as size of cell in your xib or storyboard.Don't changes it's size runtime.
can you check with use of identifier like...
NSString *CellIdentifier = [NSString stringWithFormat:#"%d_%d",indexPath.section,indexPath.row];
Sometimes it happens if you have a big difference between estimated size and real size of cell.
Check you have a clear consequence of top to bottom constraints (if you are using autolayout).
Is there something that can break the autolayout to work properly ? E.g compresion resistance settings ?
Are you sure there are data for the cell after reload ? (Will be weird, but to be sure, just double check that.)
Also as Apple denotes here Apple - self sizing guide try to set the estimation of size as close as possible to real dimensions.
You can also try to refer to invalidation of collection layout as you are using your own. Refer to Possible flow-layout help
Try to set the estimation as close as possible and you will see if it solve your problem.

UIcollection view support cover flow

Hi friends is it possible to add a cover flow effect in uicollection view in horizontal layout.if possible means kindly tell me how to implement the effect.
Yes it is possible, You need to Implement your custom UICollectionViewFlowLayout.
Make a custom class which inherits UICollectionViewFlowLayout
- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds {
return YES; }
- (UICollectionViewScrollDirection)scrollDirection {
return UICollectionViewScrollDirectionVertical; }
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect {
UICollectionView *collectionView = [self collectionView];
UIEdgeInsets insets = [collectionView contentInset];
CGPoint offset = [collectionView contentOffset];
CGFloat minY = -insets.top;
NSArray *attributes = [super layoutAttributesForElementsInRect:rect];
// minY is Point where we implement this cover flow.
if (offset.y < minY) {
// Figure out how much we've pulled down
CGFloat deltaY = fabsf(offset.y - minY);
for (UICollectionViewLayoutAttributes *attrs in attributes) {
// Locate the header attributes
NSString *kind = [attrs representedElementKind];
if (kind == UICollectionElementKindSectionHeader) {
// This is header's height and y based on how much the user has scrolled down.
CGSize headerSize = [self headerReferenceSize];
CGRect headerRect = [attrs frame];
headerRect.size.height = MAX(minY, headerSize.height + deltaY);
headerRect.origin.y = headerRect.origin.y - deltaY;
[attrs setFrame:headerRect];
break;
}
}
}
return attributes; }
Now in your class where you allocate UICollectionView
CustomCoverFlowHeaderCollectionViewLayout *flow; flow = [[CustomCoverFlowHeaderCollectionViewLayout alloc] init]; [stretchyLayout setHeaderReferenceSize:CGSizeMake(320.0, 160.0)]; // Set our custom layout [collectionView setCollectionViewLayout: flow]; [collectionView setAlwaysBounceVertical:YES]; [collectionView registerClass:[UICollectionReusableView class] forSupplementaryViewOfKind:UICollectionElementKindSectionHeader
withReuseIdentifier:#"myCoverCollectionView"];
You are almost done
- (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView
viewForSupplementaryElementOfKind:(NSString *)kind
atIndexPath:(NSIndexPath *)indexPath {
if (!header) {
header = [collectionView dequeueReusableSupplementaryViewOfKind:kind
withReuseIdentifier:#"myCoverCollectionView" forIndexPath:indexPath];
CGRect bounds;
bounds = [header bounds];
UIImageView *imageView;
imageView = [[UIImageView alloc] initWithFrame:bounds];
[imageView setImage:[UIImage imageNamed:#"background"]];
[imageView setContentMode:UIViewContentModeScaleAspectFill];
[imageView setClipsToBounds:YES];
[imageView setAutoresizingMask:UIViewAutoresizingFlexibleHeight];
[header addSubview:imageView];
}
return header; }
Here is a good project based on UICollectionView for you.

UICollectionViewFlowLayout ignoring section insets for items of different heights - bug or expected?

I'm using UICollectionViewFlowLayout and wanted to apply section insets like below. All my items have the same width but varying heights. The insets work when items within the same section are the same height but not when they are different heights in the same section. Is this expected behaviour for this layout? Do I need to subclass and make a custom one or is something missing?
- (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout insetForSectionAtIndex:(NSInteger)section {
UIEdgeInsets insets = UIEdgeInsetsZero;
CGFloat big = 30;
CGFloat small = 10;
if (section < 5) {
insets = UIEdgeInsetsMake(0, big, 0, small);
} else {
insets = UIEdgeInsetsMake(0, small, 0, big);
}
return insets;
}
For some reason the UICollectionViewFlowLayout has the behaviour to center the cells if you have a single item each row. It ignores the section insets then.
You can solve this issue by overriding the UICollectionViewFlowLayout and change the following two methods:
- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath
{
UICollectionViewLayoutAttributes *attribute = [super layoutAttributesForItemAtIndexPath:indexPath];
[self fixLayoutAttributeInsets:attribute];
return attribute;
}
And
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
{
NSArray *results = [super layoutAttributesForElementsInRect:rect];
for (UICollectionViewLayoutAttributes *attribute in results)
{
[self fixLayoutAttributeInsets:attribute];
}
return results;
}
The magic:
- (void)fixLayoutAttributeInsets:(UICollectionViewLayoutAttributes *)attribute
{
if ([attribute representedElementKind])
{ //nil means it is a cell, we do not want to change the headers/footers, etc
return;
}
//Get the correct section insets
UIEdgeInsets sectionInsets;
if ([[[self collectionView] delegate] respondsToSelector:#selector(collectionView:layout:insetForSectionAtIndex:)])
{
sectionInsets = [(id<UICollectionViewDelegateFlowLayout>)[[self collectionView] delegate] collectionView:[self collectionView] layout:self insetForSectionAtIndex:[[attribute indexPath] section]];
}
else
{
sectionInsets = [self sectionInset];
}
//Adjust the x position of the view, the size should be correct or else do more stuff here
CGRect frame = [attribute frame];
frame.origin.x = sectionInsets.left;
[attribute setFrame:frame];
}
This solution does not work (and is not needed) if you have multiple cells on a single row so you should check for that case with a boolean or whatever you like... This is just a simple example for this scenario
Doesn't seem to be possible with varying heights, so I made the cells full width and mange the padding logic inside the cell class itself.

Subclassing UICollectionView, skipping indexes

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"];

Subclassing UICollectionViewLayout and assign to UICollectionView

I have a CollectionViewController
- (void)viewDidLoad
{
[super viewDidLoad];
// assign layout (subclassed below)
self.collectionView.collectionViewLayout = [[CustomCollectionLayout alloc] init];
}
// data source is working, here's what matters:
- (UICollectionViewCell *)collectionView:(UICollectionView *)cv cellForItemAtIndexPath:(NSIndexPath *)indexPath {
ThumbCell *cell = (ThumbCell *)[cv dequeueReusableCellWithReuseIdentifier:#"ThumbCell" forIndexPath:indexPath];
return cell;
}
I also have a UICollectionViewLayout subclass: CustomCollectionLayout.m
#pragma mark - Overriden methods
- (CGSize)collectionViewContentSize
{
return CGSizeMake(320, 480);
}
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
{
return [[super layoutAttributesForElementsInRect:rect] mutableCopy];
}
- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath
{
// configure CellAttributes
cellAttributes = [super layoutAttributesForItemAtIndexPath:indexPath];
// random position
int xRand = arc4random() % 320;
int yRand = arc4random() % 460;
cellAttributes.frame = CGRectMake(xRand, yRand, 150, 170);
return cellAttributes;
}
I'm trying to use a new frame for the cell, just one at a random position.
The problem is layoutAttributesForItemAtIndexPath is not called.
Thanks in advance for any advice.
Edit: I didn't notice that you've solved your case, but I had the same problem and here's what helped in my case. I'm leaving it here, it may be useful for someone given that there's not much on this topic yet.
Upon drawing, UICollectionView does not call the method layoutAttributesForItemAtIndexPath: but layoutAttributesForElementsInRect: to determine which cells should be visible. It's your responsibility to implement this method and from there call layoutAttributesForItemAtIndexPath: for all visible index paths to determine exact locations.
In other words, add this to your layout:
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
{
NSMutableArray * array = [NSMutableArray arrayWithCapacity:16];
// Determine visible index paths
for(???) {
[array addObject:[self layoutAttributesForItemAtIndexPath:[NSIndexPath indexPathForItem:??? inSection:???]]];
}
return [array copy];
}
Was an issue with Storyboard parameters. Assigned custom UICollectionLayout from Inspector panel.

Resources