UICollectionView вставляет ячейки выше поддерживающего положения (например, Messages.app)

По умолчанию Collection View поддерживает смещение содержимого при вставке ячеек. С другой стороны, я хотел бы вставить ячейки выше отображаемых в данный момент, чтобы они отображались над верхним краем экрана, например Messages.app, при загрузке более ранних сообщений. Кто-нибудь знает, как его достичь?

Ответ 1

Это техника, которую я использую. Я обнаружил, что другие вызывали странные побочные эффекты, такие как мерцание экрана:

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

Ответ 2

Джеймс Мартинс фантастическая версия, преобразованная в 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()
})

Ответ 3

Мой подход использует подклассовый макет потока. Это означает, что вам не нужно взломать код прокрутки/компоновки в контроллере представления. Идея заключается в том, что всякий раз, когда вы знаете, что вы вставляете ячейки сверху, вы устанавливаете настраиваемое свойство, вы отмечаете, что следующее обновление макета будет вставлять ячейки вверх, а вы помните размер содержимого перед обновлением. Затем вы переопределите prepareLayout() и установите требуемое смещение содержимого. Это выглядит примерно так:

определить переменные

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

переопределить prepareLayout() и после вызова 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
}

Ответ 4

Я сделал это в двух строках кода (хотя это было в UITableView), но я думаю, что вы сможете сделать это одинаково.

Я повернул таблицу 180 градусов.

Затем я поворачивал каждую ячейку таблицы на 180 градусов.

Это означало, что я мог бы рассматривать его как стандартную верхнюю и нижнюю таблицу, но нижняя часть обрабатывалась как верхняя.

Ответ 5

В дополнение к ответу Fogmeister (с кодом), самый чистый подход - инвертировать (перевернуть) UICollectionView чтобы у вас было представление прокрутки, которое прилипало к нижней части, а не к верхней части. Это также работает для UITableView, как указывает Fogmeister.

- (void)viewDidLoad
{
    [super viewDidLoad];

    self.collectionView.transform = CGAffineTransformMake(1, 0, 0, -1, 0, 0);

}

В Свифте:

override func viewDidLoad() {
    super.viewDidLoad()

    collectionView.transform = CGAffineTransformMake(1, 0, 0, -1, 0, 0)
}

У этого есть побочный эффект также отображения ваших ячеек вверх дном, так что вы должны также перевернуть их. Поэтому мы передаем trasform (cell.transform = collectionView.transform) следующим образом:

- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
    UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"Cell" forIndexPath:indexPath];

    cell.transform = collectionView.transform;

    return cell;
}

В Свифте:

func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
    var cell = collectionView.dequeueReusableCellWithReuseIdentifier("Cell", forIndexPath: indexPath) as! UICollectionViewCell

    cell.transform = collectionView.transform

    return cell
}

Наконец, главное, что следует помнить при разработке в рамках этого проекта, это то, что параметры NSIndexPath в делегатах NSIndexPath вспять. Таким образом, indexPath.row == 0 - это строка в нижней части collectionView где она обычно находится вверху.

Этот метод используется во многих проектах с открытым исходным кодом для создания описанного поведения, включая популярный SlackTextViewController (https://github.com/slackhq/SlackTextViewController), поддерживаемый Slack

Думаю, я бы добавил немного кода в фантастический ответ Fogmeister!

Ответ 6

Любовь Джеймса Мартинса. Но для меня он начал разбиваться при вставке/удалении выше/ниже определенного окна содержимого. Я взял удар в подклассе UICollectionViewFlowLayout, чтобы получить поведение, которое я хотел. Надеюсь, это поможет кому-то. Любая оценка приветствуется:)

@interface FixedScrollCollectionViewFlowLayout () {

    __block float bottomMostVisibleCell;
    __block float topMostVisibleCell;
}

@property (nonatomic, assign) BOOL isInsertingCellsToTop;
@property (nonatomic, strong) NSArray *visableAttributes;
@property (nonatomic, assign) float offset;;

@end

@implementation FixedScrollCollectionViewFlowLayout


- (id)initWithCoder:(NSCoder *)aDecoder {

    self = [super initWithCoder:aDecoder];

    if (self) {
        _isInsertingCellsToTop = NO;
    }
    return self;
}

- (id)init {

    self = [super init];

    if (self) {
        _isInsertingCellsToTop = NO;
    }
    return self;
}

- (void)prepareLayout {

    NSLog(@"prepareLayout");
    [super prepareLayout];
}

- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect {

    NSLog(@"layoutAttributesForElementsInRect");
    self.visableAttributes = [super layoutAttributesForElementsInRect:rect];
    self.offset = 0;
    self.isInsertingCellsToTop = NO;
    return self.visableAttributes;
}

- (void)prepareForCollectionViewUpdates:(NSArray *)updateItems {

    bottomMostVisibleCell = -MAXFLOAT;
    topMostVisibleCell = MAXFLOAT;
    CGRect container = CGRectMake(self.collectionView.contentOffset.x, self.collectionView.contentOffset.y, self.collectionView.frame.size.width, self.collectionView.frame.size.height);

    [self.visableAttributes  enumerateObjectsUsingBlock:^(UICollectionViewLayoutAttributes *attributes, NSUInteger idx, BOOL *stop) {

        CGRect currentCellFrame =  attributes.frame;
        CGRect containerFrame = container;

        if(CGRectIntersectsRect(containerFrame, currentCellFrame)) {
            float x = attributes.indexPath.row;
            if (x < topMostVisibleCell) topMostVisibleCell = x;
            if (x > bottomMostVisibleCell) bottomMostVisibleCell = x;
        }
    }];

    NSLog(@"prepareForCollectionViewUpdates");
    [super prepareForCollectionViewUpdates:updateItems];
    for (UICollectionViewUpdateItem *updateItem in updateItems) {
        switch (updateItem.updateAction) {
            case UICollectionUpdateActionInsert:{
                NSLog(@"UICollectionUpdateActionInsert %ld",updateItem.indexPathAfterUpdate.row);
                if (topMostVisibleCell>updateItem.indexPathAfterUpdate.row) {
                    UICollectionViewLayoutAttributes * newAttributes = [self layoutAttributesForItemAtIndexPath:updateItem.indexPathAfterUpdate];
                    self.offset += (newAttributes.size.height + self.minimumLineSpacing);
                    self.isInsertingCellsToTop = YES;
                }
                break;
            }
            case UICollectionUpdateActionDelete: {
                NSLog(@"UICollectionUpdateActionDelete %ld",updateItem.indexPathBeforeUpdate.row);
                if (topMostVisibleCell>updateItem.indexPathBeforeUpdate.row) {
                    UICollectionViewLayoutAttributes * newAttributes = [self layoutAttributesForItemAtIndexPath:updateItem.indexPathBeforeUpdate];
                    self.offset -= (newAttributes.size.height + self.minimumLineSpacing);
                    self.isInsertingCellsToTop = YES;
                }
                break;
            }
            case UICollectionUpdateActionMove:
                NSLog(@"UICollectionUpdateActionMoveB %ld", updateItem.indexPathBeforeUpdate.row);
                break;
            default:
                NSLog(@"unhandled case: %ld", updateItem.indexPathBeforeUpdate.row);
                break;
        }
    }

    if (self.isInsertingCellsToTop) {
        if (self.collectionView) {
            [CATransaction begin];
            [CATransaction setDisableActions:YES];
        }
    }
}

- (void)finalizeCollectionViewUpdates {

    CGPoint newOffset = CGPointMake(self.collectionView.contentOffset.x, self.collectionView.contentOffset.y + self.offset);

    if (self.isInsertingCellsToTop) {
        if (self.collectionView) {
            self.collectionView.contentOffset = newOffset;
            [CATransaction commit];
        }
    }
}

Ответ 7

Вдохновленный решением Bryan Pratte, я разработал подкласс UICollectionViewFlowLayout, чтобы получить поведение в чате, не перевернув просмотр коллекции вверх дном. Этот макет написан в Swift 3 и абсолютно применим с RxSwift и RxDataSources, потому что интерфейс полностью отделен от любой логики или привязки.

Три вещи были важны для меня:

  • Если появилось новое сообщение, прокрутите вниз. Неважно, где вы находитесь в списке в этот момент. Прокрутка выполняется с помощью setContentOffset вместо scrollToItemAtIndexPath.
  • Если вы делаете "Lazy Loading" более старыми сообщениями, представление прокрутки не должно меняться и остается там, где оно есть.
  • Добавить исключения для начала. Представление коллекции должно вести себя "нормально", пока на экране больше сообщений, чем на экране.

Мое решение: https://gist.github.com/jochenschoellig/04ffb26d38ae305fa81aeb711d043068

Ответ 8

