UICollectionViewCell disappears in iOS 8 - ios

I have a collectionView which works well in iOS 7 and now in iOS 8 is acts strangely.
when collectionView appears it only displays one cell: (it must be 2)
but after scrolling a bit the second cell appears
Im using a custom collectionViewFlowLayout. but changing to UICollectionViewFlowLayout doesn't fix the issue.
Cell Size : 657, 500
Min Spacing For Lines : 100
Min Spacing For Cells : 10
I have added left and right edge insets: (if I remove the insets it works well. but I must use insets to keep my cell at the center of view)
- (UIEdgeInsets)collectionView:(UICollectionView *)cv
layout:(UICollectionViewLayout *)cvl
insetForSectionAtIndex:(NSInteger)section {
return UIEdgeInsetsMake(0, (cv.bounds.size.width - 657) / 2.0f, 0,
(cv.bounds.size.width - 657) / 2.0f);
}
Here is my custom flow layout:
#import "CoverFlowLayout.h"
static const CGFloat kMaxDistancePercentage = 0.3f;
//static const CGFloat kMaxRotation = (CGFloat)(50.0 * (M_PI / 180.0));
static const CGFloat kMaxZoom = 0.1f;
#implementation CoverFlowLayout
- (id)init {
if ((self = [super init])) {
self.scrollDirection = UICollectionViewScrollDirectionHorizontal;
self.minimumLineSpacing = 10000.0f; }
return self;
}
- (NSArray*)layoutAttributesForElementsInRect:(CGRect)rect {
// 1
CGRect visibleRect =
(CGRect){.origin = self.collectionView.contentOffset,
.size = self.collectionView.bounds.size};
CGFloat maxDistance =
visibleRect.size.width * kMaxDistancePercentage;
// 2
NSArray *array = [super layoutAttributesForElementsInRect:rect];
for (UICollectionViewLayoutAttributes *attributes in array) {
// 3
CGFloat distance =
CGRectGetMidX(visibleRect) - attributes.center.x;
// 4
CGFloat normalizedDistance = distance / maxDistance;
normalizedDistance = MIN(normalizedDistance, 1.0f);
normalizedDistance = MAX(normalizedDistance, -1.0f);
// 5
//CGFloat rotation = normalizedDistance * kMaxRotation;
CGFloat zoom = 1.0f + ((1.0f - ABS(normalizedDistance)) * kMaxZoom);
// 6
CATransform3D transform = CATransform3DIdentity;
transform.m34 = 1.0 / -1000.0;
//transform = CATransform3DRotate(transform,
// rotation, 0.0f, 1.0f, 0.0f);
transform = CATransform3DScale(transform, zoom, zoom, zoom);
attributes.transform3D = transform;
}
// 7
return array;
}
- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds {
return YES;
}
- (CGPoint)targetContentOffsetForProposedContentOffset: (CGPoint)proposedContentOffset withScrollingVelocity:(CGPoint)velocity
{
// 1
CGFloat offsetAdjustment = CGFLOAT_MAX;
CGFloat horizontalCenter = proposedContentOffset.x +
(CGRectGetWidth(self.collectionView.bounds) / 2.0f);
// 2
CGRect targetRect = CGRectMake(proposedContentOffset.x,
0.0f, self.collectionView.bounds.size.width, self.collectionView.bounds.size.height);
NSArray *array =
[super layoutAttributesForElementsInRect:targetRect];
for (UICollectionViewLayoutAttributes* layoutAttributes in array)
{
// 3
CGFloat distanceFromCenter = layoutAttributes.center.x - horizontalCenter;
if (ABS(distanceFromCenter) < ABS(offsetAdjustment))
{
offsetAdjustment = distanceFromCenter;
}
}
// 4
return CGPointMake(proposedContentOffset.x + offsetAdjustment,
proposedContentOffset.y);
}
initially in overrided layoutAttributesForElementsInRect visible rect is {0,0, 1024, 768}
but [super layoutAttributesForElementsInRect:rect]; returns only one UICollectionViewCellAttribute. (it should be 2)
is any idea how can I fix this?

I don know How it can be cause of the issue but it was originated from:
NSIndexPath *visibleIndexPath = [self.collectionView indexPathForItemAtPoint:midPoint];
I want to update my pageControl to indicate which cell is at the center of screen.
I Changed my method and now it works well:
//*****updating page control*****
// get the visible rect
CGRect visibleRect = (CGRect) {.origin = self.collectionView.contentOffset,
.size = self.collectionView.bounds.size};
// get the mid point in the visible rect
CGPoint midPoint = CGPointMake(CGRectGetMidX(visibleRect), CGRectGetMidY(visibleRect));
// find indexPath of the item in that midPoint
//in iOS 8 Cause the second cell disappear
//NSIndexPath *visibleIndexPath = [self.collectionView indexPathForItemAtPoint:midPoint];
//iterating through visble cells to find the cell which contains midpoint then get get that cell indexpath
for (UICollectionViewCell *cell in [self.collectionView visibleCells]) {
if (CGRectContainsPoint(cell.frame, midPoint)) {
NSIndexPath *indexPath = [self.collectionView indexPathForCell:cell];
//update page control
self.pageControl.currentPage = indexPath.row;
//quiting loop
break;
}
}

