web-dev-qa-db-fra.com

Comment implémenter UICollectionView avec défilement horizontal infini?

Je veux implémenter UICollectionView qui défile horizontalement et infiniment?

22
Abdelrahman

Si vos données sont statiques et que vous souhaitez un comportement circulaire, vous pouvez procéder comme suit:

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
}

En gros, vous dites à votre vue Collection que vous avez un grand nombre de cellules (Int.max ne sera pas infini, mais pourrait faire l'affaire) et vous accédez à votre source de données à l'aide de l'opérateur%. Dans mon exemple, nous allons nous retrouver avec "item 0", "item 1", "item 2", "item 0", "item 1", "item 2" ....

J'espère que ça aide :)

32
cicerocamargo

Apparemment, la solution la plus proche de la bonne solution avait été proposée par le Manikanta Adimulam. La solution la plus propre consisterait à ajouter le dernier élément au début de la liste de données et le premier à la dernière position de la liste de données (ex: [4] [0] [1] [2] [3] [4] [ 0]), nous passons donc au premier élément du tableau lorsque nous déclenchons le dernier élément de la liste et inversement. Cela fonctionnera pour les vues de collection avec un élément visible:

  1. Sous-classe UICollectionView.
  2. Remplacez UICollectionViewDelegate et substituez les méthodes suivantes:

    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. Remplacez la méthode layoutSubviews () dans votre UICollectionView afin de toujours effectuer un décalage correct pour le premier élément:

    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. Ignorez la méthode init et calculez les dimensions de votre cellule:

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

Fonctionne comme un charme! Si vous souhaitez obtenir cet effet avec une vue de collection ayant plusieurs éléments visibles, utilisez la solution publiée ici .

6
Bio-Matic

J'ai implémenté le défilement infini dans UICollectionView. Fait le code disponible dans github. Vous pouvez essayer. C'est dans Swift 3.0.

Défilé interminable

Vous pouvez l'ajouter en utilisant pod. L'utilisation est assez simple. Intialise simplement la InfiniteScrollingBehaviour comme ci-dessous.

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

et implémentez la méthode déléguée requise pour renvoyer une UICollectionViewCell configurée. Un exemple d'implémentation ressemblera à:

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
    }

Il ajoutera les éléments de limite de début et de fin appropriés dans votre jeu de données d'origine et ajustera la variable contentOffset de collectionView.

Dans les méthodes de rappel, cela vous donnera l'index d'un élément dans le jeu de données d'origine. 

4
Vishal Singh
  1. Pour appliquer cette fonctionnalité de boucle infinie Vous devez disposer d'une disposition collectionView appropriée

  2. Vous devez ajouter le premier élément du tableau au dernier et le dernier élément du tableau au premier.
    ex: - array = [1,2,3,4]
    tableau de présentation = [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

Pour ceux qui recherchent des vues de collection avec défilement infini et horizontal dont les sources de données sont ajoutées à la fin, ajoutez-la à votre source de données dans scrollViewDidScroll et appelez reloadData() sur votre vue de collection. Il maintiendra le décalage de défilement.

Exemple de code ci-dessous. J'utilise ma vue Collection pour un sélecteur de date paginé, dans lequel je charge plus de pages (de mois entiers) lorsque l'utilisateur se trouve à l'extrémité droite (de la seconde à la dernière):

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: - Cela ne semble pas fonctionner lorsque vous insérez au début de la source de données.

0
Matthew Quiros

Code testé

J'ai réalisé cela simplement en répétant cellule x fois. Comme suit,

Déclarez combien de boucles aimeriez-vous avoir

let x = 50

Implémenter numberOfItems

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

Ajoutez cette fonction utilitaire pour calculer arrayIndex à partir d'une ligne indexPath

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

Implémenter 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
}

Ajouter une fonction utilitaire pour faire défiler jusqu'au milieu de collectionView à un index donné

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

Cela implique également que vos données sont statiques et que toutes vos cellules UICollectionView doivent avoir la même taille. J'ai trouvé cette solution prometteuse .

Vous devriez peut-être simplement télécharger l'exemple de projet sur github et l'exécuter vous-même. Le code dans ViewController qui crée la UICollectionView est assez simple et facile à utiliser.

Vous suivez essentiellement ces étapes:

  • Créer une InfiniteCollectionView dans le Storyboard
  • Définir infiniteDataSource et infiniteDelegate
  • Implémentez les fonctions nécessaires pour créer vos cellules à défilement infini
0
SebastianR