UICollectionView动态数据源项添加到集合视图的开头

时间:2015-07-15 17:17:41

标签: ios swift uiscrollview uicollectionview uicollectionviewlayout

我正在使用动态内容加载功能创建集合视图。在我的情况下,我正在制作日历。当前日期将在可见部分(加载后)。如果向上滚动,则会显示较旧的日期,如果向下滚动,则会显示下一个日期。

我的收藏视图使用标准流程布局。我连续有7个单元格,有4个可见行。

我在添加旧日期时遇到问题(例如,如果向上滚动,则会显示单元格)。我在做什么:首先我实现滚动视图方法来检测滚动事件。 startOffsetY是viewDidLoad中设置的contentOffset.y值。它不等于0,因为我设置了contentInsetupd只是一个标志,表示可以启动新的更新。

func scrollViewDidScroll(scrollView: UIScrollView) {
   if scrollView.contentOffset.y <= startOffsetY - 20 && upd {
      calendar.updateDataSource()
   }
}

接下来,我从我的数据源数组中的第一个日期开始计算上一个时间片的日期(大约4周,我也尝试过1周)。

之后,我计算indexPath数组并更新集合视图:

var indexes = [NSIndexPath]()
for n in 1...4  {    
   for i in reverse(1...7) {
      indexes.append(NSIndexPath(forItem: n * 7 - i, inSection: 0))
   }     
}

self.calendarCollectionView.performBatchUpdates({ () -> Void in

    self.calendarCollectionView.insertItemsAtIndexPaths(indexes)
}, completion: { (finish) -> Void in

    self.upd = true                
})

但是,当添加行并且正在进行滚动时,我有明显的滞后。

我尝试了不同的策略:我使用reloadData()它是理想的(在模拟器上)并且在我的iPhone 4S上非常滞后(这是我体验模拟器比设备更快的第一次)。从这一点来说,我发现,插入项目的动画可能是问题所在。我试图将performBatchUpdates包裹到UIView.performWithoutAnimation块中,但也没有运气。

