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

par défaut la vue Collection maintient l'offset du contenu tout en insérant des cellules. D'autre part, je voudrais insérer des cellules au-dessus de l'affiche actuellement pour qu'ils apparaissent au-dessus du bord supérieur de l'écran comme des Messages.app font quand vous chargez des messages plus tôt. Personne ne sait la manière de la réaliser?

28
demandé sur mrvn 2014-08-28 15:58:35

19 réponses

C'est la technique que j'utilise. J'ai trouvé que d'autres provoquent des effets secondaires étranges tels que le clignotement d'é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];
50
répondu James Martin 2016-11-02 19:27:38

mon approche s'appuie sur un schéma d'écoulement subclassé. Cela signifie que vous n'avez pas à hacker 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 sur le dessus, vous définissez la propriété personnalisée vous signalez que la prochaine mise à jour de la mise en page sera d'insérer des cellules sur le dessus et vous vous souvenez de la taille du contenu avant la mise à jour. Ensuite, vous outrepassez prepareLayout () et définissez le décalage du contenu désiré. Il ressemble à quelque chose comme ceci:

définir les variables

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

remplacerprepareLayout() et après l'appel de 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
}
20
répondu Peter Stajger 2015-12-10 02:37:46

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()
})
19
répondu Sebastian 2015-09-21 09:38:30

j'ai fait cela en deux lignes de code (bien que ce soit sur une vue UITableView) mais je pense que vous seriez capable de le faire de la même manière.

puis j'ai fait tourner chaque cellule de tableview de 180 degrés aussi.

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

12
répondu Fogmeister 2014-08-28 12:01:21

ajouter à la réponse de Fogmeister( avec le code), l'approche la plus propre est d'Inverser (tourner à l'envers) la vue UICollectionView de sorte que vous ayez une vue de défilement qui est collante vers le bas plutôt que vers le haut. Cela fonctionne également pour UITableView, comme Fogmeister le souligne.

- (void)viewDidLoad
{
    [super viewDidLoad];

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

}

Dans Swift:

override func viewDidLoad() {
    super.viewDidLoad()

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

ceci a l'effet secondaire d'afficher aussi vos cellules à l'envers, donc vous devez les retourner aussi. Nous transférons donc la trasform (cell.transform = collectionView.transform) comme donc:

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

    cell.transform = collectionView.transform;

    return cell;
}

Dans Swift:

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 à se rappeler lors du développement sous cette conception est que le NSIndexPath paramètres délégués sont inversés. Donc indexPath.row == 0 est la rangée du bas de la vue de la collection où elle se trouve normalement au sommet.

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) maintenu par Mou

J'ai pensé ajouter un contexte de code à la réponse fantastique de Fogmeister!

6
répondu mattr 2015-08-16 18:54:45
@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
répondu Bryan Pratte 2016-07-22 19:20:10

Inspiré par Bryan Pratte's solution I developed subclass of Uicollection Viewflowlayout to get chat behavior sans tourner la collecte de vue à l'envers. Cette disposition est écrit dans Swift 3 et absolument utilisable avec RxSwift et RxDataSources parce que 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 est un nouveau message, faites défiler vers le bas. Peu importe où vous êtes sur la liste en ce moment. Le défilement est réalisé avec setContentOffset au lieu de scrollToItemAtIndexPath.
  2. si vous faites "chargement paresseux" avec des messages plus anciens, alors la vue de défilement ne devrait pas changer et reste exactement où elle est.
  3. ajouter des exceptions au début. La vue collection devrait se comporter "normal" jusqu'à ce qu'il y ait plus de messages que d'espace sur l'écran.

Mon solution: https://gist.github.com/jochenschoellig/04ffb26d38ae305fa81aeb711d043068

3
répondu Jochen Schöllig 2017-01-19 16:15:39

Voici une version légèrement modifiée de la solution de Peter (disposition de flux subclassant, pas d'approche à l'envers, approche légère). Swift 3. Remarque UIView.animate avec la durée zéro-c'est pour permettre l'animation de la régularité/étrangeté des cellules (ce qui est sur une rangée) animer, mais arrêter l'animation de l'offset viewport changer (qui serait terrible)

