Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to position UITableViewCell at bottom of screen?

There are one to three UITableViewCells in a UITableViewView. Is there a way to always position the cell(s) at the bottom of screen after reloadData?

+----------------+     +----------------+     +----------------+
|                |     |                |     |                |
|                |     |                |     |                |
|                |     |                |     |                |
|                |     |                |     | +------------+ |
|                |     |                |     | |   cell 1   | |
|                |     |                |     | +------------+ |
|                |     | +------------+ |     | +------------+ |
|                |     | |   cell 1   | |     | |   cell 2   | |
|                |     | +------------+ |     | +------------+ |
| +------------+ |     | +------------+ |     | +------------+ |
| |   cell 1   | |     | |   cell 2   | |     | |   cell 3   | |
| +------------+ |     | +------------+ |     | +------------+ |
+----------------+     +----------------+     +----------------+
like image 228
ohho Avatar asked Mar 08 '13 02:03

ohho


4 Answers

I've create a new sample solution since the previous answer is not outdated for modern use.

The latest technique uses autolayout and self-sizing cells so the previous answer would no longer work. I reworked the solution to work with the modern features and created a sample project to put on GitHub.

Instead of counting up the height of each row, which is causes additional work, this code instead gets the frame for the last row so that the content inset from the top can be calculated. It leverages what the table view is already doing so no additional work is necessary.

This code also only sets the top inset in case the bottom inset is set for the keyboard or another overlay.

Please report any bugs or submit improvements on GitHub and I will update this sample.

GitHub: https://github.com/brennanMKE/BottomTable

- (void)updateContentInsetForTableView:(UITableView *)tableView animated:(BOOL)animated {     NSUInteger lastRow = [self tableView:tableView numberOfRowsInSection:0];     NSUInteger lastIndex = lastRow > 0 ? lastRow - 1 : 0;      NSIndexPath *lastIndexPath = [NSIndexPath indexPathForItem:lastIndex inSection:0];     CGRect lastCellFrame = [self.tableView rectForRowAtIndexPath:lastIndexPath];      // top inset = table view height - top position of last cell - last cell height     CGFloat topInset = MAX(CGRectGetHeight(self.tableView.frame) - lastCellFrame.origin.y - CGRectGetHeight(lastCellFrame), 0);      UIEdgeInsets contentInset = tableView.contentInset;     contentInset.top = topInset;      UIViewAnimationOptions options = UIViewAnimationOptionBeginFromCurrentState;     [UIView animateWithDuration:animated ? 0.25 : 0.0 delay:0.0 options:options animations:^{         tableView.contentInset = contentInset;     } completion:^(BOOL finished) {     }]; } 
like image 76
Brennan Avatar answered Oct 09 '22 08:10

Brennan


Call this method whenever a row is added:

- (void)updateContentInset {     NSInteger numRows=[self tableView:_tableView numberOfRowsInSection:0];     CGFloat contentInsetTop=_tableView.bounds.size.height;     for (int i=0;i<numRows;i++) {         contentInsetTop-=[self tableView:_tableView heightForRowAtIndexPath:[NSIndexPath indexPathForItem:i inSection:0]];         if (contentInsetTop<=0) {             contentInsetTop=0;             break;         }     }     _tableView.contentInset = UIEdgeInsetsMake(contentInsetTop, 0, 0, 0); } 
like image 45
Florian L. Avatar answered Oct 09 '22 06:10

Florian L.


You can set a header in your table view and make it tall enough to push the first cell down. Then set the contentOffset of your tableView accordingly. I don't think there is a quick built in way to do this though.

like image 27
Patrick Tescher Avatar answered Oct 09 '22 06:10

Patrick Tescher


I didn't like empty-cell, contentInset or transform based solutions, instead I came up with other solution:

Example

UITableView's layout is private and subject to change if Apple desires, it's better to have full control thus making your code future-proof and more flexible. I switched to UICollectionView and implemented special layout based on UICollectionViewFlowLayout for that (Swift 3):

override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
    // Do we need to stick cells to the bottom or not
    var shiftDownNeeded = false

    // Size of all cells without modifications
    let allContentSize = super.collectionViewContentSize()

    // If there are not enough cells to fill collection view vertically we shift them down
    let diff = self.collectionView!.bounds.size.height - allContentSize.height
    if Double(diff) > DBL_EPSILON {
        shiftDownNeeded = true
    }

    // Ask for common attributes
    let attributes = super.layoutAttributesForElements(in: rect)

    if let attributes = attributes {
        if shiftDownNeeded {
            for element in attributes {
                let frame = element.frame;
                // shift all the cells down by the difference of heights
                element.frame = frame.offsetBy(dx: 0, dy: diff);
            }
        }
    }

    return attributes;
}

It works pretty well for my cases and, obviously, may be optimized by somehow caching content size height. Also, I'm not sure how will that perform without optimizations on big datasets, I didn't test that. I've put together sample project with demo: MDBottomSnappingCells.

Here is Objective-C version:

- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect;
{
    // Do we need to stick cells to the bottom or not
    BOOL shiftDownNeeded = NO;

    // Size of all cells without modifications
    CGSize allContentSize = [super collectionViewContentSize];

    // If there are not enough cells to fill collection view vertically we shift them down
    CGFloat diff = self.collectionView.bounds.size.height - allContentSize.height;
    if(diff > DBL_EPSILON)
    {
        shiftDownNeeded = YES;
    }

    // Ask for common attributes
    NSArray *attributes = [super layoutAttributesForElementsInRect:rect];
    if(shiftDownNeeded)
    {
        for(UICollectionViewLayoutAttributes *element in attributes)
        {
            CGRect frame = element.frame;
            // shift all the cells down by the difference of heights
            element.frame = CGRectOffset(frame, 0, diff);
        }
    }

    return attributes;
}
like image 22
MANIAK_dobrii Avatar answered Oct 09 '22 07:10

MANIAK_dobrii