Drag & Drop sur iOS 11

le 10/01/2018 par Paul Bancarel
Tags: Software Engineering

Avec iOS 11 Apple intègre le drag & drop au coeur de son système. Permettant d'une part de faciliter son implémentation au sein des applications iOS mais également de le rendre effectif entre applications. En fournissant une API simple et rapide à intégrer Apple espère ainsi qu’un maximum d'applications adoptent cette nouvelle fonctionnalité et que le partage de données au sein d'iOS deviennent encore plus naturel.

Illustration 1: Drag & drop d’une photo dans l’application Mail

Même si cela peut paraître évident rappelons que l’action du “drag” correspond au déplacement votre vue et que le “drop” au moment où vous la relâchez.

Comme l’a expliqué Bruce Nilo lors de la WWDC de 2017, les équipes d’UIKit avaient pour but de rendre les APIs drag & drop réactives et sécurisées. C’est pourquoi ces APIs assurent que les données soient délivrées de manière asynchrone par le système et qu’elles soient uniquement visible par l’application qui reçoit le drop. Une autre différence notable est le fait que le drag & drop inter-application n’est effectif que sur iPad.

1. Fonctionnement

a. Cycle de vie

Comme le décrit le schéma 1. Le cycle de vie du drag & drop est le suivant: L’utilisateur touche un élément et la vue se surélève légèrement ce qui conclut la phase de “Lift”. Puis l’utilisateur déplace son élément constituant la phase de ”drag”. Enfin il relâche son élément. Si l’élément est au dessus d’une vue qui n’accepte pas le “drop”. L’opération se termine et on annule l’animation. Si l’élément est au dessus d’une vue qui accepte le “drop” à ce moment là on exécute l’action associée. L’animation de “drop” démarre et les données sont transférées en parallèle. Ces trois actions constituent ainsi la phase de “drop”.

Durant cette phase de drop deux scénarios sont alors possibles: 1) Les données ont fini d’être transférées avant la fin de l’animation. 2) Les données n’ont pas fini d’être transférées lorsque l’animation se termine. (voir les placeholders 2.a)

Schéma 1: Drag & drop lifecycle

b. Fonctionnement

Un drag est associé à une vue par l’intermédiaire d’un delegate. Vous pouvez ainsi ajouter la possibilité de faire un drag à votre vue de la manière suivante:

let delegate:UIDragInteractionDelegate = ... let dragInteraction = UIDragInteraction(delegate: delegate) view.addInteraction(dragInteraction)

Quand la phase de “Lift” démarre le delegate fournit un ensemble de DragItem si cet ensemble est vide le drag échoue. Le DragItem est un objet représentant votre modèle de donnée.

Voir le lien github

De la même manière il faudra faire supporter le “drop” à votre vue destination via un delegate:

let dropDelegate: UIDropInteractionDelegate = ... let dropInteraction = UIDropInteraction(delegate: dropDelegate) view.addInteraction(dropInteraction)

Tips: Il sera peut-être utile d’autoriser l’utilisateur d'interagir avec votre vue view.isUserInteractionEnabled = true

Lors de cette phase de drop vous pouvez spécifier quel type de “drop” votre vue supporte. (Par exemple ci-dessous on accepte seulement une image)

Voir le lien github

Il vous faudra également renseigner comment seront transférées les données. Pour cela Apple vous fournit quatre possibilités:

Schéma 2: Différentes opérations

  • **.cancel**: Les données ne sont pas transférées
  • **.copy**: Les données sont copiées de la source vers la destination
  • **.move**: Les données sont déplacées de la source vers la destination
  • **.forbidden**: Ces données ne peuvent être transférées vers la destination.

A chaque fois que l’utilisateur survolera votre vue destination la méthode suivante du delegate sera appelée :

Voir le lien github

Il ne vous reste plus qu’à exécuter votre action de drop:

Voir le lien github

Tips: La session possède une propriété progress permettant d’avoir un feedback sur le transfert en cours.

c. Cas Particulier des UITableView et des UICollectionView

La partie précédente ne détaille que le cas d’une simple vue. Apple facilite encore plus l’intégration du “drag & drop” en rendant les UITableView et UICollectionView compatibles.

Le principe reste somme toute identique. Ainsi elles possèdent une propriété dragDelegate et dropDelegate qui demandent respectivement un (UITableView/UICollectionView)DragDelegate et un (UITableView/UICollectionView)DropDelegate nous permettant de retrouver les méthodes équivalentes décrites dans le paragraphe précédent:

Par exemple func dragInteraction(_ interaction: UIDragInteraction, itemsForBeginning session: UIDragSession) -> [UIDragItem]{ /*...*/}