Здесь немного измененная версия решения Peter (подклассификация раскладки потока, без перевернутого, легкого подхода). Это Swift 3. Заметьте UIView.animate с нулевой продолжительностью - чтобы анимация четности/нечетности ячеек (что на строке) оживилась, но остановите анимацию смещения видового экрана (которая будет выглядеть ужасно)

Использование:

        let layout = self.collectionview.collectionViewLayout as! ContentSizePreservingFlowLayout
        layout.isInsertingCellsToTop = true
        self.collectionview.performBatchUpdates({
            if let deletionIndexPaths = deletionIndexPaths, deletionIndexPaths.count > 0 {
                self.collectionview.deleteItems(at: deletionIndexPaths.map { return IndexPath.init(item: $0.item+twitterItems, section: 0) })
            }
            if let insertionIndexPaths = insertionIndexPaths, insertionIndexPaths.count > 0 {
                self.collectionview.insertItems(at: insertionIndexPaths.map { return IndexPath.init(item: $0.item+twitterItems, section: 0) })
            }
        }) { (finished) in
            completionBlock?()
        }

Здесь ContentSizePreservingFlowLayout в целом:

    class ContentSizePreservingFlowLayout: UICollectionViewFlowLayout {
        var isInsertingCellsToTop: Bool = false {
            didSet {
                if isInsertingCellsToTop {
                    contentSizeBeforeInsertingToTop = collectionViewContentSize
                }
            }
        }
        private var contentSizeBeforeInsertingToTop: CGSize?

        override func prepare() {
            super.prepare()
            if isInsertingCellsToTop == true {
                if let collectionView = collectionView, let oldContentSize = contentSizeBeforeInsertingToTop {
                    UIView.animate(withDuration: 0, animations: {
                        let newContentSize = self.collectionViewContentSize
                        let contentOffsetY = collectionView.contentOffset.y + (newContentSize.height - oldContentSize.height)
                        let newOffset = CGPoint(x: collectionView.contentOffset.x, y: contentOffsetY)
                        collectionView.contentOffset = newOffset
                    })
                }
                contentSizeBeforeInsertingToTop = nil
                isInsertingCellsToTop = false
            }
        }
    }

Ответ 9

Не самое элегантное, но довольно простое и эффективное решение, на которое я застрял. Работает только с линейной компоновкой (не сеткой), но это прекрасно для меня.

// retrieve data to be inserted
NSArray *fetchedObjects = [managedObjectContext executeFetchRequest:fetchRequest error:nil];
NSMutableArray *objects = [fetchedObjects mutableCopy];
[objects addObjectsFromArray:self.messages];

// self.messages is a DataSource array
self.messages = objects;

// calculate index paths to be updated (we are inserting 
// fetchedObjects.count of objects at the top of collection view)
NSMutableArray *indexPaths = [NSMutableArray new];
for (int i = 0; i < fetchedObjects.count; i ++) {
    [indexPaths addObject:[NSIndexPath indexPathForItem:i inSection:0]];
}

// calculate offset of the top of the displayed content from the bottom of contentSize
CGFloat bottomOffset = self.collectionView.contentSize.height - self.collectionView.contentOffset.y;

// performWithoutAnimation: cancels default collection view insertion animation
[UIView performWithoutAnimation:^{

    // capture collection view image representation into UIImage
    UIGraphicsBeginImageContextWithOptions(self.collectionView.bounds.size, NO, 0);
    [self.collectionView drawViewHierarchyInRect:self.collectionView.bounds afterScreenUpdates:YES];
    UIImage *snapshotImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();

    // place the captured image into image view laying atop of collection view
    self.snapshot.image = snapshotImage;
    self.snapshot.hidden = NO;

    [self.collectionView performBatchUpdates:^{
        // perform the actual insertion of new cells
        [self.collectionView insertItemsAtIndexPaths:indexPaths];
    } completion:^(BOOL finished) {
        // after insertion finishes, scroll the collection so that content position is not
        // changed compared to such prior to the update
        self.collectionView.contentOffset = CGPointMake(0, self.collectionView.contentSize.height - bottomOffset);
        [self.collectionView.collectionViewLayout invalidateLayout];

        // and hide the snapshot view
        self.snapshot.hidden = YES;
    }];
}];

Ответ 10

if ([newMessages count] > 0)
{
    [self.collectionView reloadData];

    if (hadMessages)
        [self.collectionView scrollToItemAtIndexPath:[NSIndexPath indexPathForItem:[newMessages count] inSection:0] atScrollPosition:UICollectionViewScrollPositionTop animated:NO];
}

