web-dev-qa-db-ja.com

水平方向に無限のスクロールUICollectionViewを実装する方法は?

水平および無限にスクロールするUICollectionViewを実装したいですか?

31
Abdelrahman

データが静的で、一種の循環動作が必要な場合は、次のようなことができます。

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は無限ではありませんが、トリックを行う可能性がある)とコレクションビューに言い、%演算子を使用してデータソースにアクセスします。私の例では、「アイテム0」、「アイテム1」、「アイテム2」、「アイテム0」、「アイテム1」、「アイテム2」...になります。

これが役立つことを願っています:)

38
cicerocamargo

Manikanta Adimulamにより、最も良い解決策が提案されたようです。最もクリーンなソリューションは、データリストの先頭に最後の要素を追加し、最後のデータリストの位置に最初の要素を追加することです(例:[4] [0] [1] [2] [3] [4] [ 0])。したがって、最後のリスト項目をトリガーするときに最初の配列項目までスクロールします。逆の場合も同様です。これは、1つの表示項目があるコレクションビューで機能します。

  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. UICollectionView内のlayoutSubviews()メソッドをオーバーライドして、常に最初のアイテムの正しいオフセットを作成します。

    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
    

チャームのように機能します!複数の表示項目があるコレクションビューでこの効果を達成したい場合は、 here に投稿されたソリューションを使用してください。

10
Bio-Matic

UICollectionViewに無限スクロールを実装しました。 githubでコードを利用できるようにしました。試してみてください。そのSwift 3.0。

InfiniteScrolling

ポッドを使用して追加できます。使い方はとても簡単です。以下のように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
Vishal Singh
  1. この無限ループ機能を適用するには、適切なcollectionViewレイアウトが必要です。

  2. 配列の最初の要素を最後に追加し、配列の最後の要素を最初に追加する必要があります
    ex:-array = [1,2,3,4]
    presenting array = [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)
    }
    
1

テスト済みコード

これを達成するために、単純にxセルをx回繰り返します。次のように、

ループの数を宣言します

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
}

このユーティリティ関数を追加して、indexPath行でarrayIndexを計算します

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)
}
1
Jože Ws

データソースが最後に追加される無限および水平方向にスクロールするコレクションビューを探している場合-scrollViewDidScrollでデータソースに追加し、コレクションビューでreloadData()を呼び出します。スクロールオフセットを維持します。

以下のサンプルコード。ページ分割された日付ピッカーにコレクションビューを使用します。ユーザーが右端(2番目から最後)に向かっているときに(月全体の)より多くのページを読み込みます。

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 [〜#〜]:-データソースの先頭に挿入する場合、これは機能しないようです。

0
Matthew Quiros

ここで提供される回答は、機能を実装するのに適しています。しかし、私の意見では、それらには回避できる低レベルの更新(コンテンツオフセットの設定、データソースの操作など)が含まれています。まだ満足しておらず、別のアプローチを探している場合は、私がやったことです。

主なアイデアは、最後のセルの前にセルに到達するたびにセルの数を更新することです。アイテムの数を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()
    }
}

長所

  • 簡単な実装
  • Int.maxを使用しない(私の意見では、これは良い考えではありません)
  • 任意の番号を使用しない(50など)
  • データの変更または操作なし
  • コンテンツオフセットまたはその他のスクロールビュー属性の手動更新はありません

短所

  • ページングを有効にする必要があります(ただし、ページングをサポートしないようにロジックを更新できます)
  • 一部の属性(現在のインデックス、現在の最大カウント)の参照を維持する必要がある
  • 各スクロールエンドでコレクションビューを再ロードする必要があります(表示されるセルが最小の場合は大したことではありません)。キャッシュせずに非同期で何かをロードしている場合、これは劇的に影響する可能性があります(これは悪い習慣であり、データはセルの外側にキャッシュされるべきです)
  • 双方向の無限スクロールが必要な場合は機能しません
0
Santina

また、データが静的であり、すべてのUICollectionViewセルが同じサイズでなければならないことを意味します この有望なソリューション

サンプルプロジェクトを github からダウンロードして、自分でプロジェクトを実行する必要があります。 UICollectionViewを作成するViewControllerのコードは非常に単純で使いやすいです。

基本的には次の手順に従います:

  • ストーリーボードでInfiniteCollectionViewを作成します
  • infiniteDataSourceおよびinfiniteDelegateを設定します
  • 無限にスクロールするセルを作成するために必要な機能を実装する
0
SebastianR