Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

UICollectionView insert cells above maintaining position (like Messages.app)

By default Collection View maintains content offset while inserting cells. On the other hand I'd like to insert cells above the currently displaying ones so that they appear above the screen top edge like Messages.app do when you load earlier messages. Does anyone know the way to achieve it?

like image 526
mrvn Avatar asked Aug 28 '14 11:08

mrvn


4 Answers

This is the technique I use. I've found others cause strange side effects such as screen flicker:

    CGFloat bottomOffset = self.collectionView.contentSize.height - self.collectionView.contentOffset.y;

    [CATransaction begin];
    [CATransaction setDisableActions:YES];

    [self.collectionView performBatchUpdates:^{
        [self.collectionView insertItemsAtIndexPaths:indexPaths];
    } completion:^(BOOL finished) {
        self.collectionView.contentOffset = CGPointMake(0, self.collectionView.contentSize.height - bottomOffset);
    }];

    [CATransaction commit];
like image 87
James Martin Avatar answered Oct 19 '22 02:10

James Martin


James Martin’s fantastic version converted to Swift 2:

let amount = 5 // change this to the amount of items to add
let section = 0 // change this to your needs, too
let contentHeight = self.collectionView!.contentSize.height
let offsetY = self.collectionView!.contentOffset.y
let bottomOffset = contentHeight - offsetY

CATransaction.begin()
CATransaction.setDisableActions(true)

self.collectionView!.performBatchUpdates({
    var indexPaths = [NSIndexPath]()
    for i in 0..<amount {
        let index = 0 + i
        indexPaths.append(NSIndexPath(forItem: index, inSection: section))
    }
    if indexPaths.count > 0 {
        self.collectionView!.insertItemsAtIndexPaths(indexPaths)
    }
    }, completion: {
        finished in
        print("completed loading of new stuff, animating")
        self.collectionView!.contentOffset = CGPointMake(0, self.collectionView!.contentSize.height - bottomOffset)
        CATransaction.commit()
})
like image 38
Sebastian Avatar answered Oct 19 '22 02:10

Sebastian


My approach leverages subclassed flow layout. This means that you don't have to hack scrolling/layout code in a view controller. Idea is that whenever you know that you are inserting cells on top you set custom property you flag that next layout update will be inserting cells to top and you remember content size before update. Then you override prepareLayout() and set desired content offset there. It looks something like this:

define variables

private var isInsertingCellsToTop: Bool = false
private var contentSizeWhenInsertingToTop: CGSize?

override prepareLayout() and after calling super

if isInsertingCellsToTop == true {
    if let collectionView = collectionView, oldContentSize = contentSizeWhenInsertingToTop {
        let newContentSize = collectionViewContentSize()
        let contentOffsetY = collectionView.contentOffset.y + (newContentSize.height - oldContentSize.height)
        let newOffset = CGPointMake(collectionView.contentOffset.x, contentOffsetY)
        collectionView.setContentOffset(newOffset, animated: false)
}
    contentSizeWhenInsertingToTop = nil
    isInsertingMessagesToTop = false
}
like image 26
Peter Stajger Avatar answered Oct 19 '22 04:10

Peter Stajger


I did this in two lines of code (although it was on a UITableView) but I think you'd be able to do it the same way.

I rotated the tableview 180 degrees.

Then I rotated each tableview cell by 180 degrees also.

This meant that I could treat it as a standard top to bottom table but the bottom was treated like the top.

like image 15
Fogmeister Avatar answered Oct 19 '22 02:10

Fogmeister