Кажется, это работает до сих пор. Перезагрузите коллекцию, прокрутите предыдущее сообщение до вершины без анимации.

Ответ 11

Мне удалось написать решение, которое работает для случаев, когда вставлять ячейки сверху и снизу одновременно.

  • Сохранить позицию верхней видимой ячейки. Вычислите высоту ячейки, которая находится под navBar (вид сверху, в моем случае это self.participantsView)
// get the top cell and save frame
NSMutableArray<NSIndexPath*> *visibleCells = [self.collectionView indexPathsForVisibleItems].mutableCopy;
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"item" ascending:YES];
[visibleCells sortUsingDescriptors:@[sortDescriptor]];

ChatMessage *m = self.chatMessages[visibleCells.firstObject.item];
UICollectionViewCell *topCell = [self.collectionView cellForItemAtIndexPath:visibleCells.firstObject];
CGRect topCellFrame = topCell.frame;
CGRect navBarFrame = [self.view convertRect:self.participantsView.frame toView:self.collectionView];
CGFloat offset = CGRectGetMaxY(navBarFrame) - topCellFrame.origin.y;
  1. Перезагрузите данные.
[self.collectionView reloadData];
  1. Получить новую позицию элемента. Получить атрибуты для этого индекса. Извлеките смещение и измените contentOffset для коллекции.
// scroll to the old cell position
NSUInteger messageIndex = [self.chatMessages indexOfObject:m];

UICollectionViewLayoutAttributes *attr = [self.collectionView layoutAttributesForItemAtIndexPath:[NSIndexPath indexPathForItem:messageIndex inSection:0]];

self.collectionView.contentOffset = CGPointMake(0, attr.frame.origin.y + offset);

Ответ 12

Код версии Swift 3: на основе ответа Джеймса Мартина

    let amount = 1 // 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(item: index, section: section))
      }
      if indexPaths.count > 0 {
        self.collectionView.insertItems(at: indexPaths as [IndexPath])
      }
    }, completion: {
       finished in
       print("completed loading of new stuff, animating")
       self.collectionView.contentOffset = CGPoint(x: 0, y: self.collectionView.contentSize.height - bottomOffset)
       CATransaction.commit()
    })

Ответ 13

Несмотря на то, что все решения, описанные выше, работают для меня, основная причина неудачи заключается в том, что когда пользователь прокручивается, пока эти элементы добавляются, прокрутка останавливается или будет заметный лаг Вот решение, которое помогает поддерживать (визуальную) позицию прокрутки при добавлении элементов вверх.

class Layout: UICollectionViewFlowLayout {

    var heightOfInsertedItems: CGFloat = 0.0

    override func targetContentOffset(forProposedContentOffset proposedContentOffset: CGPoint) -> CGPoint {
        var offset = proposedContentOffset
        offset.y +=  heightOfInsertedItems
        heightOfInsertedItems = 0.0
        return offset
    }

    override func targetContentOffset(forProposedContentOffset proposedContentOffset: CGPoint, withScrollingVelocity velocity: CGPoint) -> CGPoint {
        var offset = proposedContentOffset
        offset.y += heightOfInsertedItems
        heightOfInsertedItems = 0.0
        return offset
    }

    override func prepare(forCollectionViewUpdates updateItems: [UICollectionViewUpdateItem]) {
        super.prepare(forCollectionViewUpdates: updateItems)
        var totalHeight: CGFloat = 0.0
        updateItems.forEach { item in
            if item.updateAction == .insert {
                if let index = item.indexPathAfterUpdate {
                    if let attrs = layoutAttributesForItem(at: index) {
                        totalHeight += attrs.frame.height
                    }
                }
            }
        }

        self.heightOfInsertedItems = totalHeight
    }
}

Этот макет запоминает высоту элементов, которые будут вставлены, а затем в следующий раз, когда макет будет запрошен для смещения, он компенсирует смещение по высоте добавленных элементов.

Ответ 14

Это то, что я узнал из JSQMessagesViewController: как сохранить положение прокрутки?. Очень просто, полезно и не мерцает!

 // Update collectionView dataSource
data.insert(contentsOf: array, at: startRow)

// Reserve old Offset
let oldOffset = self.collectionView.contentSize.height - self.collectionView.contentOffset.y

// Update collectionView
collectionView.reloadData()
collectionView.layoutIfNeeded()