Related

UICollectionView sticky column glitch with contentOffset

Issue
I've created a UICollectionViewFlowLayout that uses a sticky top row and sticky left column. With this layout i've also added a pinch gesture to the collectionView to enable zooming.
The zooming works perfect when the collectionView isn't scrolled all the way to the right, or the bottom; when this is true the contentOffset.x and contentOffset.y are behaving weirdly.
Can anyone spot the problem? I've been trying to debug this issue for some time now.
Here's a gif representation:
Code
Grid
- (void)actionPinchGesture:(UIPinchGestureRecognizer *)gesture {
MyCustomLayout *layout = (MyCustomLayout *)self.gridView.collectionViewLayout;
if (gesture.state == UIGestureRecognizerStateBegan) {
[layout setPreviousPinchValue:gesture.scale];
}
if (gesture.state == UIGestureRecognizerStateChanged) {
[layout setPinchValue:gesture.scale];
[layout updatePinchValues];
}
}
MyCustomLayout.h
#property (nonatomic, assign) previousPinchValue;
#property (nonatomic, assign) pinchValue;
- (void)updateGridScale;
MyCustomLayout.m
#interface MyCustomLayout() {
CGFloat zoomLevel; // stored zoom level.
CGFloat cachedSectionWidth; // stored original section width.
CGFloat cachedColumnWidth; // stored original column width.
CGFloat cachedRowHeight; // stored row height.
// these values above are cached when the grid is setup for the first time
}
#end
#implementation MyCustomLayout
- (void)updateGridScale {
float scaleDeltaFactor = self.pinchValue / self.previousPinchValue;
float currentZoom = self->zoomLevel;
float newZoom = currentZoom * scaleDeltaFactor;
float kMaxZoom = 2.0;
float kMinZoom = 0.47;
newZoom = MAX(kMinZoom, MIN(newZoom, kMaxZoom));
self->zoomLevel = newZoom;
self.previousPinchValue = self.pinchValue;
CGFloat sectionWidth = self->cachedSectionWidth;
CGFloat columnWidth = self->cachedColumnWidth;
CGFloat rowHeight = self->cachedRowHeight;
sectionWidth = sectionWidth * newZoom;
columnWidth = columnWidth * newZoom;
rowHeight = rowHeight * newZoom;
if (newZoom == 1 || newZoom == 0) {
sectionWidth = self->cachedSectionWidth;
columnWidth = self->cachedColumnWidth;
}
self.topColumnWidth = columnWidth;
self.sectionHeight = rowHeight;
[self invalidateLayoutCache];
[self.collectionView reloadData];
}
- (void)prepareLayout {
[super prepareLayout];
[self prepareAttributesForRows:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, self.collectionView.numberOfSections)]];
[self.allAttributes addObjectsFromArray:self.sectionAttributes];
}
- (void)prepareAttributesForRows:(NSIndexSet *)sectionIndexes {
NSIndexPath *sectionIndexPath = [NSIndexPath indexPathForItem:0 inSection:section];
CGFloat sectionMinY = self.hourHeaderHeight + self.contentMargin.top;
CGFloat sectionMinX = (self.collectionView.contentOffset.x - 0.5) - self.collectionView.contentInset.left;
CGFloat sectionY = sectionMinY + ((self.sectionHeight + self.sectionGap) * section);
CGFloat height = self.sectionHeight + 1;
UICollectionViewLayoutAttributes *atts = [self layoutAttributesForSupplementaryViewAtIndexPath:sectionIndexPath ofKind:INSEPGLayoutElementKindSectionHeader withItemCache:self.sectionHeaderAttributes];
atts.frame = CGRectMake(sectionMinX, sectionY, self.sectionHeaderWidth + 1.5, height);
atts.zIndex = [self zIndexForElementKind:INSEPGLayoutElementKindSectionHeader floating:YES];
NSString *indexPathString = [NSString stringWithFormat:#"%li", sectionIndexPath.section];
NSNumber *y_value = [NSNumber numberWithFloat:round(sectionY)];
if (![[self.cachedSectionInfo allKeys] containsObject:indexPathString]) {
[self.cachedSectionInfo setObject:y_value forKey:indexPathString];
}
}
#end

Horizontal UitableView with Auto Layout

I am implementing a horizontal UITableView for showing some Sticker category . Here is my code for rotation of Table View.
- (void)refreshOrientation
{
if (!self.tableView)
return;
// First reset rotation
self.tableView.transform = CGAffineTransformIdentity;
self.tableView.frame = CGRectMake(0, 0, self.bounds.size.width, self.bounds.size.height);
// Adjust frame
int xOrigin = (self.bounds.size.width - self.bounds.size.height) / 2.0;
int yOrigin = (self.bounds.size.height - self.bounds.size.width) / 2.0;
self.tableView.frame = CGRectMake(xOrigin, yOrigin, self.bounds.size.height, self.bounds.size.width);
// Apply rotation again
self.tableView.transform = CGAffineTransformMakeRotation(-M_PI/2);
self.tableView.scrollIndicatorInsets = UIEdgeInsetsMake(0.0, 0.0, 0.0, self.bounds.size.height - 7.0);
}
And Rotation of Table View Cell Contents
- (UITableViewCell *)tableView:(UITableView *)aTableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell * cell = [self.delegate tableView:self cellForRowAtIndexPath:indexPath];
// Rotate if needed
if (CGAffineTransformEqualToTransform(cell.contentView.transform, CGAffineTransformIdentity))
{
int xOrigin = (cell.bounds.size.width - cell.bounds.size.height) / 2.0;
int yOrigin = (cell.bounds.size.height - cell.bounds.size.width) / 2.0;
cell.contentView.frame = CGRectMake(xOrigin, yOrigin, cell.bounds.size.height, cell.bounds.size.width);
cell.contentView.transform = CGAffineTransformMakeRotation(M_PI/2.0);
}
return cell;
}
Its working fine without auto layout. but if i use auto layout constraints it got Crooked . Thanks in advance if anyone can help me understand the rotation concept in auto layout .

