Как реализовать горизонтально бесконечное прокручивание UICollectionView?

Я хочу реализовать UICollectionView, который прокручивается горизонтально и бесконечно?

Ответ 1

Если ваши данные являются статическими и вы хотите своего рода циклическое поведение, вы можете сделать что-то вроде этого:

var dataSource = ["item 0", "item 1", "item 2"]

func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
    return Int.max // instead of returnin dataSource.count
}

func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {

    let itemToShow = dataSource[indexPath.row % dataSource.count]
    let cell = UICollectionViewCell() // setup cell with your item and return
    return cell
}

В основном вы говорите в своем представлении коллекции, что у вас огромное количество ячеек (Int.max не будет бесконечным, но может сделать трюк), и вы получаете доступ к источнику данных с помощью оператора%. В моем примере мы получим "item 0", "item 1", "item 2", "item 0", "item 1", "item 2"....

Надеюсь, это поможет:)

Ответ 2

По-видимому, наиболее подходящее решение было предложено Маникантой Адимулам. Самым чистым решением было бы добавить последний элемент в начале списка данных, а первый - в последнюю позицию списка данных (например: [4] [0] [1] [2] [3] [4] [ 0]), поэтому мы прокручиваем до первого элемента массива, когда запускаем последний элемент списка, и наоборот. Это будет работать для представлений коллекции с одним видимым элементом:

  1. Подкласс UICollectionView.
  2. Переопределите UICollectionViewDelegate и переопределите следующие методы:

    public func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
        let numberOfCells = items.count
        let page = Int(scrollView.contentOffset.x) / Int(cellWidth)
        if page == 0 { // we are within the fake last, so delegate real last
            currentPage = numberOfCells - 1
        } else if page == numberOfCells - 1 { // we are within the fake first, so delegate the real first
            currentPage = 0
        } else { // real page is always fake minus one
           currentPage = page - 1
        }
        // if you need to know changed position, you can delegate it
        customDelegate?.pageChanged(currentPage)
    }
    
    
    public func scrollViewDidScroll(_ scrollView: UIScrollView) {
        let numberOfCells = items.count
        if numberOfCells == 1 {
            return
        }
        let regularContentOffset = cellWidth * CGFloat(numberOfCells - 2)
        if (scrollView.contentOffset.x >= cellWidth * CGFloat(numberOfCells - 1)) {
            scrollView.contentOffset = CGPoint(x: scrollView.contentOffset.x - regularContentOffset, y: 0.0)
        } else if (scrollView.contentOffset.x < cellWidth) {
            scrollView.contentOffset = CGPoint(x: scrollView.contentOffset.x + regularContentOffset, y: 0.0)
        }
    }
    
  3. Переопределите метод layoutSubviews() внутри UICollectionView, чтобы всегда делать правильное смещение для первого элемента:

    override func layoutSubviews() {
        super.layoutSubviews()
    
        let numberOfCells = items.count
        if numberOfCells > 1 {
            if contentOffset.x == 0.0 {
                contentOffset = CGPoint(x: cellWidth, y: 0.0)
            }
        }
    }
    
  4. Переопределите метод init и рассчитайте размеры вашей ячейки:

    let layout = self.collectionViewLayout as! UICollectionViewFlowLayout
    cellPadding = layout.minimumInteritemSpacing
    cellWidth = layout.itemSize.width
    

Работает как шарм! Если вы хотите добиться этого эффекта в представлении коллекции с несколькими видимыми элементами, используйте решение, размещенное здесь.

Ответ 3

Я реализовал бесконечную прокрутку в UICollectionView. Сделал код доступным в github. Вы можете попробовать. Его в быстром 3.0.

InfiniteScrolling

Вы можете добавить его с помощью pod. Использование довольно простое. Просто проинсталлируйте InfiniteScrollingBehaviour, как показано ниже.

infiniteScrollingBehaviour = InfiniteScrollingBehaviour(withCollectionView: collectionView, andData: Card.dummyCards, delegate: self)