Utilisation:

        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: .item+twitterItems, section: 0) })
            }
            if let insertionIndexPaths = insertionIndexPaths, insertionIndexPaths.count > 0 {
                self.collectionview.insertItems(at: insertionIndexPaths.map { return IndexPath.init(item: .item+twitterItems, section: 0) })
            }
        }) { (finished) in
            completionBlock?()
        }

Ici 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
répondu xaphod 2017-05-31 15:30:17

pas la solution la plus élégante mais assez simple et efficace que j'ai retenue pour l'instant. Fonctionne seulement avec la disposition linéaire (pas de grille), mais il est très bien pour moi.

// 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
répondu mrvn 2014-08-30 05:44:30
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 vers le haut sans animation.

2
répondu Chad Podoski 2014-09-18 22:33:07

Code de version 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()
    })
2
répondu mriaz0011 2017-03-19 13:15:58

j'ai réussi à écrire une solution qui fonctionne pour les cas lors de l'insertion de cellules en haut et en bas en même temps.

  1. Enregistrer la position de la cellule visible supérieure. Calculer la hauteur de la cellule qui est sous le navBar (la vue du haut. dans mon cas, c'est l'auto.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'élément. Obtenir les attributs de cet indice. Extrait de l' décalage et le changement 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);
1
répondu grigorievs 2016-05-21 12:55:12

C'est ce que j'ai appris de JSQMessagesViewController: Comment maintenir la position de défilement?. Très simple, utile et PAS de 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
répondu Steven 2017-12-22 08:01:53

j'ai trouvé les cinq étapes de travailler en toute transparence:

  1. Préparer les données pour vos nouvelles cellules, et insérer les données appropriées

  2. Dire UIView pour arrêter l'animation

    UIView.setAnimationsEnabled(false)
    
  3. insérez réellement ces cellules

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

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

    avis pour remplacer CELL_HEIGHT par la hauteur de vos cellules (qui est seulement facile si les cellules sont de taille fixe). Il est important d'ajouter n'importe quelle marge / inserts cellule à cellule.

  5. n'oubliez pas de dire UIView pour démarrer l'animation de nouveau:

    UIView.setAnimationsEnabled(true)
    
1
répondu Teng L 2018-05-26 16:25:11

j'ai utilisé l'approche @James Martin, mais si vous utilisez les données de base et Nsfetchedresulscontroller la bonne approche est de stocker le nombre de messages plus tôt chargés dans _earlierMessagesLoaded et cochez la valeur dans le 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];
}
0
répondu Kappe 2015-02-10 12:43:02

alors que toutes les solutions ci-dessus fonctionnent pour moi, la principale raison de ceux qui échouent est que lorsque l'utilisateur défile alors que ces éléments sont ajoutés, scroll s'arrêtera ou il y aura un retard perceptible Voici une solution qui aide à maintenir la position (visuelle)de défilement tout en ajoutant des éléments dans le 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 mise en page se souvient de la hauteur des éléments qui sont sur le point d'être insérés, et la prochaine fois, quand la mise en page sera demandée offset, il compensera offset par le la hauteur des éléments ajoutés.

0
répondu tt.Kilew 2017-11-10 13:51:03

quelques-unes des approches suggérées ont eu plus ou moins de succès pour moi. J'ai finalement utilisé une variante du sous-classement et prepareLayout option Peter Stajger mettant ma correction de décalage dans finalizeCollectionViewUpdates. Cependant, aujourd'hui que je cherchais à quelques documents supplémentaires, j'ai trouvé targetContentOffset(forProposedContentOffset proposedContentOffset: CGPoint) et je pense que cela se sent beaucoup plus comme de l'emplacement prévu pour ce type de correction. Donc c'est mon implémentation en utilisant ça. Remarque mon implmentation était pour une série horizontale, mais cellsInsertingToTheLeft peut être facilement mis à jour en tant que 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
répondu SuperGuyAbe 2018-07-09 17:31:04

basé sur @Steven answer, j'ai réussi à faire insert cell avec scroll en bas, sans clignoter (et en utilisant des cellules auto), 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
répondu HelloimDarius 2018-09-27 08:03:38
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;
-1
répondu Bogdan Onu 2015-10-02 14:51:31