UICollectionView find center cell when content inset is used

I'm trying to figure out how to find the center most cell of my UICollectionView, which is set to only scroll horizontally.
I tried this SO: how to get indexPath for cell which is located in the center of UICollectionView but it isn't working out, I think because I'm using the contentInset property of my UICollectionView.
What I'm doing is setting the contentInset left and right of the flow layout to exactly 1/2 of self.view's bounds width minus 1/2 of itemSize width. I'm not sure how to calculate the center most cell due to this. I'd appreciate any help offered. Code:
CGPoint cellCenter = CGPointMake((self.collectionView.center.x +
self.collectionView.contentOffset.x) -
(self.sampleFlowLayout.sectionInset.left +
self.sampleFlowLayout.sectionInset.right), self.collectionView.center.y);
NSIndexPath *indexPath = [self.collectionView indexPathForItemAtPoint:cellCenter];
if (indexPath)
{
NSInteger tag = [self.collectionView cellForItemAtIndexPath:indexPath].tag;
if (tag != self.currentSample)
{
self.currentSample = tag;
self.imageView.image = [self sampleImageWithOption:self.currentSample];
}
}
- (void)viewDidLoad
{
self.sampleFlowLayout = [[UICollectionViewFlowLayout alloc]init];
CGSize itemSize = CGSizeMake(63, 63);
self.sampleFlowLayout.scrollDirection = UICollectionViewScrollDirectionHorizontal;
self.sampleFlowLayout.sectionInset = UIEdgeInsetsMake(0, (self.view.bounds.size.width / 2) - (itemSize.width / 2), 0, (self.view.bounds.size.width / 2) - (itemSize.width / 2));
}
I figured this out myself using help from this answer: https://stackoverflow.com/a/22696037/1533438
Final code:
- (void)snapCollectionView:(UICollectionView *)collectionView withProposedOffset:(CGPoint)proposedOffset
{
CGRect cvBounds = collectionView.bounds;
CGFloat halfWidth = cvBounds.size.width * 0.5;
CGFloat proposedContentOffsetCenterX = proposedOffset.x + halfWidth;
NSArray *attributesArray = [collectionView.collectionViewLayout layoutAttributesForElementsInRect:cvBounds];
UICollectionViewLayoutAttributes *candidateAttributes;
for (UICollectionViewLayoutAttributes *attributes in attributesArray)
{
if (attributes.representedElementCategory !=
UICollectionElementCategoryCell)
{
continue;
}
if (!candidateAttributes)
{
candidateAttributes = attributes;
continue;
}
if (fabsf(attributes.center.x - proposedContentOffsetCenterX) <
fabsf(candidateAttributes.center.x - proposedContentOffsetCenterX))
{
candidateAttributes = attributes;
}
}
CGPoint offset = CGPointMake(candidateAttributes.center.x - halfWidth, proposedOffset.y);
[collectionView setContentOffset:offset animated:YES];
CGPoint cellCenter = CGPointMake(offset.x + self.sampleCollectionView.bounds.size.width / 2.0, self.sampleCollectionView.bounds.size.height / 2.0);
NSIndexPath *indexPath = [self.sampleCollectionView indexPathForItemAtPoint:cellCenter];
if (indexPath)
{
NSInteger tag = [self.sampleCollectionView cellForItemAtIndexPath:indexPath].tag;
if (tag != self.currentSample)
{
self.currentSample = tag;
UIImage *image;
if (self.currentSample == 0)
{
image = self.image;
}
else
{
image = [self sampleImageWithOption:self.currentSample];
}
dispatch_async(dispatch_get_main_queue(),
^{
self.imageView.image = image;
});
}
}
}

