Цикл данных в UICollectionView (или UITableView) swift
Я пытаюсь сделать 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
).
5 ответов
у вас это прекрасно. Другой вариант-создать коллекцию, которая обертывает массив исходных данных (photos
) и предлагает зацикленный доступ к его содержимому:
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)]
}
}
вы можете объявить эту коллекцию цикла вместе с вашим photos
время:
let photos: [NSURL] = ...
lazy var loopedPhotos: LoopedCollection<NSURL> = LoopedCollection(self.photos)
и затем вы можете в конечном итоге перейти к методам просмотра коллекции, чтобы быть более универсальными в циклической коллекции, или использовать циклическую коллекцию напрямую:
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]
}
интересный вопрос! Не плохой подход, но недостатком является то, что размер индикатора полосы прокрутки будет очень мало.
вы можете установить количество элементов в два раза больше фактического количества элементов, затем, как только пользователь прокрутил во вторую половину (и прокрутка остановлена), повторно отрегулируйте смещение данных и положение прокрутки, а затем перезагрузите. Пользователь не увидит никаких изменений, но как только они снова перейдут к прокрутке, позиция скроллера, похоже, подпрыгнула в верхней части. Я как это, так как размер индикатора прокрутки останется разумным, и пользователь фактически получит визуальную обратную связь, которую они прокрутили до конца и теперь повторяют.
ваш код является самым простым решением. И в большинстве случаев он идеально подойдет. Если вы хотите реализовать honest infinity scroll, вы должны создать свой собственный макет и кэширование ячеек.
что вам нужно, это две вещи:
- контролировать текущее смещение прокрутки и уведомлять, когда пользователь находится близко к нижней части.
- когда этот порог срабатывает, сигнализируйте 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
}
}
эта функция возвратит true когда вы достигнете 70% из течения размер контента, но не стесняйтесь настраивать по мере необходимости.
сейчас в нашем cellForRowAtIndexPath
мы могли бы технически вызвать нашу функцию, чтобы определить, можем ли мы добавить наш набор данных.
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 {
}
и вы сможете вызвать его, используя