web-dev-qa-db-fra.com

UICollectionView insère des cellules au-dessus de la position de maintien (comme Messages.app)

Par défaut, Collection View conserve le contenu en décalage lors de l'insertion de cellules. D'autre part, j'aimerais insérer des cellules au-dessus des cellules actuellement affichées afin qu'elles apparaissent au-dessus de l'écran, comme le fait Messages.app lorsque vous chargez des messages précédents. Est-ce que quelqu'un sait comment y parvenir?

32
mrvn

C'est la technique que j'utilise. J'ai constaté que d'autres provoquaient d'étranges effets secondaires tels que le scintillement de l'écran:

    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];
55
James Martin

Mon approche s'appuie sur la disposition des flux de sous-classes. Cela signifie que vous n'avez pas à pirater le code de défilement/mise en page dans un contrôleur de vue. L'idée est que chaque fois que vous savez que vous insérez des cellules par-dessus, vous définissez une propriété personnalisée, vous signalez que la prochaine mise à jour de la présentation insérera des cellules par-dessus et que vous vous souviendrez de la taille du contenu avant la mise à jour. Ensuite, vous substituez prepareLayout () et définissez le décalage de contenu souhaité à cet emplacement. Cela ressemble à quelque chose comme ça:

définir des variables  

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

override prepareLayout() et après avoir appelé 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
}
21
Peter Stajger

La version fantastique de James Martin convertie en 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()
})
21
Sebastian

Je l'ai fait en deux lignes de code (bien que ce soit sur UITableView) mais je pense que vous pourriez le faire de la même manière.

J'ai fait pivoter la table à 180 degrés.

Ensuite, j'ai également fait pivoter chaque cellule de la table à 180 degrés.

Cela signifiait que je pouvais le traiter comme une table standard de haut en bas, mais le bas était traité comme le haut.

12
Fogmeister

Ajoutant à la réponse de Fogmeister (avec le code), l’approche la plus propre consiste à inverser (à l’envers) la UICollectionView de manière à obtenir une vue de défilement collante vers le bas plutôt que vers le haut. Cela fonctionne également pour UITableView, comme le souligne Fogmeister.

- (void)viewDidLoad
{
    [super viewDidLoad];

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

}

En rapide:

override func viewDidLoad() {
    super.viewDidLoad()

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

Cela a pour effet secondaire d'afficher également vos cellules à l'envers, de sorte que vous devez également les retourner. Nous transférons donc la transformation (cell.transform = collectionView.transform) comme suit:

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

    cell.transform = collectionView.transform;

    return cell;
}

En rapide:

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

    cell.transform = collectionView.transform

    return cell
}

Enfin, la principale chose à retenir lors du développement sous cette conception est que les paramètres NSIndexPath dans les délégués sont inversés. Donc, indexPath.row == 0 est la ligne en bas de la collectionView où il se trouve normalement en haut.

Cette technique est utilisée dans de nombreux projets open source pour produire le comportement décrit, y compris le populaire SlackTextViewController ( https://github.com/slackhq/SlackTextViewController ) géré par Slack

Je pensais ajouter du contexte à la réponse fantastique de Fogmeister!

7
mattr

J'adore la solution de James Martin. Mais pour moi, cela a commencé à s'effriter lors de l'insertion/suppression au-dessus/en dessous d'une fenêtre de contenu spécifique. J'ai essayé de sous-classer UICollectionViewFlowLayout pour obtenir le comportement souhaité. J'espère que ça aide quelqu'un. Tout commentaire apprécié :) 

@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];
        }
    }
}
3
Bryan Pratte

Code de version de Swift 3: basé sur la réponse de James Martin

    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()
    })
3
mriaz0011

Inspiré par la solution de Bryan Pratte, j'ai développé la sous-classe de UICollectionViewFlowLayout pour obtenir le comportement de discussion sans changer la vue de la collection. Cette structure est écrite en Swift 3 et peut être utilisée de manière absolue avec RxSwift et RxDataSources car l'interface utilisateur est complètement séparée de toute logique ou liaison.

Trois choses étaient importantes pour moi:

  1. S'il y a un nouveau message, faites-le défiler. Peu importe où vous vous trouvez dans la liste en ce moment. Le défilement est réalisé avec setContentOffset au lieu de scrollToItemAtIndexPath.
  2. Si vous effectuez un "Chargement paresseux" avec des messages plus anciens, la vue de défilement ne devrait pas changer et reste exactement là où elle se trouve.
  3. Ajouter des exceptions pour le début. La vue Collection devrait se comporter de manière "normale" jusqu'à ce qu'il y ait plus de messages que d'espace sur l'écran.