// Restore old Offset
collectionView.contentOffset = CGPoint(x: 0, y: self.collectionView.contentSize.height - oldOffset)

Ответ 15

Я обнаружил, что пять шагов работают без проблем:

  1. Подготовьте данные для ваших новых ячеек и вставьте данные соответствующим образом

  2. Скажите UIView чтобы остановить анимацию

    UIView.setAnimationsEnabled(false)
    
  3. На самом деле вставить эти клетки

    collectionView?.insertItems(at: indexPaths)
    
  4. Прокрутите представление коллекции (которое является подклассом UIScrollView)

    scrollView.contentOffset.y += CELL_HEIGHT * CGFloat(ITEM_COUNT)
    

    Обратите внимание на замену CELL_HEIGHT высотой ваших ячеек (что легко сделать, если ячейки имеют фиксированный размер). Важно добавить любые межклеточные поля/вставки.

  5. Не забудьте сказать UIView чтобы запустить анимацию снова:

    UIView.setAnimationsEnabled(true)
    

Ответ 16

Я использовал подход @James Martin, но если вы используете coredata и NSFetchedResultsController правильный подход - сохранить количество ранее загруженных сообщений, загруженных в _earlierMessagesLoaded и проверить значение в controllerDidChangeContent:

#pragma mark - NSFetchedResultsController

- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller
{
    if(_earlierMessagesLoaded)
    {
        __block NSMutableArray * indexPaths = [NSMutableArray new];
        for (int i =0; i<[_earlierMessagesLoaded intValue]; i++)
        {
            [indexPaths addObject:[NSIndexPath indexPathForRow:i inSection:0]];
        }

        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];
            _earlierMessagesLoaded = nil;
        }];
    }
    else
        [self finishReceivingMessageAnimated:NO];
}

Ответ 17

Некоторые из предложенных подходов имели разную степень успеха для меня. В конце концов, я использовал вариант с подклассами и prepareLayout Peter Stajger, поместив мою коррекцию смещения в finalizeCollectionViewUpdates. Однако сегодня, когда я просматривал некоторую дополнительную документацию, я обнаружил targetContentOffset(forProposedContentOffset proposedContentOffset: CGPoint) и я думаю, что это намного больше похоже на предполагаемое местоположение для этого типа исправления. Так что это моя реализация, использующая это. Обратите внимание, что моя имплементация была для горизонтальной коллекции, но cellsInsertingToTheLeft может быть легко обновлен как cellsInsertingAbove и смещение соответствующим образом скорректировано.

class GCCFlowLayout: UICollectionViewFlowLayout {

    var cellsInsertingToTheLeft: Int?

    override func targetContentOffset(forProposedContentOffset proposedContentOffset: CGPoint) -> CGPoint {
        guard let cells = cellsInsertingToTheLeft else { return proposedContentOffset }
        guard let collectionView = collectionView else { return proposedContentOffset }
        let contentOffsetX = collectionView.contentOffset.x + CGFloat(cells) * (collectionView.bounds.width - 45 + 8)
        let newOffset = CGPoint(x: contentOffsetX, y: collectionView.contentOffset.y)
        cellsInsertingToTheLeft = nil
        return newOffset
    }
}

Ответ 18

Основываясь на ответе @Steven, мне удалось сделать вставку ячейки с прокруткой вниз, без мерцания (и с использованием автоэлементов), протестировано на iOS 12

    let oldOffset = self.collectionView!.contentOffset
    let oldOffsetDelta = self.collectionView!.contentSize.height - self.collectionView!.contentOffset.y

    CATransaction.begin()
    CATransaction.setCompletionBlock {
        self.collectionView!.setContentOffset(CGPoint(x: 0, y: self.collectionView!.contentSize.height - oldOffsetDelta), animated: true)
    }
        collectionView!.reloadData()
        collectionView!.layoutIfNeeded()
        self.collectionView?.setContentOffset(oldOffset, animated: false)
    CATransaction.commit()

Ответ 19

CGPoint currentOffset = _collectionView.contentOffset;
CGSize contentSizeBeforeInsert = [_collectionView.collectionViewLayout collectionViewContentSize];

[_collectionView reloadData];

CGSize contentSizeAfterInsert = [_collectionView.collectionViewLayout collectionViewContentSize];

CGFloat deltaHeight = contentSizeAfterInsert.height - contentSizeBeforeInsert.height;
currentOffset.y += MAX(deltaHeight, 0);

_collectionView.contentOffset = currentOffset;