и реализовать требуемый метод делегирования для возврата настроенного UICollectionViewCell. Пример реализации будет выглядеть так:

func configuredCell(forItemAtIndexPath indexPath: IndexPath, originalIndex: Int, andData data: InfiniteScollingData, forInfiniteScrollingBehaviour behaviour: InfiniteScrollingBehaviour) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CellID", for: indexPath)
        if let collectionCell = cell as? CollectionViewCell,
            let card = data as? Card {
            collectionCell.titleLabel.text = card.name
        }
        return cell
    }

Он добавит соответствующие исходные и конечные граничные элементы в ваш исходный набор данных и отрегулирует collectionView contentOffset.

В методах обратного вызова он даст вам индекс элемента в исходном наборе данных.

Ответ 4

  1. Чтобы применить эту бесконечную функциональность цикла, вы должны иметь правильную схему collectionView

  2. Вам нужно сначала добавить первый элемент массива в последний и последний элемент массива
    ex: - array = [1,2,3,4]
    представляющий массив = [4,1,2,3,4,1]

    func infinateLoop(scrollView: UIScrollView) {
        var index = Int((scrollView.contentOffset.x)/(scrollView.frame.width))
        guard currentIndex != index else {
            return
        }
        currentIndex = index
        if index <= 0 {
            index = images.count - 1
            scrollView.setContentOffset(CGPoint(x: (scrollView.frame.width+60) * CGFloat(images.count), y: 0), animated: false)
        } else if index >= images.count + 1 {
            index = 0
            scrollView.setContentOffset(CGPoint(x: (scrollView.frame.width), y: 0), animated: false)
        } else {
            index -= 1
        }
        pageController.currentPage = index
    }
    
    func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
        infinateLoop(scrollView: scrollView)
    }
    func scrollViewDidScroll(_ scrollView: UIScrollView) {
        infinateLoop(scrollView: scrollView)
    }
    

Ответ 5

Проверенный код

Я достиг этого, просто повторяя ячейку за х раз. Как показано ниже,

Объявите, сколько циклов вы хотели бы иметь

let x = 50

Внедрить numberOfItems

func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
    return myArray.count*x // large scrolling: lets see who can reach the end :p
}

Добавьте эту функцию утилиты для вычисления arrayIndex с учетом строки indexPath

func arrayIndexForRow(_ row : Int) {
    return row % myArray.count
}

Внедрить cellForItem

func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
    let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "myIdentifier", for: indexPath) as! MyCustomCell
    let arrayIndex = arrayIndexForRow(indexPath.row)
    let modelObject = myArray[arrayIndex]
    // configure cell
    return cell
}

Добавить функцию утилиты для прокрутки до середины collectionView при заданном индексе

func scrollToMiddle(atIndex: Int, animated: Bool = true) {
    let middleIndex = atIndex + x*yourArray.count/2
    collectionView.scrollToItem(at: IndexPath(item: middleIndex, section: 0), at: .centeredHorizontally, animated: animated)
}

Ответ 6

Также подразумевая, что ваши данные являются статическими и что все ваши ячейки UICollectionView должны иметь одинаковый размер, я нашел это многообещающее решение.

Возможно, вам стоит просто загрузить пример проекта в github и запустить проект самостоятельно. Код в ViewController, который создает UICollectionView, довольно прост в использовании и прост в использовании.

Вы в основном выполните следующие действия:

  • Создайте InfiniteCollectionView в раскадровке
  • Устанавливать infiniteDataSource и infiniteDelegate
  • Внедрите необходимые функции, которые создадут ваши бесконечно прокручивающиеся ячейки

Ответ 7

Для тех, кто ищет бесконечные и горизонтально прокручиваемые коллекции, источники данных которых добавляются в конце, добавьте к источнику данных в scrollViewDidScroll и вызовите reloadData() в вашем представлении коллекции. Он будет поддерживать сдвиг прокрутки.

