it-swarm.com.de

Wie implementiere ich horizontal unendliches Scrollen von UICollectionView?

Ich möchte UICollectionView implementieren, das horizontal und unendlich scrollt.

22
Abdelrahman

Wenn Ihre Daten statisch sind und Sie eine Art kreisförmiges Verhalten wünschen, können Sie Folgendes tun:

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
}

Grundsätzlich sagen Sie zu Ihrer Sammlungsansicht, dass Sie über eine große Anzahl von Zellen verfügen (Int.max ist nicht unendlich, kann aber den Trick ausführen) und Sie greifen mit dem Operator% auf Ihre Datenquelle zu. In meinem Beispiel enden wir mit "item 0", "item 1", "item 2", "item 0", "item 1", "item 2" ....

Ich hoffe das hilft :)

32
cicerocamargo

Die beste Lösung wurde anscheinend von Manikanta Adimulam vorgeschlagen. Die sauberste Lösung wäre, das letzte Element am Anfang der Datenliste und das erste an die letzte Datenlistenposition hinzuzufügen (Beispiel: [4] [0] [1] [2] [3] [4] [ 0]), so scrollen wir zum ersten Feldelement, wenn wir das letzte Listenelement auslösen, und umgekehrt. Dies funktioniert für Sammlungsansichten mit einem sichtbaren Element:

  1. Unterklasse UICollectionView.
  2. Überschreiben Sie UICollectionViewDelegate und überschreiben Sie die folgenden Methoden:

    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. Überschreiben Sie die layoutSubviews () -Methode in Ihrer UICollectionView, um immer einen korrekten Offset für das erste Element zu erstellen:

    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. Überschreiben Sie die init-Methode und berechnen Sie Ihre Zellabmessungen:

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

Funktioniert wie ein Zauber! Wenn Sie diesen Effekt mit einer Sammlungsansicht erzielen möchten, die mehrere sichtbare Elemente aufweist, verwenden Sie die Lösung hier .

6
Bio-Matic

Ich habe unendliches Scrollen in UICollectionView implementiert. Den Code in github verfügbar gemacht. Du kannst es versuchen. Es ist in Swift 3.0.

Unendliches Scrollen

Sie können es mit pod hinzufügen. Die Nutzung ist ziemlich einfach. Initialisieren Sie einfach die InfiniteScrollingBehaviour wie folgt.

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

und implementieren Sie die erforderliche Delegatmethode, um eine konfigurierte UICollectionViewCell zurückzugeben. Eine Beispielimplementierung sieht wie folgt aus:

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
    }

Sie fügt dem ursprünglichen Datensatz die entsprechenden führenden und nachlaufenden Begrenzungselemente hinzu und passt contentOffset von collectionView an.

In den Rückmeldemethoden erhalten Sie den Index eines Elements im ursprünglichen Datensatz. 

4
Vishal Singh
  1. So wenden Sie diese Endlosschleifenfunktion an Sie sollten über ein geeignetes collectionView-Layout verfügen

  2. Sie müssen zuerst das letzte Element des Arrays und das letzte Element des Arrays hinzufügen
    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

Für diejenigen, die nach unendlich und horizontal scrollenden Auflistungsansichten suchen, an deren Datenquellen am Ende angefügt werden, fügen Sie sie in scrollViewDidScroll an Ihre Datenquelle an und rufen Sie reloadData() in Ihrer Auflistungsansicht auf. Der Scroll-Offset wird beibehalten.

Beispielcode unten. Ich verwende meine Sammlungsansicht für eine paginierte Datumsauswahl, bei der ich mehr Seiten (ganze Monate) lade, wenn sich der Benutzer am rechten Ende (vorletzt) ​​befindet:

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: - Dies scheint nicht zu funktionieren, wenn Sie am Anfang der Datenquelle einfügen.

0
Matthew Quiros

Getesteter Code

Ich habe dies durch einfaches Wiederholen von Zellen für x Anzahl von Malen erreicht. Wie folgt,

Erklären Sie, wie viele Loops Sie möchten

let x = 50

numberOfItems implementieren

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

Fügen Sie diese Dienstprogrammfunktion hinzu, um arrayIndex in einer indexPath-Zeile zu berechnen

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

cellForItem implementieren

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
}

Fügen Sie eine Utility-Funktion hinzu, um bei gegebenem Index zur Mitte von collectionView zu scrollen

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

Die hier gegebenen Antworten sind gut, um die Funktion zu implementieren. Meiner Meinung nach enthalten sie jedoch einige Aktualisierungen auf niedriger Ebene (Einstellen des Inhaltsoffsets, Manipulieren der Datenquelle ...), die vermieden werden können. Wenn Sie immer noch nicht zufrieden sind und nach einem anderen Ansatz suchen, habe ich Folgendes getan.

Die Hauptidee ist, die Anzahl der Zellen zu aktualisieren, wenn Sie die Zelle vor der letzten erreichen. Jedes Mal, wenn Sie die Anzahl der Elemente um 1 erhöhen, entsteht die Illusion eines unendlichen Bildlaufs. Zu diesem Zweck können wir die Funktion scrollViewDidEndDecelerating(_ scrollView: UIScrollView) verwenden, um zu ermitteln, wann der Benutzer den Bildlauf abgeschlossen hat, und dann die Anzahl der Elemente in der Sammlungsansicht zu aktualisieren. Hier ist ein Code-Snippet, um dies zu erreichen:

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()
    }
}

Vorteile

  • Einfache Implementierung
  • Keine Verwendung von Int.max (was meiner Meinung nach keine gute Idee ist)
  • Keine Verwendung einer beliebigen Zahl (wie 50 oder etwas anderes)
  • Keine Änderung oder Manipulation der Daten
  • Keine manuelle Aktualisierung des Inhaltsoffsets oder anderer Scroll-Ansichtsattribute

Nachteile

  • Paging sollte aktiviert sein (obwohl die Logik aktualisiert werden kann, um kein Paging zu unterstützen)
  • Für einige Attribute muss eine Referenz gepflegt werden (aktueller Index, aktuelle maximale Anzahl)
  • Die Sammlungsansicht muss an jedem Bildlaufende neu geladen werden (keine große Sache, wenn die sichtbaren Zellen minimal sind). Dies kann sich drastisch auf Sie auswirken, wenn Sie etwas asynchron laden, ohne es zu zwischenspeichern (was eine schlechte Praxis ist und Daten außerhalb der Zellen zwischengespeichert werden sollten).
  • Funktioniert nicht, wenn Sie einen unendlichen Bildlauf in beide Richtungen wünschen
0
Santina

Das bedeutet auch, dass Ihre Daten statisch sind und dass alle Ihre UICollectionView-Zellen dieselbe Größe haben sollten. Ich fand diese vielversprechende Lösung .

Sie sollten vielleicht das Beispielprojekt unter github herunterladen und das Projekt selbst ausführen. Der Code im ViewController, der UICollectionView erstellt, ist recht einfach und einfach zu verwenden.

Sie folgen grundsätzlich diesen Schritten:

  • Erstellen Sie eine InfiniteCollectionView im Storyboard
  • Setze infiniteDataSource und infiniteDelegate
  • Implementieren Sie die erforderlichen Funktionen zum Erstellen von Zellen mit unendlich vielen Blättern
0
SebastianR