Ios 在UICollectionView(或UITableView)swift中循环数据

Ios 在UICollectionView(或UITableView)swift中循环数据,ios,swift,uitableview,uicollectionview,Ios,Swift,Uitableview,Uicollectionview,我正在尝试创建一个将无限滚动的UICollectionView。这个想法是,当您到达数据数组的底部时,它会重新开始 我是通过为numberOfItemsInSection返回一个更大的数字,然后执行%来从数组中获取数据 这很好,我理解: override func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { return 500 } o

我正在尝试创建一个将无限滚动的
UICollectionView
。这个想法是,当您到达数据数组的底部时,它会重新开始

我是通过为
numberOfItemsInSection
返回一个更大的数字,然后执行
%
来从数组中获取数据

这很好,我理解:

override func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
    return 500
}

override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
    let cell = collectionView.dequeueReusableCellWithReuseIdentifier(cellIdentifier, forIndexPath: indexPath) as! PhotoCell

    let index = indexPath.item % photos.count
    let url = photos[index]
}

我的问题是,这是实现此功能的最佳方式吗?我一直在网上无休止地四处寻找,找不到关于如何做的任何其他建议(使用
UICollectionView

有趣的问题!这是一个不错的方法,但缺点是滚动条指示器的大小将非常小


您可以将项目数设置为实际项目数的两倍,然后在用户滚动到下半部分(滚动停止)后,重新调整数据偏移量和滚动位置,然后重新加载。用户将看不到任何变化,但一旦他们再次滚动,滚动条的位置似乎已经在顶部附近跳了起来。我喜欢这一点,因为滚动指示器的大小将保持合理,用户实际上会得到一些视觉反馈,他们滚动到最后,现在正在重复。

您的代码是最简单的解决方案。而且在大多数情况下,它将非常适合。如果您想实现诚实的无限滚动,您应该创建自己的布局和单元格缓存。

您可以找到更多详细信息

资料来源:
你所拥有的一切都很好。另一个选项是构建一个集合,该集合包装数据源阵列(
照片
),并提供对其内容的循环访问:

struct LoopedCollection<Element>: CollectionType {
    let _base: AnyRandomAccessCollection<Element>

    /// Creates a new LoopedCollection that wraps the given collection.
    init<Base: CollectionType where Base.Index: RandomAccessIndexType, Base.Generator.Element == Element>(_ base: Base, withLength length: Int = Int.max) {
        self._base = AnyRandomAccessCollection(base)
        self.endIndex = length
    }

    /// The midpoint of this LoopedCollection, adjusted to match up with
    /// the start of the base collection.
    var startAlignedMidpoint: Int {
        let mid = endIndex / 2
        return mid - mid % numericCast(_base.count)
    }

    // MARK: CollectionType

    let startIndex: Int = 0
    let endIndex: Int

    subscript(index: Int) -> Element {
        precondition(index >= 0, "Index must not be negative.")
        let adjustedIndex = numericCast(index) % _base.count
        return _base[_base.startIndex.advancedBy(adjustedIndex)]
    }
}
然后,您可以最终将集合视图方法转换为更通用的循环集合,或直接使用循环集合:

override func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
    return loopedPhotos.count
}

override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
    let cell = collectionView.dequeueReusableCellWithReuseIdentifier(cellIdentifier, forIndexPath: indexPath) as! PhotoCell

    let url = loopedPhotos[index]
}

