UICollectionViewFlowLayout and ios6 - ios

I have an Extended UICollectionFlowLayout. This vertically centres the UIcollectionViewCell by translating the attribute.frame by required amount and also shifting the visible Rect of collection view to show the transformed cells.
This works perfectly fine in ios7. However in ios6 the visible Rect of collection view does not change , hence forth cells are shown shifted but clipped.
Eg : -(NSArray*)layoutAttributesForElementsInRect:(CGRect)rect rect = (0,0,320,500) and I shift cells by 200 then cells with start showing from (0,0,320,200) to (0,0,320,500) and those below 500 will be clipped. Any reason why this would happen in ios6 when it work perfectly in iOS7 ?
#implementation VerticallyCenteredFlowLayout
-(id)init
{
if (!(self = [super init])) return nil;
[self setMinimumLineSpacing:5.0];
[self setMinimumInteritemSpacing:0.0];
[self setItemSize:CGSizeMake(10, 10)];
[self setSectionInset:UIEdgeInsetsMake(0, 11, 11, 11)];
[self setScrollDirection:UICollectionViewScrollDirectionVertical];
return self;
}
-(NSArray*)layoutAttributesForElementsInRect:(CGRect)rect {
NSArray* array = [super layoutAttributesForElementsInRect:rect];
UICollectionViewLayoutAttributes* att = [array lastObject];
if (att){
CGFloat lastY = att.frame.origin.y + att.frame.size.height;
CGFloat diff = self.collectionView.frame.size.height - lastY;
if (diff > 0){
for (UICollectionViewLayoutAttributes* a in array){
a.frame = CGRectMake(a.frame.origin.x, a.frame.origin.y + diff/2, a.frame.size.width, a.frame.size.height) ;
}
}
}
return array;
}

The contentSize was not being automatically Adjusted in ios6.
Overiding following method in VerticallyCenteredFlowLayout Class fixed the issue
-(CGSize)collectionViewContentSize {
CGSize size = [super collectionViewContentSize];
if (size.height < MIN(_maxHeight,self.collectionView.frame.size.height)) {
size.height = MIN(_maxHeight,self.collectionView.frame.size.height);
}
return size;
}

Related

Strange UICollectionViewCell behaviour if float size

