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!
Related
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);
});
}
}
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;
}
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!
I have a horizontal UICollectionView with a UICollectionViewCell with a horizontally and vertically centered UILabel. As the label contains text of different lenght I calculate the width of the label like so. I give it a little margin (10) here to be on the save side.
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath
{
NSString *text = [_collectionViewProvider collectionViewController:self titleForRow:indexPath.row];
CGSize size = CGSizeMake(MIN(_maxWidth, [text sizeWithFont:self.font].width + 16 + 10), CGRectGetHeight(collectionView.bounds));
return size;
}
As I like to have the users having the impression of a horizontal scroll wheel I apply a transform and adjust the zIndex of the cells depending on the distance from the center. It works almost as expected but as the frame width gets shrinked when the transform is apllied on the far left/right I have the effect that the text gets clipped which looks rather strange.
Here is an image that shows the effect and when the cell is in the center.
Has anybody an idea how to solve this?
Here is my code to calculate the layout.
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
{
NSArray *array = [super layoutAttributesForElementsInRect:rect];
CGRect visibleRect = CGRectZero;
visibleRect.origin = self.collectionView.contentOffset;
visibleRect.size = self.collectionView.bounds.size;
CGFloat activeDistance = floorf(CGRectGetWidth(visibleRect) / 2);
CGFloat midX = ceilf(CGRectGetMidX(visibleRect));
CGFloat baseAngle = M_PI/180 * 40;
for (UICollectionViewLayoutAttributes *attributes in array)
{
if (CGRectContainsRect(visibleRect, attributes.frame)) {
CGFloat distance = midX - attributes.center.x;
attributes.alpha = (activeDistance - ABS(distance))/activeDistance;
CGFloat value = -distance/activeDistance;
value *= baseAngle;
attributes.transform3D = CATransform3DMakeRotation(value, 0, 1, 0);
attributes.zIndex = ABS(distance)/activeDistance * 200;
}
else {
attributes.alpha = 0;
}
}
return array;
}
I present a view controller with a uicollectionview. I want the cells to animate in from the top.
My layout is a sublclass of flowlayout, so I override this method:
- (UICollectionViewLayoutAttributes *) initialLayoutAttributesForAppearingItemAtIndexPath:(NSIndexPath *)indexPath {
UICollectionViewLayoutAttributes* attributes = [super initialLayoutAttributesForAppearingItemAtIndexPath:indexPath];
CGFloat height = [self collectionViewContentSize].height;
attributes.transform3D = CATransform3DMakeTranslation(0, -height, 0);
return attributes;
}
It almost works, but i see the cells appear (flash) momentarily on the screen before they animate in.
Any ideas why they appear in their final location before the transform is applied, and how I might prevent this?
Rather than setting the transform, change the center:
- (UICollectionViewLayoutAttributes *) initialLayoutAttributesForAppearingItemAtIndexPath:(NSIndexPath *)indexPath {
UICollectionViewLayoutAttributes* attributes = [super initialLayoutAttributesForAppearingItemAtIndexPath:indexPath];
CGPoint c = attributes.center;
c.y -= self.collectionViewContentSize.height;
attributes.center = c;
return attributes;
}
Changing the transform can always cause problems with determining visibility, because doing so invalidates the frame property.