Ma solution:https://Gist.github.com/jochenschoellig/04ffb26d38ae305fa81aeb711d043068

3
Jochen Schöllig

Voici une version légèrement modifiée de la solution de Peter (structure du flux de sous-classes, pas d’approche à l’envers, légère). C'est Swift 3 . Note UIView.animate avec une durée nulle - pour autoriser l'animation de la planéité/de l'étrangeté des cellules (ce qui est sur une rangée), mais arrêtez le changement d'animation de la fenêtre d'affichage (ce qui serait terrible)

Usage:

        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?()
        }

Voici ContentSizePreservingFlowLayout dans son intégralité:

    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
            }
        }
    }
3
xaphod
if ([newMessages count] > 0)
{
    [self.collectionView reloadData];

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

Cela semble fonctionner jusqu'à présent. Rechargez la collection, faites défiler le premier message précédemment vers le haut sans animation.

2
Chad Podoski

Ce n’est pas la solution la plus élégante, mais tout à fait simple et efficace, que je me suis fixée pour le moment. Fonctionne uniquement avec une mise en page linéaire (pas de grille) mais ça me convient.

// 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;
    }];
}];
2
mrvn

J'ai réussi à écrire une solution qui fonctionne pour les cas lors de l'insertion simultanée de cellules en haut et en bas.

  1. Enregistrez la position de la cellule visible en haut. Calculer la hauteur de la cellule située sous la barre de navigation (la vue de dessus. Dans mon cas, il s'agit de 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. Rechargez vos données.
[self.collectionView reloadData];
  1. Obtenir la nouvelle position de l'article. Obtenez les attributs pour cet index. Extrayez l'offset et changez contentOffset de la collectionView.
// 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);
2
grigorievs

C’est ce que j’ai appris de JSQMessagesViewController: Comment maintenir la position de défilement? . Très simple, utile et sans scintillement!

 // 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)
1
Steven

J'ai trouvé que les cinq étapes fonctionnent de manière transparente:

  1. Préparez les données pour vos nouvelles cellules et insérez-les comme il convient

  2. Dites UIView pour arrêter l'animation

    UIView.setAnimationsEnabled(false)
    
  3. Effectivement insérer ces cellules

    collectionView?.insertItems(at: indexPaths)
    
  4. Faire défiler la vue de collection (qui est une sous-classe de UIScrollView)

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

    Avis de remplacer CELL_HEIGHT par la hauteur de vos cellules (ce qui n’est facile que si les cellules ont une taille fixe). Il est important d’ajouter des marges/encarts de cellule à cellule.

  5. N'oubliez pas de dire à UIView de redémarrer l'animation:

    UIView.setAnimationsEnabled(true)
    
1
Teng L

Bien que toutes les solutions ci-dessus fonctionnent pour moi, la principale raison de l'échec est que, lorsque l'utilisateur fait défiler ces éléments en cours d'ajout, le défilement s'arrête ou bien il se produit un décalage notable maintenir la position de défilement (visuel) tout en ajoutant des éléments en haut.

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

Cette disposition mémorise la hauteur des éléments à insérer, puis la prochaine fois que la présentation demandera un décalage, elle compensera le décalage par la hauteur des éléments ajoutés.

1
tt.Kilew

Sur la base de la réponse de @Steven, j'ai réussi à insérer des cellules avec défilement vers le bas, sans scintillement (et en utilisant des cellules automatiques), testé sur 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()
0
HelloimDarius

Quelques-unes des approches suggérées ont eu divers degrés de succès pour moi. J'ai finalement utilisé une variante de l'option de sous-classement et prepareLayout, Peter Stajger, mettant ma correction de décalage dans finalizeCollectionViewUpdates. Cependant, aujourd’hui, j’étais en train de regarder une documentation supplémentaire, j’ai trouvé targetContentOffset(forProposedContentOffset proposedContentOffset: CGPoint) et je pense que cela ressemble beaucoup plus à l’emplacement prévu pour ce type de correction. Donc, ceci est ma mise en œuvre en utilisant cela. Notez que mon implémentation était destinée à une collection horizontale, mais cellsInsertingToTheLeft pouvait facilement être mis à jour sous la forme cellsInsertingAbove et le décalage corrigé en conséquence.

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
    }
}
0
SuperGuyAbe

J'ai utilisé l'approche @James Martin, mais si vous utilisez coredata et NSFetchedResultsController, la bonne approche consiste à stocker le nombre de messages antérieurs chargés dans_earlierMessagesLoadedet à vérifier la valeur dans lecontrollerDidChangeContent:

#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];
}
0
Kappe