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

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;
}