web-dev-qa-db-fra.com

UICollectionView glisser-déposer efficace

J'essaie actuellement d'implémenter le comportement de réorganisation UITableView en utilisant UICollectionView.

Appelons un ItableView TV et un ICollectionView CV (pour clarifier l'explication suivante)

J'essaie essentiellement de reproduire le glisser-déposer du téléviseur, mais je n'utilise pas le mode d'édition, la cellule est prête à être déplacée dès que le geste de pression longue est déclenché. Cela fonctionne parfaitement, j'utilise la méthode de déplacement du CV, tout va bien.

Je mets à jour la propriété contentOffset du CV pour gérer le défilement lorsque l'utilisateur fait glisser une cellule. Lorsqu'un utilisateur se rend dans un rect en particulier en haut et en bas, je mets à jour le contentOffset et le défilement du CV. Le problème est que lorsque l'utilisateur arrête de bouger son doigt, le geste n'envoie aucune mise à jour qui arrête le défilement et recommence dès que l'utilisateur déplace son doigt.

Ce comportement n'est certainement pas naturel, je préférerais continuer à faire défiler jusqu'à ce que l'utilisateur relâche le CV comme c'est le cas sur le téléviseur. L'expérience TV glisser-déposer est géniale et je veux vraiment reproduire le même sentiment. Est-ce que quelqu'un sait comment il gère le défilement de la télévision pendant la réorganisation?

  • J'ai essayé d'utiliser une minuterie pour déclencher une action de défilement à plusieurs reprises tant que la position du geste est au bon endroit, le défilement était horrible et peu productif (très lent et nerveux).
  • J'ai également essayé d'utiliser GCD pour écouter la position du geste dans un autre fil, mais le résultat est encore pire.

J'ai manqué d'idée à ce sujet, donc si quelqu'un a la réponse, je l'épouserais!

Voici l'implémentation de la méthode longPress:

- (void)handleLongPress:(UILongPressGestureRecognizer *)sender
{
    ReorganizableCVCLayout *layout = (ReorganizableCVCLayout *)self.collectionView.collectionViewLayout;
    CGPoint gesturePosition = [sender locationInView:self.collectionView];
    NSIndexPath *selectedIndexPath = [self.collectionView indexPathForItemAtPoint:gesturePosition];

    if (sender.state == UIGestureRecognizerStateBegan)
    {
        layout.selectedItem = selectedIndexPath;
        layout.gesturePoint = gesturePosition; // Setting gesturePoint invalidate layout
    }
    else if (sender.state == UIGestureRecognizerStateChanged)
    {
        layout.gesturePoint = gesturePosition; // Setting gesturePoint invalidate layout
        [self swapCellAtPoint:gesturePosition];
        [self manageScrollWithReferencePoint:gesturePosition];
    }
    else
    {
        [self.collectionView performBatchUpdates:^
        {
            layout.selectedItem = nil;
            layout.gesturePoint = CGPointZero; // Setting gesturePoint invalidate layout
        } completion:^(BOOL completion){[self.collectionView reloadData];}];
    }
}

Pour faire défiler le CV, j'utilise cette méthode:

- (void)manageScrollWithReferencePoint:(CGPoint)gesturePoint
{
    ReorganizableCVCLayout *layout = (ReorganizableCVCLayout *)self.collectionView.collectionViewLayout;
    CGFloat topScrollLimit = self.collectionView.contentOffset.y+layout.itemSize.height/2+SCROLL_BORDER;
    CGFloat bottomScrollLimit = self.collectionView.contentOffset.y+self.collectionView.frame.size.height-layout.itemSize.height/2-SCROLL_BORDER;
    CGPoint contentOffset = self.collectionView.contentOffset;

    if (gesturePoint.y < topScrollLimit && gesturePoint.y - layout.itemSize.height/2 - SCROLL_BORDER > 0)
        contentOffset.y -= SCROLL_STEP;
    else if (gesturePoint.y > bottomScrollLimit &&
             gesturePoint.y + layout.itemSize.height/2 + SCROLL_BORDER < self.collectionView.contentSize.height)
        contentOffset.y += SCROLL_STEP;

    [self.collectionView setContentOffset:contentOffset];
}
59
foOg

Cela pourrait aider

https://github.com/lxcid/LXReorderableCollectionViewFlowLayout

Ceci étend le UICollectionView pour permettre à chacun des UICollectionViewCells d'être réarrangé manuellement par l'utilisateur avec une longue touche (alias toucher et maintenir). L'utilisateur peut faire glisser la cellule vers n'importe quelle autre position de la collection et les autres cellules se réorganiseront automatiquement. Merci d'aller à lxcid pour cela.

70
dantes85

Ici est une alternative:

Les différences entre DraggableCollectionView et LXReorderableCollectionViewFlowLayout sont:

  • La source de données n'est modifiée qu'une seule fois. Cela signifie que pendant que l'utilisateur fait glisser un élément, les cellules sont repositionnées sans modifier la source de données.
  • Il est écrit de manière à pouvoir être utilisé avec des mises en page personnalisées.
  • Il utilise un CADisplayLink pour un défilement et une animation fluides.
  • Les animations sont annulées moins fréquemment lors du glissement. Cela semble plus "naturel".
  • Le protocole étend UICollectionViewDataSource avec des méthodes similaires à UITableViewDataSource.

C'est un travail en cours. Plusieurs sections sont désormais prises en charge.

Pour l'utiliser avec une mise en page personnalisée, voir DraggableCollectionViewFlowLayout. La plupart de la logique existe dans LSCollectionViewLayoutHelper. Il y a aussi un exemple dans CircleLayoutDemo montrant comment faire fonctionner l'exemple CircleLayout d'Apple à partir de la WWDC 2012.

47
Luke

Depuis iOS 9, UICollectionView prend désormais en charge la réorganisation.

Pour UICollectionViewControllers, remplacez simplement collectionView(collectionView: UICollectionView, moveItemAtIndexPath sourceIndexPath: NSIndexPath, toIndexPath destinationIndexPath: NSIndexPath)

Pour UICollectionViews, vous devrez gérer les gestes vous-même en plus d'implémenter la méthode UICollectionViewDataSource ci-dessus.

Voici le code de la source:

private var longPressGesture: UILongPressGestureRecognizer!

override func viewDidLoad() {
    super.viewDidLoad()

    longPressGesture = UILongPressGestureRecognizer(target: self, action: "handleLongGesture:")
    self.collectionView.addGestureRecognizer(longPressGesture)
}

func handleLongGesture(gesture: UILongPressGestureRecognizer) {

    switch(gesture.state) {

    case UIGestureRecognizerState.Began:
        guard let selectedIndexPath = self.collectionView.indexPathForItemAtPoint(gesture.locationInView(self.collectionView)) else {
            break
        }
        collectionView.beginInteractiveMovementForItemAtIndexPath(selectedIndexPath)
    case UIGestureRecognizerState.Changed:
        collectionView.updateInteractiveMovementTargetPosition(gesture.locationInView(gesture.view!))
    case UIGestureRecognizerState.Ended:
        collectionView.endInteractiveMovement()
    default:
        collectionView.cancelInteractiveMovement()
    }
}

Sources: https://developer.Apple.com/library/ios/documentation/UIKit/Reference/UICollectionView_class/#//Apple_ref/doc/uid/TP40012177-CH1-SW67

http://nshint.io/blog/2015/07/16/uicollectionviews-now-have-easy-reordering/

30
chrisamanse

Si vous voulez expérimenter le déploiement du vôtre, je viens d'écrire un tutoriel basé sur Swift que vous pouvez consulter. J'ai essayé de construire le plus basique des cas afin d'être plus facile à suivre ceci .

3
Mike M

Ici est une autre approche:

La principale différence est que cette solution ne nécessite pas de cellule "fantôme" ou "factice" pour fournir la fonctionnalité de glisser-déposer. Il utilise simplement la cellule elle-même. Les animations sont conformes à UITableView. Il fonctionne en ajustant la source de données privée de la présentation de la vue de la collection tout en vous déplaçant. Une fois que vous lâchez prise, il indiquera à votre contrôleur que vous pouvez valider la modification dans votre propre source de données.

Je pense qu'il est un peu plus simple de travailler avec la plupart des cas d'utilisation. Encore un travail en cours, mais encore une autre façon d'y parvenir. La plupart devraient trouver cela assez facile à intégrer dans leur propre UICollectionViewLayouts personnalisé.

2
Henry T Kirk