trouve son équivalent pour une UITableView:

func tableView(_ tableView: UITableView, itemsForBeginning session: UIDragSession, at indexPath: IndexPath) -> [UIDragItem] { /*...*/}

J’en profite pour introduire une nouvelle méthode qui permet à l’utilisateur d’ajouter plusieurs éléments à son drag:

Voir le lien github

La méthode **itemsForAddingTo** nous permet donc de rajouter un ensemble de DragItem à celui déjà défini dans le **itemsForBeginning**. Le rendu est le suivant:

Illustration 2: Drag multiple

Alors que les vues “classiques” devait retourner une UIDropProposal(operation: operation) contenant une opération. Dans une table/collection view on retourne une (UITableView/UICollectionView)DropProposal contenant une opération (rappel: .cancel, .copy, .move ou .forbidden) ainsi qu’un intent permettant de décrire comment la table/collection view doit se comporter lorsqu’un élément la survole durant le drag:

  • **.unspecified** : Aucune réaction des cellules
  • **.insertAtDestinationIndexPath** : Les cellules s’écartent
  • **.insertIntoDestinationIndexPath** : La cellule se surligne
  • **.automatic** : Mix automatique entre insertAtDestinationIndexPath et insertIntoDestinationIndexPath

Schéma 3: Gauche: Description des “Intent” | Droite: Exemple du insertAtDestinationIndexPath

Voir le lien github

2. Spécificités du Drag & Drop

a. Drag & Drop sur des données volumineuses - Placeholders

Comme nous l’avons évoqué en partie 1, les données sont transférées de manière asynchrone car les données transférées peuvent être volumineuses. Dans ce cas on ne peut garantir que leur transfert soit terminé avant la fin de l’animation du drop et il faudra donc insérer une cellule temporairement dans la tableview/collection view afin de garantir une meilleure expérience à l'utilisateur. C’est le but des placeholders !

Illustration 3: Placeholders

Voir le lien github

Tips: Dans ce cas ci Apple conseille d’éviter d’utiliser la méthode reloadData et de préférez performBatchUpdates(_:completion:) pour mettre à jour ses table/collection view.

b. Modifier la preview du Drag & Drop

Par défaut toute la cellule de la table/collection view subit le “Lift”.

Illustration 4: Preview par défaut d’une cellule

Pour modifier la preview d’une tableview/collection view, Apple donne accès via son drop delegate à la méthode suivante:

Voir le lien github

Vous pouvez ainsi à l’aide d’une courbe de bézier cibler uniquement l’élément de votre cellule qui doit subir le drag.

Illustration 5: Preview personnalisée pour une cellule

Dans le cas d’une simple vue on passera par la méthode **previewForLifting**:

Voir le lien github

Tips: Ces méthodes peuvent très bien être appelées plusieurs fois selon le comportement de l’utilisateur.

  • sessionDidEnter when enter hover the drop view
  • sessionDidUpdate when hover the drop view
  • sessionDidExit when exit hover the drop view

c. Drag & Drop sur des fichiers

Avec iOS 11 Apple a également introduit l’application Files permettant de gérer vos fichiers directement depuis votre iPhone ou votre iPad. Les APIs drag & drop d’Apple permettent donc de manipuler directement des fichiers. L’utilisateur peut par exemple faire glisser son fichier dans son éditeur préféré afin de le modifier.

Voir le lien github

Conclusion

Ce petit tour d'horizon des APIs drag & drop d'iOS nous permet de nous rendre compte qu'elles sont simples à implémenter tout en restant suffisamment souples afin de s'adapter aux différents scénarios de chaque application. On peut donc raisonnablement penser que ces nouvelles gestures deviennent vite un "must-have" pour vos prochaines versions d'applications sur iOS.

Lien poc github:

https://github.com/paul1893/DragAndDropSampleiOS

Sources :

https://developer.apple.com/documentation/uikit/drag_and_drop/adopting_drag_and_drop_in_a_table_view https://developer.apple.com/documentation/uikit/views_and_controls/collection_views/supporting_drag_and_drop_in_collection_views https://developer.apple.com/documentation/uikit/drag_and_drop/adopting_drag_and_drop_in_a_custom_view

https://developer.apple.com/videos/play/wwdc2017/203/com/documentation/uikit/ https://developer.apple.com/videos/play/wwdc2017/223/ https://developer.apple.com/videos/play/wwdc2017/227/ https://developer.apple.com/videos/play/wwdc2017/213/

.caption { color: #767676; font-size: 12px; font-style: italic; margin-bottom:25px; } code { font-size: 85%; }