Transforming UICollectionViewCell causes touch events not be captured

Im trying to create a horizontal collection view by subclassing UICollectionViewFlowLayout.
which the center cell be scaled a little more to be focused on the view.
see the screen shot:
but UICollectionViewCell doesn't capture touch events. I mean when I tap on the cell the delegate method - (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath won't called.
but when I remove the scale transform from the cell, the above method called!
#import "CoverFlowLayout.h"
static const CGFloat kMaxDistancePercentage = 0.3f;
static const CGFloat kMaxRotation = (CGFloat)(50.0 * (M_PI / 180.0));
static const CGFloat kMaxZoom = 0.1f;
#implementation CoverFlowLayout
- (id)init {
if ((self = [super init])) {
self.scrollDirection = UICollectionViewScrollDirectionHorizontal;
self.minimumLineSpacing = 10000.0f; }
return self;
}
- (NSArray*)layoutAttributesForElementsInRect:(CGRect)rect {
// 1
CGRect visibleRect =
(CGRect){.origin = self.collectionView.contentOffset,
.size = self.collectionView.bounds.size};
CGFloat maxDistance =
visibleRect.size.width * kMaxDistancePercentage;
// 2
NSArray *array =
[super layoutAttributesForElementsInRect:rect];
for (UICollectionViewLayoutAttributes *attributes in array) {
// 3
CGFloat distance =
CGRectGetMidX(visibleRect) - attributes.center.x;
// 4
CGFloat normalizedDistance = distance / maxDistance;
normalizedDistance = MIN(normalizedDistance, 1.0f);
normalizedDistance = MAX(normalizedDistance, -1.0f);
// 5
CGFloat rotation = normalizedDistance * kMaxRotation;
CGFloat zoom = 1.0f + ((1.0f - ABS(normalizedDistance)) * kMaxZoom);
// 6
CATransform3D transform = CATransform3DIdentity;
transform.m34 = 1.0 / -1000.0;
//transform = CATransform3DRotate(transform,
// rotation, 0.0f, 1.0f, 0.0f);
transform = CATransform3DScale(transform, zoom, zoom, 0.0f);
attributes.transform3D = transform;
}
// 7
return array;
}
- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds {
return YES;
}
- (CGPoint)targetContentOffsetForProposedContentOffset: (CGPoint)proposedContentOffset withScrollingVelocity:(CGPoint)velocity
{
// 1
CGFloat offsetAdjustment = CGFLOAT_MAX;
CGFloat horizontalCenter = proposedContentOffset.x +
(CGRectGetWidth(self.collectionView.bounds) / 2.0f);
// 2
CGRect targetRect = CGRectMake(proposedContentOffset.x,
0.0f, self.collectionView.bounds.size.width, self.collectionView.bounds.size.height);
NSArray *array =
[super layoutAttributesForElementsInRect:targetRect];
for (UICollectionViewLayoutAttributes* layoutAttributes in array)
{
// 3
CGFloat distanceFromCenter = layoutAttributes.center.x - horizontalCenter;
if (ABS(distanceFromCenter) < ABS(offsetAdjustment))
{
offsetAdjustment = distanceFromCenter;
}
}
// 4
return CGPointMake(proposedContentOffset.x + offsetAdjustment,
proposedContentOffset.y);
}
any idea?
You don't need to scale on z-axis, the problem is caused because you were scaling on z to zero
The scale on z-axis should be greater than zero; Making it zero causes the button to have "no depth", so the touches are not recognized. (Although you can still see the button)
Scaling is a computed using a multiplication, so in order to cause "no transformation on z-axis", the value should be 1, rather than 0
I had to scale the cell along the Z axis too:
transform = CATransform3DScale(transform, zoom, zoom, zoom);
the cell touch events works.
does anyone knows why scaling along Z axis is necessary?

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!

Resources