Цикл данных в 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, вы должны создать свой собственный макет и кэширование ячеек.


вы можете найти более подробную информацию здесь

источник : iosnomad enter image description here


что вам нужно, это две вещи:

  1. контролировать текущее смещение прокрутки и уведомлять, когда пользователь находится близко к нижней части.
  2. когда этот порог срабатывает, сигнализируйте 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 {

}

и вы сможете вызвать его, используя