Updated:
Sample project is github link
USE iPHONE 6 simulator
2 possible ways to achieve this bug.
Just build and run and see.
Or uncomment 256 row
//return CGSizeMake(widthAndHeight * self.venueLayoutZoom, widthAndHeight * self.venueLayoutZoom);
and press "Remove column" button
I have UICollectionView with a lot of UICollectionViewCells. My UI makes me to use zero space between cells. I have a pinch gesture in my UICollectionView. So sometimes sizeForItem:atIndexPath: returns me a big float numbers (like 11.821123411231). The problem is:
If I don't round these floats I have a strange behaviour sometimes -
It should be
If I round up or down sizeForItem:atIndexPath: it looks great but there are spaces between cells
I don't know what to do. It is really necessary to do without these spaces or strange cells.
My flow layout is this
Controller Code:
- (CGSize)collectionView:(UICollectionView *)collectionView
layout:(UICollectionViewLayout *)collectionViewLayout
sizeForItemAtIndexPath:(nonnull NSIndexPath *)indexPath
{
CGFloat widthAndHeight = [self widthAndHeightForCollectionView:collectionView];
CGFloat result = widthAndHeight * self.venueLayoutZoom;
return CGSizeMake(result, result);
}
- (void)didReceivePinchGesture:(UIPinchGestureRecognizer *)gesture
{
static CGFloat scaleStart;
if (gesture.state == UIGestureRecognizerStateBegan) {
scaleStart = self.venueLayoutZoom;
}
else if (gesture.state == UIGestureRecognizerStateChanged) {
self.venueLayoutZoom = scaleStart * gesture.scale;
}
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
VenueLayoutCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:kVenueLayoutCellReuseIdentifier forIndexPath:indexPath];
self.activeCollectionViewCellsDictionary[indexPath] = cell;
if (self.activeCollectionViewObjects.count > indexPath.section) {
NSArray *rows = self.activeCollectionViewObjects[indexPath.section];
if (rows.count > indexPath.row) {
if ([rows[indexPath.row] isKindOfClass:[VenueLayoutCellObject class]]) {
VenueLayoutCellObject *object = rows[indexPath.row];
cell.cellObject = object;
}
}
}
return cell;
}
Cell Code:
#property (nonatomic, strong) CALayer *circularLayer;
- (void)awakeFromNib
{
[super awakeFromNib];
[self allocAndAddLayers];
}
- (void)allocAndAddLayers
{
self.circularLayer = [CALayer layer];
[self.layer addSublayer:self.circularLayer];
}
#pragma mark - Property Set
- (void)setCellObject:(VenueLayoutCellObject *)cellObject
{
self->_cellObject = cellObject;
self.objectBackgroundColor = cellObject.objectColor;
self.type = cellObject.type;
}
- (void)prepareForReuse
{
[self.circularLayer removeFromSuperlayer];
[self allocAndAddLayers];
}
- (void)setType:(VenueLayoutObjectType)type
{
self->_type = type;
[self updateInderfaceDependOnType];
}
- (void)layoutSubviews
{
[CATransaction begin];
[CATransaction setDisableActions:YES];
[self updateRoundedCorners];
[CATransaction commit];
[super layoutSubviews];
}
- (void)updateRoundedCorners
{
CGRect bounds = self.bounds;
CGRect frame = self.frame;
self.circularLayer.frame = CGRectMake(0, 0, frame.size.width, frame.size.height);
}
- (void)setLayerBackgroundColor:(UIColor *)color
{
self.circularLayer.backgroundColor = color.CGColor;
self.objectMaskLayer.strokeColor = color.CGColor;
self.deselectedColor = color;
}
- (void)updateInderfaceDependOnType
{
UIColor *tempObjectColor = [UIColor grayColor];
UIColor *objectColor = self.objectBackgroundColor ? : tempObjectColor;
[self setLayerBackgroundColor:objectColor];
}
Updated Code:
- (CGSize)collectionView:(UICollectionView *)collectionView
layout:(UICollectionViewLayout *)collectionViewLayout
sizeForItemAtIndexPath:(nonnull NSIndexPath *)indexPath
{
CGFloat widthAndHeight = self.widthAndHeightForActiveCollectionView;
CGFloat result = lroundf(widthAndHeight * self.venueLayoutZoom);
CGFloat width = result;
if (indexPath.row > self.increasedWidthInitialIndex) {
width++;
}
return CGSizeMake(width, result);
}
CGFloat widthAndHeight = CGRectGetWidth(self.activeCollectionView.bounds) / maxCount;
NSNumber *widthNumber = #(CGRectGetWidth(self.activeCollectionView.bounds));
NSInteger count = widthNumber.integerValue % maxCount;
maxCount--;
self.increasedWidthInitialIndex = maxCount - count;
Updated:
If I use low item size (e.g CGSizeMake(7.f, 7.f)) cells doesn't fit whole
collection view but space still exists
Eventually setting width / height to half pixels changes the scale according to the device pixel depth (retina vs non-retina) - more info here
When you try and divide your screen to non-whole numbers, i.e. for screen width of 320 create 6 cells of 53.3 with might cause the UI to break.
You should try and find the remainder of your devision, i.e:
320 % 6 -> 2
And then make 2 cells 1 pixel wider.
So instead of 6 cells consisting of 53.3 pixels wide, you will have 2 consisting of 54 and 4 more of 53, then everything will fit correctly.
EDIT:
After going over your project, I have added printouts of your collection see what issues could be caused by widths, Iv'e added this method in the ViewController.m:
- (void)printoutGrid
{
NSLog(#"collectionview bounds: %f, %#", self.activeCollectionView.bounds.size.width, NSStringFromCGSize(self.activeCollectionView.contentSize));
CGFloat calculatedWidth = [self widthAndHeightForActiveCollectionViewItem];
CGFloat result = floor(calculatedWidth * self.venueLayoutZoom);
for (int rowAt = 0; rowAt < self.activeCollectionViewObjects.count; rowAt++)
{
NSArray* arrayAt = self.activeCollectionViewObjects[rowAt];
NSString* rowString = #"|";
for (int colAt = 0; colAt < arrayAt.count; colAt++)
{
if (colAt > self.increasedWidthInitialIndex)
{
rowString = [rowString stringByAppendingString:[NSString stringWithFormat:#"%d|", (int)result + 1]];
}
else
{
rowString = [rowString stringByAppendingString:[NSString stringWithFormat:#"%d|", (int)result]];
}
}
NSLog(#"%#", rowString);
}
}
And called it after every time you reload the collection or invalidate the layout, Iv'e found only that you should use floor instead of lroundf.
This method will help you debug your widths for the future.
But, after going over your project settings, something seemed off in your simulator, I noticed it was too large / out of scale and not retina, I went to your project settings and saw that your project is not retina compatible - which means the UI will not use Auto-Scaling -> which means it will use the same resolution (320 x 518) for all devices and what it does, it stretches the UI - meaning in iPhone 6 it multiplies it, and the stretching is being anti-analysed, which causes these so-called empty spaces between cells even though there is not space there.
What you need to do, is go to your project settings -> and in general scroll down to "Launch Screen File" and select "LaunchScreen" from the list:
This seems to solve the issue for this project. Let me know if you have anymore question and if it works.
Please note - it will require you to change size calculation a-bit because the resolution changes again after "viewDidLayoutSubviews" and not only when the view loads.
Good luck!

UIScrollView paging with animation

I have achieved the following using UIScrollView and enabled paging.
I want the centre element of the scrollview to show little bigger than other elements. Need to increase/decrease the font of the text label as the scroll view is scrolling depending on its location.
I tried using transform but hard luck.
Code for adding the label's:
array = [[NSMutableArray alloc] init];
for(int i=0;i<10;i++){
UILabel *label = [[UILabel alloc]initWithFrame:CGRectMake(x, 0, 150, 50)];
label.text = [NSString stringWithFormat:#"ID - %d",i];
label.textAlignment = UITextAlignmentCenter;
x +=150;
[self.scrollView addSubview:label];
[array addObject:label];
}
[self.scrollView setContentSize:CGSizeMake(x, 50)];
Animation which I performed in ScollViewDidScroll
float position = label.center.x - scrollView.contentOffset.x;
float offset = 2.0 - (fabs(scrollView.center.x - position) * 1.0) / scrollView.center.x;
label.transform = CGAffineTransformIdentity;
label.transform = CGAffineTransformScale(label.transform,offset, offset);
CODE: What I have achieved till now:
https://www.dropbox.com/s/h2q4qvg3n4fi34f/ScrollViewPagingPeeking.zip?dl=0
Don't use scrollview, used UICollectionView and Make collectionView cell bigger as screen size.
And Enable it's paging property than used it's delegate methods.
It's more preferable and efficient way of paging.
It will work using a collection view. Return layout attributes where the transform scale is calculated according to the position. Subclass the UICollectionViewLayout you are using (probably UICollectionViewFlowLayout). To get the resizing based on position to work you should override a few of its methods thusly:
- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds
{
return YES;
}
- (NSArray*)layoutAttributesForElementsInRect:(CGRect)rect
{
NSArray *array = [super layoutAttributesForElementsInRect:rect];
for (UICollectionViewLayoutAttributes *attributes in array)
{
[self updateLayoutAttributesForItemAtIndexPath:layoutAttributes];
}
return array;
}
- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath
{
UICollectionViewLayoutAttributes *layoutAttributes = [super layoutAttributesForItemAtIndexPath:indexPath];
[self updateLayoutAttributesForItemAtIndexPath:layoutAttributes];
}
- (UICollectionViewLayoutAttributes *)updateLayoutAttributesScaleForPosition:(UICollectionViewLayoutAttributes *)layoutAttributes
{
// NOTE: Your code assigning an updated transform scale based on the cell position here
return layoutAttributes;
}
Probably the main thing you were missing was the override to the shouldInvalidateLayoutForBoundsChange: method

How can I keep the same collection view cells on the screen when zooming a UICollectionView?

I have a UICollectionView that uses a UICollectionViewLayout subclass for its layout. The layout is a simple grid.
When I zoom the collection view in or out, the positions of the cells on the screen change. In some cases, when zooming in, cells move off the screen entirely. I zoom the cells with a pinch gesture recognizer that sends x and y scale values to the layout class and then invalidates the layout.
As the cells get bigger, they move because their origins are calculated relative to the 0,0 position of the collection view.
I want to be able to zoom the collection view in, while having as many of the cells that were originally on the screen stay there. A good solution would be to have the cell in the center of the screen stay in the center as it becomes larger. Cells around the center cell would grow, and that might push them off the screen.
I've tried adjusting the collection view's content offset, but I haven't achieved what I want. I'm not quite sure how to calculate its new value, and I've learned that the changes caused by invalidateLayout do not happen immediately.
I tried a key value observer for the collection view's content size, but that caused stuttering because the changes in the KVO method happened well after the original zooming.
I've also worked a little bit with scrollToItemAtIndexPath, but the code in my full app is not guaranteed to have a cell at the exact center of the screen. That solution is less desirable for me.
Here is the code where the pinch recognizer sends changes to the layout class:
[self.gridLayout updateCellWidthScale:xScale];
[self.gridLayout updateCellHeightScale:yScale];
[self.gridLayout invalidateLayout];
Here is the code in the layout class
(numberOfRows and numberOfColumns are both set to 20):
-(id)initWithNumberOfRows:(NSUInteger)numberOfRows
andNumberOfColumns:(NSUInteger)numberOfColumns
{
self = [super init];
if (self)
{
_numberOfRows = numberOfRows;
_numberOfColumns = numberOfColumns;
_cellWidth = 80.0f;
_cellHeight = 80.0f;
_cellWidthScale = 1.0f;
_cellHeightScale = 1.0f;
}
return self;
}
-(void)updateCellWidthScale:(CGFloat)newWidthScale
{
self.cellWidthScale *= newWidthScale;
}
-(void)updateCellHeightScale:(CGFloat)newHeightScale
{
self.cellHeightScale *= newHeightScale;
}
-(CGSize)collectionViewContentSize
{
CGSize returnValue = CGSizeMake(self.numberOfColumns * self.cellWidth * self.cellWidthScale,
self.numberOfRows * self.cellHeight * self.cellHeightScale);
return returnValue;
}
- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)path
{
UICollectionViewLayoutAttributes* attributes = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:path];
CGRect rect = [self frameForItemAtIndexPath:path];
attributes.size = CGSizeMake(rect.size.width, rect.size.height);
attributes.center = CGPointMake(rect.origin.x + (0.5f * rect.size.width),
rect.origin.y + (0.5f * rect.size.height));
return attributes;
}
-(NSArray*)layoutAttributesForElementsInRect:(CGRect)rect
{
NSMutableArray *returnValue = [[NSMutableArray alloc] init];
for (NSInteger i=0; i < self.numberOfRows; i++)
{
for (NSInteger j=0; j < self.numberOfColumns; j++)
{
NSIndexPath* indexPath = [NSIndexPath indexPathForItem:j inSection:i];
CGRect frame = [self frameForItemAtIndexPath:indexPath];
if (CGRectIntersectsRect(frame, rect))
{
[returnValue addObject:[self layoutAttributesForItemAtIndexPath:indexPath]];
}
}
}
return returnValue;
}
- (CGRect)frameForItemAtIndexPath:(NSIndexPath *)indexPath
{
CGRect returnValue = CGRectMake(indexPath.section * self.cellWidth * self.cellWidthScale,
indexPath.row * self.cellHeight * self.cellHeightScale,
self.cellWidth * self.cellWidthScale,
self.cellHeight * self.cellHeightScale);
return returnValue;
}
You need to set your collectionView contentOffset to the value it was before starting to zoom multiplied the gestures scale.
Your pinch recognizer method should look like this (you need to add some more code to stop changing contentOffset when reaching the MAXIMUM_SCALE or MINIMUM_SCALE).
- (void)didReceivePinchGesture:(UIPinchGestureRecognizer*)gesture
{
static CGFloat scaleStart;
static CGPoint p;
if (gesture.state == UIGestureRecognizerStateBegan)
{
scaleStart = self.scale;
p = self.collectionView.contentOffset;
}
else if (gesture.state == UIGestureRecognizerStateChanged)
{
CGFloat tempScale = scaleStart * gesture.scale;
if (tempScale < MINMUM_SCALE)
{
self.scale = MINMUM_SCALE;
}
else if (tempScale > MAXIMUM_SCALE)
{
self.scale = MAXIMUM_SCALE;
}
else
{
self.scale = tempScale ;
}
dispatch_async(dispatch_get_main_queue(), ^{
[self.collectionView.collectionViewLayout invalidateLayout];
self.collectionView.contentOffset = CGPointMake(0, p.y * gesture.scale);
});
}
}

Creating a stretchy UICollectionView like Evernote on iOS 7

I've been working on trying to recreate the stretchy collection view that Evernote uses in iOS 7 and I'm really close to having it working. I've managed to create a custom collection view flow layout that modifies the layout attribute transforms when the content offset y value lies outside collection view bounds. I'm modifying the layout attributes in the layoutAttributesForElementsInRect method and it behaves as expected except that the bottom cells can disappear when you hit the bottom of the scroll view. The further you pull the content offset the more cells can disappear. I think the cells basically get clipped off. It doesn't happen at the top though and I'd expect to see the same behavior in both places. Here's what my flow layout implementation looks like right now.
#implementation CNStretchyCollectionViewFlowLayout
{
BOOL _transformsNeedReset;
CGFloat _scrollResistanceDenominator;
}
- (id)init
{
self = [super init];
if (self)
{
// Set up the flow layout parameters
self.minimumInteritemSpacing = 10;
self.minimumLineSpacing = 10;
self.itemSize = CGSizeMake(320, 44);
self.sectionInset = UIEdgeInsetsMake(10, 0, 10, 0);
// Set up ivars
_transformsNeedReset = NO;
_scrollResistanceDenominator = 800.0f;
}
return self;
}
- (void)prepareLayout
{
[super prepareLayout];
}
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
{
// Set up the default attributes using the parent implementation
NSArray *items = [super layoutAttributesForElementsInRect:rect];
// Compute whether we need to adjust the transforms on the cells
CGFloat collectionViewHeight = self.collectionViewContentSize.height;
CGFloat topOffset = 0.0f;
CGFloat bottomOffset = collectionViewHeight - self.collectionView.frame.size.height;
CGFloat yPosition = self.collectionView.contentOffset.y;
// Update the transforms if necessary
if (yPosition < topOffset)
{
// Compute the stretch delta
CGFloat stretchDelta = topOffset - yPosition;
NSLog(#"Stretching Top by: %f", stretchDelta);
// Iterate through all the visible items for the new bounds and update the transform
for (UICollectionViewLayoutAttributes *item in items)
{
CGFloat distanceFromTop = item.center.y;
CGFloat scrollResistance = distanceFromTop / 800.0f;
item.transform = CGAffineTransformMakeTranslation(0, -stretchDelta + (stretchDelta * scrollResistance));
}
// Update the ivar for requiring a reset
_transformsNeedReset = YES;
}
else if (yPosition > bottomOffset)
{
// Compute the stretch delta
CGFloat stretchDelta = yPosition - bottomOffset;
NSLog(#"Stretching bottom by: %f", stretchDelta);
// Iterate through all the visible items for the new bounds and update the transform
for (UICollectionViewLayoutAttributes *item in items)
{
CGFloat distanceFromBottom = collectionViewHeight - item.center.y;
CGFloat scrollResistance = distanceFromBottom / 800.0f;
item.transform = CGAffineTransformMakeTranslation(0, stretchDelta + (-stretchDelta * scrollResistance));
}
// Update the ivar for requiring a reset
_transformsNeedReset = YES;
}
else if (_transformsNeedReset)
{
NSLog(#"Resetting transforms");
_transformsNeedReset = NO;
for (UICollectionViewLayoutAttributes *item in items)
item.transform = CGAffineTransformIdentity;
}
return items;
}
- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds
{
// Compute whether we need to adjust the transforms on the cells
CGFloat collectionViewHeight = self.collectionViewContentSize.height;
CGFloat topOffset = 0.0f;
CGFloat bottomOffset = collectionViewHeight - self.collectionView.frame.size.height;
CGFloat yPosition = self.collectionView.contentOffset.y;
// Handle cases where the layout needs to be rebuilt
if (yPosition < topOffset)
return YES;
else if (yPosition > bottomOffset)
return YES;
else if (_transformsNeedReset)
return YES;
return NO;
}
#end
I also zipped up the project for people to try out. Any help would be greatly appreciated as I'm pretty new to creating custom collection view layouts. Here's the link to it:
https://dl.dropboxusercontent.com/u/2975688/StackOverflow/stretchy_collection_view.zip
Thanks everyone!
I was able to solve the problem. I'm not sure if there's actually a bug in iOS or not, but the issue was that the cells were actually getting translated outside the content view of the collection view. Once the cell would get translated far enough, it would get clipped off. I find it interesting that this does not happen in the simulator for non-retina displays, but does with retina displays which is why I feel this may actually be a bug.
With that in mind, a workaround for now is to add padding to the top and bottom of the collection view by overriding the collectionViewContentSize method. Once you do this, if you add padding to the top, you need to adjust the layout attributes for the cells as well so they are in the proper location. The final step is to set the contentInset on the collection view itself to adjust for the padding. Leave the scroll indicator insets alone since those are fine. Here's the implementation of my final collection view controller and the custom flow layout.
CNStretchyCollectionViewController.m
#implementation CNStretchyCollectionViewController
static NSString *CellIdentifier = #"Cell";
- (void)viewDidLoad
{
// Register the cell
[self.collectionView registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:CellIdentifier];
// Tweak out the content insets
CNStretchyCollectionViewFlowLayout *layout = (CNStretchyCollectionViewFlowLayout *) self.collectionViewLayout;
self.collectionView.contentInset = layout.bufferedContentInsets;
// Set the delegate for the collection view
self.collectionView.delegate = self;
self.collectionView.clipsToBounds = NO;
// Customize the appearance of the collection view
self.collectionView.backgroundColor = [UIColor whiteColor];
self.collectionView.indicatorStyle = UIScrollViewIndicatorStyleDefault;
}
#pragma mark - UICollectionViewDataSource Methods
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
{
return 20;
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:CellIdentifier forIndexPath:indexPath];
if ([indexPath row] % 2 == 0)
cell.backgroundColor = [UIColor orangeColor];
else
cell.backgroundColor = [UIColor blueColor];
return cell;
}
#end
CNStretchyCollectionViewFlowLayout.m
#interface CNStretchyCollectionViewFlowLayout ()
- (CGSize)collectionViewContentSizeWithoutOverflow;
#end
#pragma mark -
#implementation CNStretchyCollectionViewFlowLayout
{
BOOL _transformsNeedReset;
CGFloat _scrollResistanceDenominator;
UIEdgeInsets _contentOverflowPadding;
}
- (id)init
{
self = [super init];
if (self)
{
// Set up the flow layout parameters
self.minimumInteritemSpacing = 10;
self.minimumLineSpacing = 10;
self.itemSize = CGSizeMake(320, 44);
self.sectionInset = UIEdgeInsetsMake(10, 0, 10, 0);
// Set up ivars
_transformsNeedReset = NO;
_scrollResistanceDenominator = 800.0f;
_contentOverflowPadding = UIEdgeInsetsMake(100.0f, 0.0f, 100.0f, 0.0f);
_bufferedContentInsets = _contentOverflowPadding;
_bufferedContentInsets.top *= -1;
_bufferedContentInsets.bottom *= -1;
}
return self;
}
- (void)prepareLayout
{
[super prepareLayout];
}
- (CGSize)collectionViewContentSize
{
CGSize contentSize = [super collectionViewContentSize];
contentSize.height += _contentOverflowPadding.top + _contentOverflowPadding.bottom;
return contentSize;
}
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
{
// Set up the default attributes using the parent implementation (need to adjust the rect to account for buffer spacing)
rect = UIEdgeInsetsInsetRect(rect, _bufferedContentInsets);
NSArray *items = [super layoutAttributesForElementsInRect:rect];
// Shift all the items down due to the content overflow padding
for (UICollectionViewLayoutAttributes *item in items)
{
CGPoint center = item.center;
center.y += _contentOverflowPadding.top;
item.center = center;
}
// Compute whether we need to adjust the transforms on the cells
CGFloat collectionViewHeight = [self collectionViewContentSizeWithoutOverflow].height;
CGFloat topOffset = _contentOverflowPadding.top;
CGFloat bottomOffset = collectionViewHeight - self.collectionView.frame.size.height + _contentOverflowPadding.top;
CGFloat yPosition = self.collectionView.contentOffset.y;
// Update the transforms if necessary
if (yPosition < topOffset)
{
// Compute the stretch delta
CGFloat stretchDelta = topOffset - yPosition;
NSLog(#"Stretching Top by: %f", stretchDelta);
// Iterate through all the visible items for the new bounds and update the transform
for (UICollectionViewLayoutAttributes *item in items)
{
CGFloat distanceFromTop = item.center.y - _contentOverflowPadding.top;
CGFloat scrollResistance = distanceFromTop / _scrollResistanceDenominator;
item.transform = CGAffineTransformMakeTranslation(0, -stretchDelta + (stretchDelta * scrollResistance));
}
// Update the ivar for requiring a reset
_transformsNeedReset = YES;
}
else if (yPosition > bottomOffset)
{
// Compute the stretch delta
CGFloat stretchDelta = yPosition - bottomOffset;
NSLog(#"Stretching bottom by: %f", stretchDelta);
// Iterate through all the visible items for the new bounds and update the transform
for (UICollectionViewLayoutAttributes *item in items)
{
CGFloat distanceFromBottom = collectionViewHeight + _contentOverflowPadding.top - item.center.y;
CGFloat scrollResistance = distanceFromBottom / _scrollResistanceDenominator;
item.transform = CGAffineTransformMakeTranslation(0, stretchDelta + (-stretchDelta * scrollResistance));
}
// Update the ivar for requiring a reset
_transformsNeedReset = YES;
}
else if (_transformsNeedReset)
{
NSLog(#"Resetting transforms");
_transformsNeedReset = NO;
for (UICollectionViewLayoutAttributes *item in items)
item.transform = CGAffineTransformIdentity;
}
return items;
}
- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds
{
return YES;
}
#pragma mark - Private Methods
- (CGSize)collectionViewContentSizeWithoutOverflow
{
return [super collectionViewContentSize];
}
#end
CNStretchyCollectionViewFlowLayout.h
#interface CNStretchyCollectionViewFlowLayout : UICollectionViewFlowLayout
#property (assign, nonatomic) UIEdgeInsets bufferedContentInsets;
#end
I'm actually going to through this onto Github and I'll post a link to the project once it's up. Thanks again everyone!

UICollectionViewFlowLayout subclass causes cell to disappear

I am trying to offset the center on y axis, of all cells below selected cell. I added a property to CollectionViewFlowLayout subclass, called extendedCell, which marks the cell below which I should offset everything else.
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
{
NSArray *attributes = [super layoutAttributesForElementsInRect:rect];
if(!self.extendedCell)
{
return attributes;
}
else
{
return [self offsetCells:attributes];
}
}
- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath
{
UICollectionViewLayoutAttributes *attribute = [super layoutAttributesForItemAtIndexPath:indexPath];
if(!self.extendedCell)
{
return attribute;
}
else
{
if(![attribute.indexPath isEqual:self.extendedCell] &&
attribute.center.y >= [super layoutAttributesForItemAtIndexPath:self.extendedCell].center.y)
{
CGPoint newCenter = CGPointMake(attribute.center.x,
attribute.center.y + 180.f);
attribute.center = newCenter;
}
return attribute;
}
}
-(NSArray *)offsetCells:(NSArray *)layoutAttributes
{
for(UICollectionViewLayoutAttributes *attribute in layoutAttributes)
{
if(![attribute.indexPath isEqual:self.extendedCell] &&
attribute.center.y >= [super layoutAttributesForItemAtIndexPath:self.extendedCell].center.y)
{
CGPoint newCenter = CGPointMake(attribute.center.x,
attribute.center.y + 180.0f);
attribute.center = newCenter;
}
}
return layoutAttributes;
}
Turns out that something bad happens on the way, as cells at the bottom disappear. I have a feeling that this has something to do with cells being outside UICollectionView content size, but setting the size while generating layout does not help. Any ideas how to fix that disappearance?
OK turns out I found the bug. Seems that overriding -(CGSize)collectionViewContentSize helps. If any cells lie outside the content size of the collection view they simply disappear. As the content size is set for good before any layout attributes calls, and cells are not allowed to be placed outside of it, collection view just gets rid of them. I thought the content size is based upon cells attributes after they've been set, this is not the case.
-(CGSize)collectionViewContentSize
{
if(self.extendedCell)
{
CGSize size = [super collectionViewContentSize];
return CGSizeMake(size.width, size.height + 180);
}
else
{
return [super collectionViewContentSize];
}
}
Solved it by breaking big cells into minor cells an connected them with a view. Very hacky, but iOS7 will hopefully help.
Check if any returned size of cell have one of border size equals 0.
In my case disappear cause was sizeForItem returned {320,0}
Why UICollectionView with UICollectionViewFlowLayout not show cells, but ask for size?
And for correct size view from nib (with used autolayouts) i use:
+ (CGSize)sizeForViewFromNib:(NSString *)nibName width:(CGFloat)width userData:(id)userData {
UIView *view = viewFromNib(nibName, nil);
[view configForUserData:userData];
CGSize size = [view systemLayoutSizeFittingSize:CGSizeMake(width, SOME_MIN_SIZE) withHorizontalFittingPriority:UILayoutPriorityRequired verticalFittingPriority:UILayoutPriorityFittingSizeLevel];
return CGSizeMake(width, size.height);
}

Resources