你需要的是两件事:

  • 监视当前滚动偏移,并在用户接近底部时通知
  • 触发此阈值时,向UICollectionView/UITableView发送信号,表示有更多行,并向数据源追加新行
  • 要执行1非常简单,我们可以简单地扩展UIScrollView:

    extension UIScrollView {
        /**
        A convenience method that returns true when the scrollView is near to the bottom        
        - returns: true when the current contentOffset is close enough to the bottom to merit initiating a load for the next page
        */
        func canStartLoadingNextPage() -> Bool {
            //make sure that we have content and the scrollable area is at least larger than the scroll views bounds.
            if contentOffset.y > 0 && contentSize.height > 0 && (contentOffset.y + CGRectGetHeight(bounds))/contentSize.height > 0.7 
                return true
            }
            return false
        }
    }
    
    当您达到当前内容大小的70%时,此函数将返回true,但可以根据需要随意调整

    现在,在我们的
    cellforrowatinexpath
    中,我们可以从技术上调用函数来确定是否可以附加数据集

    func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCellWithReuseIdentifier("niceReuseIdentifierName", forIndexPath: indexPath)        
    
        if collectionView.canStartLoadingNextPage() {
            //now we can append our data
            self.loadNextPage()
        }
        
        return cell
    }
    
    func loadNextPage() {
        self.collectionView.performBatchUpdates({ () -> Void in
            let nextBatch = self.pictures//or however you get the next batch
            self.pictures.appendContentsOf(nextBatch)
            self.collectionView.reloadSections(NSIndexSet(index: 0))
            }, completion: nil)
    }
    
    瞧,你现在应该有无限卷轴了

    改进和未来证明 为了改进这段代码,您可以使用一个对象来促进任何UIScrollView子类的预加载。这还可以更容易地过渡到网络通话和更复杂的逻辑:

    class ScrollViewPreloader {
        
        enum View {
            case TableView(tableView: UITableView)
            case CollectionView(collectionView: UICollectionView)
        }
    
        private (set) var view: View
        private (set) var pictures: [Picture] = []
        
        init(view: View) {
            self.view = view
        }
        
        func loadNextPageIfNeeded() {
            
            func shouldLoadNextPage(scrollView: UIScrollView) -> Bool {
                return scrollView.canStartLoadingNextPage()
            }
            
            switch self.view {
            case .TableView(tableView: let tableView):
                if shouldLoadNextPage(tableView) {
                   loadNextPageForTableView(tableView)
                }
                break
            case .CollectionView(collectionView: let collectionView):
                if shouldLoadNextPage(collectionView) {
                    loadNextPageForCollectionView(collectionView)
                }
                break
            }
        }
        
        private func loadNextBatchOfPictures() -> [Picture] {
            let nextBatch = self.pictures//or however you get the next batch
            self.pictures.appendContentsOf(nextBatch)
            return self.pictures
        }
        
        private func loadNextPageForTableView(tableView: UITableView) {
            tableView.beginUpdates()
            loadNextBatchOfPictures()
            tableView.reloadData()//or you could call insert functions etc
            tableView.endUpdates()
        }
        
        private func loadNextPageForCollectionView(collectionView: UICollectionView) {
            collectionView.performBatchUpdates({ () -> Void in
                self.loadNextBatchOfPictures()
                collectionView.reloadSections(NSIndexSet(index: 0))
                }, completion: nil)
            
        }
    }
    
    struct Picture {
        
    }
    


    如果我理解正确,您可以使用cellForRow中的
    LoadNextPageiFneed()
    函数调用它,您的意思是当我到达列表末尾(而不是滚动)时重置
    contentOffset
    有效地将它们移回列表顶部。正确吗?假设一个数据集的高度为1000。当用户在1200停止滚动时,然后在没有动画的情况下将contentOffset静默重置为200。视图应与之前完全相同,只是当用户再次滚动时,他将看到滚动指示器已“向上”移动。根据您表格的大小,您可以进行2x、4x、10x等操作。用户当然可以通过持续不断地向上滑动来“愚弄”这个算法,从而将scrollview强制到底部单元格,但我怀疑这并不是什么现实问题。顺便说一句,您可以隐藏scroller旋钮。@DavidH,你能帮我解决这个问题吗?我去年尝试过这个方法,但我所能实现的大部分都是由于重新绘制表格而导致的视觉故障,我最终使用了一个类似这样的第三方库:我和你的代码完全一样。这是最简单的解决方案,视觉效果也很好。这是一个有趣的想法。但我想它仍然不能解决如何提供无限卷轴的问题。可能需要监视滚动,然后在用户接近底部时插入行…这正是我所需要的。你真棒@DanielGalasko更重要的是,您需要在循环集合的“中间”启动collectionview,然后在用户滚动太远时重置回该起始点。例如,对于UIPicker来说,这并不太糟糕,但是集合视图要复杂得多-您需要处理许多不同的状态。如果它是循环集合,模运算不起作用吗?那么他们所需要做的就是跟踪整体尺寸?@NateCook,兄弟,你能帮我解决这个问题吗?这是一个很好的答案,但问题是即使在加载了第一批完整的数据之后,内存占用也会持续增长。你是否重复添加
    self.pictures。在我们继续向下滚动时,将
    batches的内容添加到
    self.pictures
    。一旦我们加载了所有图片并将它们添加到
    self.pictures
    中,我们就不想再添加了。作为一个切线,你在这里的回答是史诗般的,拯救了我的屁股:不@random,我们只在达到页面70%时才添加到数组中。该代码片段反映了我在生产应用程序中使用的内容。调用PerformBatchUpdate后,内容大小会增加。我认为这是将循环列表与分页数据的自动加载混为一谈。两者都可以让你永远滚动,但OP寻找的是一组循环回到顶部的有限数据,而不是一种滚动(实际上是)无限数据集的方式。这是一个极好的答案和很棒的帖子,但@natecooks answer more有助于解决我的问题。@MeghsDhameliya,兄弟,你能帮我解决问题吗?@MayPhyu结帐回答并让我知道是的兄弟@MeghsDhameliya。谢谢兄弟。我会试试你的建议。@MeghsDhameliya,兄弟,我还有一个问题。你能看看那个问题吗?
    class ScrollViewPreloader {
        
        enum View {
            case TableView(tableView: UITableView)
            case CollectionView(collectionView: UICollectionView)
        }
    
        private (set) var view: View
        private (set) var pictures: [Picture] = []
        
        init(view: View) {
            self.view = view
        }
        
        func loadNextPageIfNeeded() {
            
            func shouldLoadNextPage(scrollView: UIScrollView) -> Bool {
                return scrollView.canStartLoadingNextPage()
            }
            
            switch self.view {
            case .TableView(tableView: let tableView):
                if shouldLoadNextPage(tableView) {
                   loadNextPageForTableView(tableView)
                }
                break
            case .CollectionView(collectionView: let collectionView):
                if shouldLoadNextPage(collectionView) {
                    loadNextPageForCollectionView(collectionView)
                }
                break
            }
        }
        
        private func loadNextBatchOfPictures() -> [Picture] {
            let nextBatch = self.pictures//or however you get the next batch
            self.pictures.appendContentsOf(nextBatch)
            return self.pictures
        }
        
        private func loadNextPageForTableView(tableView: UITableView) {
            tableView.beginUpdates()
            loadNextBatchOfPictures()
            tableView.reloadData()//or you could call insert functions etc
            tableView.endUpdates()
        }
        
        private func loadNextPageForCollectionView(collectionView: UICollectionView) {
            collectionView.performBatchUpdates({ () -> Void in
                self.loadNextBatchOfPictures()
                collectionView.reloadSections(NSIndexSet(index: 0))
                }, completion: nil)
            
        }
    }
    
    struct Picture {
        
    }