UICollectionView在保持位置之上插入单元格(如Messages.app)

mrv*_*rvn 41 ios uicollectionview

默认情况下,Collection View在插入单元格时保持内容偏移.另一方面,我想在当前显示的单元格上方插入单元格,以便它们显示在屏幕顶部边缘上方,就像Messages.app在加载早期消息时一样.有谁知道实现它的方法?

小智 57

这是我使用的技术.我发现其他人会引起奇怪的副作用,例如屏幕闪烁:

    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];
Run Code Online (Sandbox Code Playgroud)

  • 在更新时工作得非常轻微闪烁,但不错.最好的我发现除了颠倒整个事情.当我做翻转时虽然它使我的集合视图非常滞后,因为它有很多东西.所以对我来说这是最好的. (2认同)

Seb*_*ian 23

詹姆斯·马丁的精彩版本转换为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()
})
Run Code Online (Sandbox Code Playgroud)


Pet*_*ger 23

我的方法利用了子类流布局.这意味着您不必在视图控制器中破解滚动/布局代码.想法是,只要你知道你在顶部插入单元格就设置了自定义属性,你就会标记下一个布局更新会将单元格插入到顶部,并且在更新之前你会记住内容大小.然后覆盖prepareLayout()并在那里设置所需的内容偏移量.它看起来像这样:

定义变量

private var isInsertingCellsToTop: Bool = false
private var contentSizeWhenInsertingToTop: CGSize?
Run Code Online (Sandbox Code Playgroud)

覆盖 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
}
Run Code Online (Sandbox Code Playgroud)

  • 到目前为止,这可能是最好的选择.看起来很有希望 (3认同)

Fog*_*ter 12

我用两行代码做了这个(尽管它是在UITableView上),但我认为你能够以同样的方式做到这一点.

我将桌面视图旋转了180度.

然后我也将每个tableview单元旋转了180度.

这意味着我可以将它视为标准的从上到下的桌子,但底部被视为顶部.

  • 更好的比例转换为(1,-1) (3认同)
  • @Fogmeister更好地缩放表(1,-1)和单元格(1,-1).如果旋转,还可以将滚动指示器翻转到屏幕的另一侧.在x轴上反射可保持滚动指示. (3认同)
  • 天才的方法! (2认同)

mri*_*011 7

Swift 3 版本代码:基于 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 index in 0..<amount {
        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()
    })
Run Code Online (Sandbox Code Playgroud)


mat*_*ttr 6

添加到Fogmeister的答案(使用代码),最干净的方法是反转(颠倒),UICollectionView以便您有一个粘贴到底部而不是顶部的滚动视图.UITableView正如Fogmeister指出的那样,这也适用.

- (void)viewDidLoad
{
    [super viewDidLoad];

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

}
Run Code Online (Sandbox Code Playgroud)

在Swift中:

override func viewDidLoad() {
    super.viewDidLoad()

    collectionView.transform = CGAffineTransformMake(1, 0, 0, -1, 0, 0)
}
Run Code Online (Sandbox Code Playgroud)

这有副作用,也可以倒置显示你的细胞,所以你也必须翻转它们.所以我们cell.transform = collectionView.transform像这样传输trasform():

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

    cell.transform = collectionView.transform;

    return cell;
}
Run Code Online (Sandbox Code Playgroud)

在Swift中:

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

    cell.transform = collectionView.transform

    return cell
}
Run Code Online (Sandbox Code Playgroud)

最后,在这种设计下开发时要记住的主要事情是代理中的NSIndexPath参数是相反的.那条indexPath.row == 0collectionView通常位于顶部的底部.

在许多开源项目中使用此技术来产生所描述的行为,包括由Slack维护的流行的SlackTextViewController(https://github.com/slackhq/SlackTextViewController)

以为我会为Fogmeister的奇妙答案添加一些代码上下文!


xap*_*hod 5

这是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?()
        }
Run Code Online (Sandbox Code Playgroud)

下面是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
            }
        }
    }
Run Code Online (Sandbox Code Playgroud)