Пример кода ниже. Я использую свой просмотр коллекции для разбивки на страницы, где я загружаю больше страниц (целых месяцев), когда пользователь идет вправо (второй и последний):

func scrollViewDidScroll(scrollView: UIScrollView) {
    let currentPage = self.customView.collectionView.contentOffset.x / self.customView.collectionView.bounds.size.width

    if currentPage > CGFloat(self.months.count - 2) {
        let nextMonths = self.generateMonthsFromDate(self.months[self.months.count - 1], forPageDirection: .Next)
        self.months.appendContentsOf(nextMonths)
        self.customView.collectionView.reloadData()
    }

// DOESN'T WORK - adding more months to the left
//    if currentPage < 2 {
//        let previousMonths = self.generateMonthsFromDate(self.months[0], forPageDirection: .Previous)
//        self.months.insertContentsOf(previousMonths, at: 0)
//        self.customView.collectionView.reloadData()
//    }
}

EDIT: - Это не работает, когда вы вставляете в начале источника данных.

Ответ 9

Ответы, представленные здесь, хороши для реализации этой функции. Но, на мой взгляд, они содержат некоторые низкоуровневые обновления (установка смещения контента, манипулирование источником данных...), которых можно избежать. Если вы все еще не удовлетворены и ищете другой подход, то, что я сделал.

Основная идея состоит в том, чтобы обновлять количество ячеек каждый раз, когда вы достигаете ячейки до последней. Каждый раз, когда вы увеличиваете количество предметов на 1, это создает иллюзию бесконечной прокрутки. Для этого мы можем использовать scrollViewDidEndDecelerating(_ scrollView: UIScrollView) чтобы определить, когда пользователь завершил прокрутку, а затем обновить количество элементов в представлении коллекции. Вот фрагмент кода для достижения этой цели:

class InfiniteCarouselView: UICollectionView {

    var data: [Any] = []

    private var currentIndex: Int?
    private var currentMaxItemsCount: Int = 0
    // Set up data source and delegate
}

extension InfiniteCarouselView: UICollectionViewDataSource {
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        // Set the current maximum to a number above the maximum count by 1
        currentMaxItemsCount = max(((currentIndex ?? 0) + 1), data.count) + 1
        return currentMaxItemsCount

    }

    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath)
        let row = indexPath.row % data.count
        let item = data[row]
        // Setup cell
        return cell
    }
}

extension InfiniteCarouselView: UICollectionViewDelegateFlowLayout {
    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
        return CGSize(width: collectionView.frame.width, height: collectionView.frame.height)
    }

    // Detect when the collection view has finished scrolling to increase the number of items in the collection view
    func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
        // Get the current index. Note that the current index calculation will keep changing because the collection view is expanding its content size based on the number of items (currentMaxItemsCount)
        currentIndex = Int(scrollView.contentOffset.x/scrollView.contentSize.width * CGFloat(currentMaxItemsCount))
        // Reload the collection view to get the new number of items
        reloadData()
    }
}

Pros

  • Простая реализация
  • Не использовать Int.max (что, по моему мнению, не очень хорошая идея)
  • Не использовать произвольное число (например, 50 или что-то еще)
  • Без изменений или манипуляций с данными
  • Нет ручного обновления смещения содержимого или любых других атрибутов прокрутки

Cons

  • Пейджинг должен быть включен (хотя логика может быть обновлена, чтобы не поддерживать пейджинг)
  • Необходимо поддерживать ссылку на некоторые атрибуты (текущий индекс, текущий максимальный счет)
  • Необходимо перезагрузить представление коллекции на каждом конце прокрутки (Ничего страшного, если видимые ячейки минимальны). Это может сильно повлиять на вас, если вы загружаете что-то асинхронно без кэширования (что является плохой практикой, и данные должны кэшироваться вне ячеек)
  • Не работает, если вы хотите бесконечную прокрутку в обоих направлениях