我真的在寻求一些帮助,我不是在寻找现成的解决方案,除非它们像我描述的那样工作(向上滚动'向下并加载内容),我可以看看它是如何工作的。再一次,滚动已经加载的项目不是问题,在我的数据数组的乞讨时添加内容时问题是滞后。

修改

由@teamnorge在Swift中提供的代码

func scrollViewDidScroll(scrollView: UIScrollView) {

    let currentOffset = scrollView.contentOffset.y
    let contentHeight = scrollView.contentSize.height
    var offset : CGFloat!

    println(currentOffset)

    /*  0.125 - 1/8,  0.5 - 1/2  */
    if currentOffset < contentHeight * 0.125 {

        offset = contentHeight * 0.125 - currentOffset


        // reload content here

        scrollView.contentOffset = CGPoint(x: 0, y: currentOffset + contentHeight * 0.5 + offset - CGFloat(cellHeight))

    }


    /*  0.75 - 6/8,  0.5 - 1/2  */
    if currentOffset > contentHeight * 0.75 {

        offset = currentOffset - contentHeight * 0.75

        // reload content here


        scrollView.contentOffset = CGPoint(x: 0, y: currentOffset - contentHeight * 0.5 - offset + CGFloat(cellHeight))

 }

它非常好用,但需要使用contentOffset y公式,因为现在只有当你向上滚动时才有效。将在明天修复此问题并添加日历日期计算。

编辑2

重新加载数据会破坏我所有原型中的所有内容。包括我之前制作的那个。发现了一些使滞后降低但仍然非常明显且完全不可接受的东西。以下是这些事情:

  • 从故事板中的单元格原型中删除autolayout
  • 将此代码添加到单元格中:

    cell.layer.shouldRasterize = true
    cell.layer.rasterizationScale = UIScreen.mainScreen().scale
    

我认为布局失效导致这些滞后。那么,我可能需要制作自定义流程布局?如果是这样,你可以给出什么建议。

1 个答案:

答案 0 :(得分:2)

这实际上是非常有趣的话题。我将尝试解释我在处理另一个日历应用程序时提出的想法。

在我的情况下,它不是日历视图,而是日视图,小时 00:00 - 24:00 从上到下列出,所以我有UITableView而不是{{1但在这种情况下,它并不重要。

使用的语言不是Swift而是Objective-C,所以我只是尝试翻译代码示例。

我使用固定数量的行创建UICollectionView而不是使用数据源进行操作,在我的情况下,只存储两天(48行,每天24小时两天)。您可以选择包含两个完整屏幕的行数。

重要的是行总数必须是8的倍数。

然后,您需要一个公式来根据第一个可见行内的内容计算每个特定单元格的日期编号。

我的想法是UITableView实际上是UICollectionView所以当我们向下或向上滚动时,我们可以处理相应的事件并计算可见的偏移量。

要模拟不定式滚动,我们处理UIScrollView并检查您是否刚刚向scrollViewDidScroll高度1/8向上滚动,将UIScrollView移至UIScrollView 1}}高度加上精确的偏移量,以便顺利地移动到“第二个屏幕”。如果您在向下滚动时超过1/2高度的6/8,请将UIScrollView向上移动到其高度的UIScrollView减去偏移量。

执行此操作时,您会看到滚动指示器上下跳动,这非常令人困惑所以我们必须将其隐藏起来,只需将其放在1/2中:

viewDidLoad

其中calendarView.showsHorizontalScrollIndicator = false calendarView.showsVerticalScrollIndicator = false 是您的calendarView实例名称。

这是代码(在这里翻译自Objective-C,未在实际项目中尝试过):

UICollectionView

然后在func scrollViewDidScroll(scrollView_:UIScrollView) { if !enableScrollingHandling {return} var currentOffsetX:CGFloat = scrollView_.contentOffset.x var currentOffSetY:CGFloat = scrollView_.contentOffset.y var contentHeight:CGFloat = scrollView_.contentSize.height var offset:CGFloat /* 0.125 - 1/8, 0.5 - 1/2 */ if currentOffSetY < (contentHeight * 0.125) { enableScrollingHandling = false offset = (contentHeight * 0.125) - currentOffSetY // @todo: your code, specify which days are listed in the first row (2nd screen) scrollView_.contentOffset = CGPoint(x: currentOffsetX, y: currentOffset + contentHeight * 0.5 + offset - CGFloat(kRowHeight)) enableScrollingHandling = true return } /* 0.75 - 6/8, 0.5 - 1/2 */ if currentOffSetY > (contentHeight * 0.75) { enableScrollingHandling = false offset = currentOffSetY - (contentHeight * 0.75) // @todo: your code, specify which days are listed in the first row (1st screen) scrollView_.contentOffset = CGPoint(x: currentOffsetX, y: currentOffset - contentHeight * 0.5 - offset + CGFloat(kRowHeight)) enableScrollingHandling = true return } } 中根据第一行中的内容(第一个屏幕或第二个屏幕的内容)以及当前可见天数(公式),您只需更新单元格内容即可。

此公式也可能是一个棘手的部分,可能需要一些解决方法,尤其是如果您决定将月份名称放在几个月之间。我也有一些关于如何组织它的想法,所以如果你遇到任何问题我们可以在这里讨论。

但是这种模拟不定式滚动的方法“两个屏幕循环和跳跃之间”的工作非常像一个魅力,非常流畅的滚动就像在旧手机上测试的行为一样。

PS: kRowHeight 只是一个常数,它是精确平滑滚动行为所需的精确行(单元格)的高度,我想可以跳过它。

更新

重要提示,我从原始代码中跳过此内容,但请注意这一点非常重要。当您手动设置collectionView:cellForItemAtIndexPath:时,您还会触发contentOffset事件,为了防止这种情况,您需要临时禁用scrollViewDidScroll事件处理。您可以通过添加状态变量scrollViewDidScroll并更改其状态enableScrollHandling来执行此操作,请参阅更新的代码。