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

后端 未结 20 2027
渐次进展
渐次进展 2020-12-02 07:23

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 appea

相关标签:
20条回答
  • 2020-12-02 08:08

    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];
    
    0 讨论(0)
  • 2020-12-02 08:09

    This is what I learned from JSQMessagesViewController: How maintain scroll position?. Very simple, useful and NO flicker!

     // 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)
    
    0 讨论(0)
  • 2020-12-02 08:11

    Swift 3 version code: based on James Martin answer

        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()
        })
    
    0 讨论(0)
  • 2020-12-02 08:11

    Love James Martin’s solution. But for me it started to breakdown when inserting/deleting above/below a specific content window. I took a stab at subclassing UICollectionViewFlowLayout to get the behavior I wanted. Hope this helps someone. Any feedback appreciated :)

    @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];
            }
        }
    }
    
    0 讨论(0)
  • 2020-12-02 08:12
    // stop scrolling
    setContentOffset(contentOffset, animated: false)
    
    // calculate the offset and reloadData
    let beforeContentSize = contentSize
    reloadData()
    layoutIfNeeded()
    let afterContentSize = contentSize
    
    // reset the contentOffset after data is updated
    let newOffset = CGPoint(
      x: contentOffset.x + (afterContentSize.width - beforeContentSize.width),
      y: contentOffset.y + (afterContentSize.height - beforeContentSize.height))
    setContentOffset(newOffset, animated: false)
    
    0 讨论(0)
  • 2020-12-02 08:15

    Based on @Steven answer, I managed to make insert cell with scroll to the bottom, without any flickering (and using auto cells), tested on 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 讨论(0)
提交回复
热议问题