So I'm using the following code to get an invisible header for my
UITableView in order to center my UITableViewCells vertically.
- (CGFloat)tableView:(nonnull UITableView *)tableView heightForHeaderInSection:(NSInteger)section
{
CGFloat contentHeight = 0.0;
for (int i = 0; i < [self numberOfSectionsInTableView:tableView]; ++i) {
for (int j = 0; j < [self tableView:tableView numberOfRowsInSection:section]; ++j) {
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:j inSection:i];
contentHeight += [self tableView:tableView heightForRowAtIndexPath:indexPath];
}
}
NSLog(#"%f", tableView.bounds.size.height);
NSLog(#"%f", contentHeight);
NSLog(#"%f", (tableView.bounds.size.height - contentHeight) / 2);
return (tableView.bounds.size.height - contentHeight) / 2;
}
However, the result returned by NSLog is so weird
I did not override tableView:heightForRowAtIndexPath and there's only one section. How come contentHeight has negative values and why was the method run multiple times?
Related
I created a custom UICollectionViewLayout and I've put it in my Collection View Controller in the storyboard. I didn't get that much problems on displaying the items/cells. My problem is that the viewForSupplementaryElementOfKind is not being called. I can't to seem to pinpoint on why is that. I tried to go back to the default layout and it does call it then but I need my custom UICollectionViewLayout. I've left NSLogs to check and viewForSupplementaryElementOfKind is not being called.
Here is my MyCollectionViewController.m
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
viewArray = [NSMutableArray array];
//setup the delegate for vertical flow layout
[(VerticalFlowLayout*)self.collectionViewLayout setDelegate:self];
//register the headers
[self.collectionView registerNib:[ButtonHeaderCollectionReusableView nib] forSupplementaryViewOfKind:UICollectionElementKindSectionHeader withReuseIdentifier:[ButtonHeaderCollectionReusableView reusableID]];
//set the insets
[self.collectionView setContentInset:UIEdgeInsetsMake(23, 5, 10, 5)];
}
#pragma mark - CollectionView DataSource
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
return [self.cardsArray count];
}
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView {
NSLog(#"Number of Sections");
return 1;
}
- (UICollectionViewCell*)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
NSLog(#"Making Card");
CardCell *card = [collectionView dequeueReusableCellWithReuseIdentifier:#"CardCell" forIndexPath:indexPath];
// UILabel *cardLabel = [card viewWithTag:101];
// [cardLabel setText:[textArray objectAtIndex:[indexPath row]]];
return card;
}
- (UICollectionReusableView*)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath {
NSLog(#"Making Header");
UICollectionReusableView *reusableView = nil; /*[[UICollectionReusableView alloc] initWithFrame:CGRectMake(0, 0, 100, 75)];*/
// [reusableView setBackgroundColor:[UIColor blackColor]];
if (kind == UICollectionElementKindSectionHeader) {
ButtonHeaderCollectionReusableView *headerview = [collectionView dequeueReusableSupplementaryViewOfKind:UICollectionElementKindSectionHeader withReuseIdentifier:[ButtonHeaderCollectionReusableView reusableID] forIndexPath:indexPath];
return headerview;
}
// if (kind == UICollectionElementKindSectionFooter) {
// UICollectionReusableView *footerview = [collectionView dequeueReusableSupplementaryViewOfKind:UICollectionElementKindSectionFooter withReuseIdentifier:#"FooterView" forIndexPath:indexPath];
// }
return reusableView;
}
- (CGSize)collectionView:collectionView layout:(nonnull UICollectionViewLayout *)collectionViewLayout referenceSizeForHeaderInSection:(NSInteger)section {
return CGSizeMake(180.0f, 72.0f);
}
Now here is my MyFlowLayout.m
- (id)initWithCoder:(NSCoder *)aDecoder {
NSLog(#"Vertical Flow Layout init with coder");
if(self = [super initWithCoder:aDecoder]) {
contentHeight = 0;
cachedAttributes = [NSMutableArray array];
}
return self;
}
- (void)prepareLayout {
NSLog(#"Preparing Layout");
// [super prepareLayout];
contentWidth = CGRectGetWidth(self.collectionView.bounds) - (self.collectionView.contentInset.left + self.collectionView.contentInset.right);
if([cachedAttributes count] == 0) {
//compute for column width
CGFloat columnWidth = contentWidth / NUMBER_OF_COLUMNS;
NSMutableArray *xOffsets = [NSMutableArray array];
for(int i = 0; i < NUMBER_OF_COLUMNS; i++) {
[xOffsets addObject:[NSNumber numberWithFloat:(i * columnWidth)]];
}
//compute for height
NSMutableArray *yOffsets = [NSMutableArray array];
for(int i = 0; i < NUMBER_OF_COLUMNS; i++) {
[yOffsets addObject:[NSNumber numberWithFloat:75]];
}
int column = 0;
//loop through all the sections and items in the collectionview
for(int i = 0; i < self.collectionView.numberOfSections; i++) {
for(int j = 0; j < [self.collectionView numberOfItemsInSection:i]; j++) {
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:j inSection:i];
//time to do some frame calculation
///let's start with the width
CGFloat width = columnWidth - CELL_PADDING * 2;
///then the height
CGFloat viewHeight = [self.delegate getViewHeightWithCollectionView:self.collectionView indexPath:indexPath withWidth:width];
CGFloat height = CELL_PADDING + viewHeight + /*textHeight +*/ CELL_PADDING;
CGFloat xOffset = [[xOffsets objectAtIndex:column] floatValue];
CGFloat yOffset = [[yOffsets objectAtIndex:column] floatValue];
CGRect frame = CGRectMake(xOffset, yOffset, columnWidth, height);
CGRect insetFrame = CGRectInset(frame, CELL_PADDING, CELL_PADDING);
//now that computation is done we shall make the attributes
UICollectionViewLayoutAttributes *attributes = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];
[attributes setFrame:insetFrame];
[cachedAttributes addObject:attributes];
//time to increment the height and the column
contentHeight = MAX(contentHeight, CGRectGetMaxY(frame));
NSNumber *yOffSetColumn = [yOffsets objectAtIndex:column];
yOffSetColumn = [NSNumber numberWithFloat:([yOffSetColumn floatValue] + height)];
[yOffsets replaceObjectAtIndex:column withObject:yOffSetColumn];
NSLog(#"Content Height: %f yOffSetColumn: %# == %#", contentHeight, yOffSetColumn, [yOffsets objectAtIndex:column]);
column = column >= (NUMBER_OF_COLUMNS - 1) ? 0 : ++column;
}
}
}
}
- (CGSize)collectionViewContentSize {
// NSLog(#"Collection View Content Size");
return CGSizeMake(contentWidth, contentHeight + 75);
}
// called after preparelayout to determine which items are visible in the given rect
- (NSArray<UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect {
NSMutableArray<UICollectionViewLayoutAttributes *> *layoutAttributes = [NSMutableArray array];
NSInteger sectionsCount = [self.collectionView.dataSource numberOfSectionsInCollectionView:self.collectionView];
NSLog(#"Sections count: %ld", (long)sectionsCount);
for(int i = 0; i < sectionsCount; i++) {
//for header
UICollectionViewLayoutAttributes *headerAttributes = [self layoutAttributesForSupplementaryViewOfKind:UICollectionElementKindSectionHeader atIndexPath:[NSIndexPath indexPathForItem:0 inSection:i]];
// if(CGRectIntersectsRect(headerAttributes.frame, rect)) {
// NSLog(#"Adding Section Attribute");
[layoutAttributes addObject:headerAttributes];
// }
for (UICollectionViewLayoutAttributes *attributes in cachedAttributes) {
if(CGRectIntersectsRect(attributes.frame, rect)) {
[layoutAttributes addObject:attributes];
}
}
// NSInteger itemsCount = [self.collectionView numberOfItemsInSection:i];
// for(int j = 0; j < itemsCount; j++) {
// UICollectionViewLayoutAttributes *attributes = [self layoutAttributesForItemAtIndexPath:[NSIndexPath indexPathForItem:j inSection:i]];
// if(CGRectIntersectsRect(attributes.frame, rect)) {
// [layoutAttributes addObject:attributes];
// }
// }
}
return layoutAttributes;
}
//- (UICollectionViewLayoutAttributes*)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath {
// NSLog(#"Layout Attributes For Item: %ld %ld", [indexPath row], [indexPath section]);
//
// UICollectionViewLayoutAttributes *attributes = [cachedAttributes objectAtIndex:[indexPath row]];
// return attributes;
//}
- (UICollectionViewLayoutAttributes *)layoutAttributesForSupplementaryViewOfKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)indexPath {
NSLog(#"Layout Attributes For Header: %# %ld %ld", elementKind, [indexPath row], [indexPath section]);
UICollectionViewLayoutAttributes *attributes = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];
[attributes setFrame:CGRectMake(0, 0, contentWidth /*320*/, 75.0f)];
return attributes;
}
As you can see in MyFlowLayout, there are things I've tried but still it didn't call viewForSupplementaryElementOfKind. I tried implementing layoutAttributesForItemAtIndexPath since I have layoutAttributesForSupplementaryViewOfKind. I tried also using fixed numbers like the 75 and 320 that you see just to check if the header would be generated. The delegate that you see, it's just for getting a height of a view.
Since I've given the header enough space to be seen and left NSLogs all over and yet (UICollectionReusableView*)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath is not being called.
Here are some that I've looked and tried: stackoverflow but as you can see from the commented code that I've included. It still didn't work.
Thanks for the help.
I also had a problem with this and my solution was to simply add attributes for section in prepareLayout
UICollectionViewLayoutAttributes *attributes = [UICollectionViewLayoutAttributes layoutAttributesForSupplementaryViewOfKind:UICollectionElementKindSectionHeader withIndexPath:[NSIndexPath indexPathForItem:0 inSection:0]];
attributes.frame = CGRectMake(0, 0, self.collectionView.frame.size.width, 100);
[cachedAttributes addObject:attributes];
Add the default size of header section:
collectionViewLayout.headerReferenceSize = CGSize(width, height)
The default size values are (0, 0). Thats why header is not displaying.
OR
There is also another way for it, implement the delegate method for UICollectionView:
collectionView(_:layout:referenceSizeForFooterInSection:)
I'm using a custom layout to get several background color sections using UICollectionReusableView's subclasses. This is the code:
- (void)prepareLayout
{
[super prepareLayout];
self.itemAttributes = [NSMutableArray new];
NSInteger numberOfSection = self.collectionView.numberOfSections;
for (int section = 0; section < numberOfSection; section++) {
if (section == self.decorationViewOfKinds.count)
break;
NSString *decorationViewOfKind = self.decorationViewOfKinds[section % self.decorationViewOfKinds.count];
if ([decorationViewOfKind isKindOfClass:[NSNull class]])
continue;
NSInteger lastIndex = [self.collectionView numberOfItemsInSection:section] - 1;
if (lastIndex < 0)
continue;
UICollectionViewLayoutAttributes *firstItem = [self layoutAttributesForItemAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:section]];
UICollectionViewLayoutAttributes *lastItem = [self layoutAttributesForItemAtIndexPath:[NSIndexPath indexPathForRow:lastIndex inSection:section]];
CGRect frame = CGRectUnion(firstItem.frame, lastItem.frame);
frame.origin.x -= self.sectionInset.top;
frame.size.height += self.sectionInset.bottom;
if (self.scrollDirection == UICollectionViewScrollDirectionHorizontal) {
frame.size.width += self.sectionInset.left + self.sectionInset.right;
frame.size.height = self.collectionView.frame.size.height;
}
else {
frame.size.width = self.collectionView.frame.size.width;
frame.size.height += self.sectionInset.top + self.sectionInset.bottom;
}
UICollectionViewLayoutAttributes *attributes = [UICollectionViewLayoutAttributes layoutAttributesForDecorationViewOfKind:decorationViewOfKind withIndexPath:[NSIndexPath indexPathForRow:0 inSection:section]];
attributes.zIndex = -1;
attributes.frame = frame;
[self.itemAttributes addObject:attributes];
[self registerNib:[UINib nibWithNibName:decorationViewOfKind bundle:[NSBundle mainBundle]] forDecorationViewOfKind:decorationViewOfKind];
}
}
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
{
NSMutableArray *attributes = [NSMutableArray arrayWithArray:[super layoutAttributesForElementsInRect:rect]];
for (UICollectionViewLayoutAttributes *attribute in self.itemAttributes) {
if (!CGRectIntersectsRect(rect, attribute.frame))
continue;
[attributes addObject:attribute];
}
return attributes;
}
This code crashes sometimes on a device using iOS 8.4.1 throwing:
Fatal Exception: NSInternalInconsistencyException
no UICollectionViewLayoutAttributes instance for -layoutAttributesForDecorationViewOfKindOfKind: AlbumsSectionBackgroundDecoratorView at path {length = 2, path = 2 - 0}
It does not happen in the simulator, just in devices with iOS 8.4.1
Any ideas?
Thanks!
EDIT
The bug seems to be happening cause backpressure in the UICollectionView. I have avoided to reload the UICollectionView and seems to be working fine 💪🏼
I used UICollectionView and subClassing UICollectionViewLayout to get a circle scrolling view. Code in Custom Layout as below,
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
{
float minY = CGRectGetMinY(rect);
float maxY = CGRectGetMaxY(rect);
int startIndex = fmax(0.0 ,minY / 60);
int endIndex = fmin([self.collectionView numberOfItemsInSection:0]-1, maxY / 60);
NSMutableArray* array = [[NSMutableArray alloc]init];
for( int i = startIndex ;i<=endIndex ;i++)
{
NSIndexPath* path = [NSIndexPath indexPathForItem:i inSection:0];
UICollectionViewLayoutAttributes* attr = [self layoutAttributesForItemAtIndexPath:path];
[array addObject:attr];
}
return array;
}
- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath
{
float peak = (self.collectionView.contentOffset.y+self.collectionView.frame.size.height/2.0)/60.0;
float x = 200 - fabs(indexPath.item - peak)*50;
UICollectionViewLayoutAttributes* attr = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];
attr.size = CGSizeMake(250, 50);
attr.center = CGPointMake(x, indexPath.item*55.0);
attr.hidden = NO;
return attr;
}
the initial state misses the 11th cell,but when I scroll, it will appear, why that wired.
I am trying to implement a table view in which as I scroll the height of the cell at top becomes bigger than rest of the cells.
I basically want the first cell to be of 200f always and rest to be 100f.
I am able to to do it when user is scrolling down but when user is scrolling to the top and is scrolling too fast, the frame is not reset properly.
relavant code:
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
int row = scrollView.contentOffset.y / kSmallHeight;
_currentCellIndexPath = [NSIndexPath indexPathForRow:row inSection:0];
UITableViewCell *topCell = [self.tableView cellForRowAtIndexPath:_currentCellIndexPath];
NSIndexPath *path = [NSIndexPath indexPathForRow:_currentCellIndexPath.row + 1 inSection:_currentCellIndexPath .section];
UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:path];
[self.tableView insertSubview:cell aboveSubview:topCell];
[self adjustCellHeightForCell:cell withDefaultOffset:NO];
}
- (void)adjustCellHeightForCell:(UITableViewCell *)cell withDefaultOffset:(BOOL)defailtOffset
{
int row = self.tableView.contentOffset.y / kSmallHeight;
double fakeOriginy = (kBigHeight + kSmallHeight * (cell.tag-1));
double set = row * kSmallHeight;
CGRect frame = cell.frame;
double offset;
if(defailtOffset)
offset = kSmallHeight;
else {
offset = ((self.tableView.contentOffset.y - set ) * ((kBigHeight - kSmallHeight)/ kSmallHeight));
}
frame.origin.y = fakeOriginy - offset;
frame.size.height = kSmallHeight + offset;
cell.frame = frame;
}
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
{
int row = self.tableView.contentOffset.y / kSmallHeight;
_currentCellIndexPath = [NSIndexPath indexPathForRow:row inSection:0];
if(indexPath.row != 0 && indexPath.row < _currentCellIndexPath.row)
[self adjustCellHeightForCell:cell withDefaultOffset:YES];
}
- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset
{
//This is the index of the "page" that we will be landing at
NSUInteger nearestIndex = (NSUInteger) ( (int)targetContentOffset->y % (int)kSmallHeight) >= kSmallHeight/2 ? targetContentOffset->y / kSmallHeight + 1 : targetContentOffset->y / kSmallHeight;
//Just to make sure we don't scroll past your conpo tent
nearestIndex = MAX( MIN( nearestIndex, self.store.coupons.count - 1 ), 0 );
//This is the actual x position in the scroll view
CGFloat yOffset = nearestIndex * kSmallHeight;
//I've found that scroll views will "stick" unless this is done
yOffset = yOffset==0?1:yOffset;
//Tell the scroll view to land on our page
*targetContentOffset = CGPointMake(targetContentOffset->x,yOffset);
}
Try This!
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
{
if(indexpath.row == 0)
{
//call your re-adjust height method here
[your_table setContentOffset:CGPointZero animated:YES];
[self adjustCellHeightForCell:cell withDefaultOffset:NO];
}
}
Iterating through the visible cells and resetting their frames at the end of viewDidScroll worked excluding the cell that has to be transformed i.e. first visible 100.0f cell on the screen.
[visibleCells enumerateObjectsUsingBlock:^(UITableViewCell *cell, NSUInteger idx, BOOL *stop) {
NSIndexPath *path = [self.tableView indexPathForCell:cell];
if(path.row > topCellPath.row + 1) {
// reset frames of small cells
}
else if(path.row <= topCellPath.row){
// reset frames of big cells
}
}];
i'm having problems to align the first row to the top when I have different UICollectionViewCell heights and I don't know why. It seems that minimumLineSpacingForSectionAtIndex method is not working.
Here is my result:
As you can see, the first row is not aligned to the top.
This is my code:
- (void)viewDidLoad
{
...
FlowLayout *flowLayout = [[FlowLayout alloc] init];
flowLayout.itemSize = CGSizeMake(152, 260);
flowLayout.sectionInset = UIEdgeInsetsMake( 5, 5, 5, 5);
flowLayout.scrollDirection = UICollectionViewScrollDirectionVertical;
self.collectionView.collectionViewLayout = flowLayout;
...
}
- (CGSize)collectionView:(UICollectionView *)collectionView layout:
(UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)
indexPath{
if (indexPath.row == 0)
return CGSizeMake(152, 200);
if (indexPath.row == 1)
return CGSizeMake(152, 270);
return CGSizeMake(152, 300);
}
- (CGFloat)collectionView:(UICollectionView *)collectionView layout:
(UICollectionViewLayout*)collectionViewLayout
minimumInteritemSpacingForSectionAtIndex:(NSInteger)section {
return 5.0;
}
- (CGFloat)collectionView:(UICollectionView *)collectionView
layout:(UICollectionViewLayout*)collectionViewLayout
minimumLineSpacingForSectionAtIndex:(NSInteger)section {
return 8.0;
}
This is the code of my FlowLayout:
#implementation FlowLayout
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
{
NSMutableArray *result = [NSMutableArray array];
NSInteger sectionCount = 1;
if ([self.collectionView.dataSource respondsToSelector:#selector(numberOfSectionsInCollectionView:)])
{
sectionCount = [self.collectionView.dataSource numberOfSectionsInCollectionView:self.collectionView];
}
for (int s = 0; s < sectionCount; s++)
{
NSInteger itemCount = [self.collectionView.dataSource collectionView:self.collectionView numberOfItemsInSection:s];
for (int i = 0; i < itemCount; i++)
{
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:i inSection:s];
UICollectionViewLayoutAttributes *layoutAttributes =
[self layoutAttributesForSupplementaryViewOfKind:UICollectionElementKindSectionHeader atIndexPath:indexPath];
if (layoutAttributes != nil) {
[result addObject:layoutAttributes];
}
UICollectionViewLayoutAttributes *layout = [self layoutAttributesForItemAtIndexPath:indexPath];
if (CGRectIntersectsRect(rect, layout.frame))
{
[result addObject:layout];
}
}
}
return result;
}
- (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 + 10;
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;
}
- (BOOL) shouldInvalidateLayoutForBoundsChange:(CGRect)newBound {
return YES;
}
What you are experiencing is a normal cell placement when using the UICollectionViewFlowLayout.
You have to tweak the layout attributes being returned by the flow layout. To do so, override the layoutAttributesForElementsInRect: method and any of the methods that return layout attributes. Modify the attributes provided by the parent class, and then return them. The attribute you have to modify is frame.
A good example of doing it is a sample code from Apple - Custom Layouts: A Worked Example
Update after attaching implementation of the layout.
Looks like in the layoutAttributesForItemAtIndexPath: method you are not adjusting the frame for first item (index 0). You are returning the attributes you get from parent class. Change the frame's origin.y to 0 for item 0.