Glissez et déposez dans vos applications iOS



Le mécanisme Drag & Drop fonctionnant sur iOS 11 et iOS 12 est un moyen de copier ou de déplacer graphiquement de manière asynchrone des données à la fois dans une seule application et entre différentes applications. Bien que cette technologie ait environ 30 ans, elle est littéralement devenue une technologie «révolutionnaire» sur iOS raison du fait que lorsque vous faites glisser quelque chose sur iOS , le multitouch vous permet d'interagir librement avec le reste du système et de collecter des données à réinitialiser à partir de différentes applications.

iOS permet de capturer plusieurs éléments à la fois. De plus, ils n'ont pas besoin d'être dans une accessibilité pratique pour la sélection: vous pouvez prendre le premier objet, puis aller dans une autre application et saisir autre chose - tous les objets seront collectés en «pile» sous votre doigt. Appelez ensuite le dock universel à l'écran, ouvrez-y n'importe quelle application et capturez le troisième objet, puis accédez à l'écran avec les applications en cours d'exécution et, sans libérer les objets, videz-les dans l'un des programmes ouverts. Une telle liberté d'action est possible sur l' iPad , sur l' iPhone , la couverture Drag & Drop dans iOS limitée au cadre d'une seule application.

Les applications les plus populaires ( Safary , Chrome , IbisPaint X , Mail , Photos , Files , etc.) ont déjà un mécanisme Drag & Drop . En plus de cela, Apple fourni aux développeurs une API très simple et intuitive pour intégrer le mécanisme Drag & Drop dans votre application. Le mécanisme Drag & Drop , tout comme les gestes, fonctionne sur UIView et utilise le concept d' interactions , un peu comme les gestes, de sorte que vous pouvez considérer le mécanisme Drag & Drop comme un geste vraiment puissant.

Il, ainsi que les gestes, est très facile à intégrer dans votre application. Surtout si votre application utilise la table UITableView ou la collection UICollectionView , car pour eux API Drag & Drop améliorée et élevée à un niveau d'abstraction supérieur dans le sens où la Collection View elle-même vous aide avec l' indexPath de l'élément de collection que vous souhaitez faire glisser et déposer Drag . Elle sait où se trouve votre doigt et l'interprète comme l' indexPath de l'élément de collection que vous « Drag glisser» Drag en ce moment, ou comme l' indexPath de l'élément de collection où vous «déposez» Drop quelque chose. La Collection View vous fournit donc indexPath , mais sinon c'est absolument la même API Drag & Drop que pour une UIView standard .

Le processus Drag & Drop sur iOS comporte 4 phases différentes:

Lift


Lift (levage) - c'est lorsque l'utilisateur effectue un geste de pression longue , indiquant l'élément qui sera «glisser-déposer». À ce moment, un soi-disant « lift preview » très léger de l'élément indiqué est formé, puis l'utilisateur commence à faire glisser ses doigts.



Glisser (glisser-déposer)


Glisser (glisser-déposer) - c'est lorsque l'utilisateur déplace l'objet sur la surface de l'écran. Au cours de cette phase, l' lift preview de cet objet peut être modifié (un signe «+» plus vert ou un autre signe apparaît) ...



... une certaine interaction avec le système est également autorisée: vous pouvez cliquer sur un autre objet et l'ajouter à la session "glisser-déposer" en cours:



Drop


La chute se produit lorsque l'utilisateur soulève un doigt. À ce moment, deux choses peuvent se produire: soit l'objet Drag sera détruit, soit l'objet Drop sera «déposé» à la destination.



Transfert de données


Si le processus de glisser -déplacer n'a pas été annulé et que la réinitialisation de « Drop » a eu lieu, le transfert de données (transfert de données) se produit, auquel cas le «point de dépôt» demande des données à la «source», et le transfert de données asynchrone se produit.

Dans ce didacticiel, nous allons vous montrer comment intégrer facilement le mécanisme Drag & Drop dans votre application iOS à l'aide de l'application de démonstration Image Gallery, tirée du cours de devoirs CS193P de Stanford .
Nous doterons la Collection View possibilité de nous remplir d'images EXTÉRIEURES, ainsi que de réorganiser les éléments INTÉRIEURS à l'aide du mécanisme Drag & Drop . En outre, ce mécanisme sera utilisé pour vider les éléments inutiles de la Collection View dans une «poubelle», qui est une vue UIV normale et représentée par un bouton sur le panneau de navigation. Nous pouvons également partager des images collectées dans notre galerie avec d'autres applications en utilisant le mécanisme Drag & Drop , par exemple, Notes ou Notes ou Mail ou une photothèque ( Photo ).

Mais avant de me concentrer sur l'implémentation du mécanisme Drag & Drop dans l'application de démonstration «Image Gallery», je vais passer en revue ses principaux composants très brièvement.

Fonctionnalités de l'application de démonstration "Image Gallery"


L'interface utilisateur ( UI ) de l'application Image Gallery est très simple. Il s'agit d'un «extrait d'écran» du Image Gallery Collection View Controller affichage de la Image Gallery Collection View Controller inséré dans le Navigation Controller :



La partie centrale de l'application est certainement Image Gallery Collection View Controller , qui est prise en charge par la classe ImageGalleryCollectionViewController avec le modèle de galerie d'images en tant que variable var imageGallery = ImageGallery () :



Le modèle est représenté par une structure struct ImageGallery contenant un tableau d'images images , dans laquelle chaque image est décrite par une structure struct ImageModel contenant l' URL localisation URL image (nous n'allons pas stocker l'image elle-même) et son rapport d'aspect:



Notre ImageGalleryCollectionViewController implémente le protocole DataSource :



La cellule personnalisée de la collection de cellules contient une image imageView: UIImageView! et indicateur d'activité de spinner: UIActivityIndicatorView! et est pris en charge par la subclass personnalisée ImageCollectionViewCell de la classe UICollectionViewCell :



Public API de la classe ImageCollectionViewCell est l' URL de l' image imageURL . Dès que nous l'installons, notre UI mise à jour, c'est-à-dire que les données de l'image sont sélectionnées de manière asynchrone sur cette imageURL et affichées dans la cellule. Pendant que les données sont extraites du réseau, l'indicateur d'activité de spinner fonctionne, indiquant que nous sommes en train de récupérer des données.

J'utilise la file d'attente globale (qos: .userInitiated) avec l' argument de qualité de service qos pour obtenir des données à une URL donnée, qui est définie sur .userInitiated , car je sélectionne les données à la demande de l'utilisateur:



Chaque fois que vous utilisez vos propres variables à l'intérieur d'une fermeture, dans notre cas c'est imageView et imageURL , le compilateur vous oblige à vous mettre devant elles . de sorte que vous vous demandez: "Y a-t-il un" lien cyclique mémoire "?" Nous n'avons pas ici de « memory cycle » explicite, car le soi lui-même n'a pas de pointeur sur cette fermeture.

Cependant, dans le cas du multithreading, vous devez garder à l'esprit que les cellules de la Collection View sont réutilisables grâce à la méthode dequeueReusableCell . Chaque fois qu'une cellule (nouvelle ou réutilisée) apparaît à l'écran, l'image est téléchargée du réseau de manière asynchrone (à ce moment le « spinner » de l'indicateur d'activité de spinner tourne).

Dès que le téléchargement est terminé et que l'image est reçue, l' UI cette cellule de collecte est mise à jour. Mais nous n'attendons pas que l'image se charge, nous continuons à faire défiler la collection et la cellule de collection que nous avons marquée quitte l'écran sans mettre à jour notre UI . Cependant, une nouvelle image devrait apparaître ci-dessous et la même cellule qui a quitté l'écran sera réutilisée, mais pour une autre image, qui peut rapidement charger et mettre à jour l' UI . À ce moment, le chargement de l'image précédemment démarré dans cette cellule reviendra et l'écran sera mis à jour, ce qui conduira à un résultat incorrect. C'est parce que nous exécutons différentes choses qui fonctionnent avec le réseau dans différents threads. Ils reviennent à des moments différents.

Comment pouvons-nous régler la situation?
Dans le cadre du mécanisme GCD que nous utilisons, nous ne pouvons pas annuler le téléchargement de l'image de la cellule qui a quitté l'écran, mais nous pouvons, lorsque nos données imageData arrivent du réseau, vérifier l' URL URL qui a provoqué le chargement de ces données et la comparer avec celle que l'utilisateur souhaite avoir dans cette cellule pour le moment, c'est-à-dire imageURL . S'ils ne correspondent pas, nous ne mettrons pas à jour la cellule d' UI et n'attendrons pas les données d'image dont nous avons besoin:



Cette ligne de code apparemment absurde url == self.imageURL fait tout fonctionner correctement dans un environnement multi-thread qui nécessite une imagination non standard. Le fait est que certaines choses dans la programmation multi-thread se produisent dans un ordre différent de celui écrit.

S'il n'a pas été possible de sélectionner les données d'image, une image est générée avec un message d'erreur sous la forme de la chaîne «Erreur» et un emoji avec «froncer les sourcils». Juste l'espace vide dans notre Collection View peut dérouter un peu l'utilisateur:



Nous ne voudrions pas que l'image avec le message d'erreur répète aspectRatio de cette image d'erreur, car dans ce cas, le texte avec les emoji sera étiré ou compressé. Nous aimerions qu'il soit neutre - carré, c'est-à-dire qu'il aurait un rapport d'aspect proche de 1,0.



Nous devons informer notre Controller de ce souhait afin qu'il corrige le rapport d'aspect aspectRatio pour l' indexPath correspondant dans son modèle imageGallery . C'est une tâche intéressante, il existe de nombreuses façons de la résoudre, et nous choisirons la plus simple d'entre elles - en utilisant la fermeture facultative var closAspectRatio: (() -> Void)? . Il peut être égal à zéro et n'a pas besoin d'être installé si cela n'est pas nécessaire:



Lors de l'appel de la fermeture changeAspectRatio? () En cas de récupération de données erronées, j'utilise la chaîne facultative . Désormais, toute personne intéressée par certains types de paramètres lors de la réception d'une image erronée peut définir cette fermeture sur quelque chose de spécifique. Et c'est exactement ce que nous faisons dans notre Controller dans la méthode cellForItemAt :



Les détails peuvent être trouvés ici .

Pour afficher les images avec le bon aspectRatio , la méthode sizeForItemAt du délégué UICollectionViewDelegateFlowLayout est utilisée :



En plus de la Collection View images Collection View , sur notre UI nous avons placé un Bar Button sur le panneau de navigation avec une image GarbageView personnalisée contenant une «poubelle» comme sous- vue :



Dans cette figure, les couleurs d'arrière-plan pour GarbageView lui-même et le bouton UIButton avec l'image de la «poubelle» (en fait, il y a un arrière-plan transparent) sont spécialement modifiés pour que vous voyiez que l'utilisateur qui «décharge» les images de la galerie dans la «poubelle» beaucoup plus de marge de manœuvre lors du "drop" Drop que l'icône de la poubelle.
La classe GarbageView a deux initialiseurs et tous deux utilisent la méthode setup () :



Dans la méthode setup () , j'ajoute également myButton en tant que sous- vue avec l'image de la «poubelle» prise à partir du Bar Button standard du Bar Button :



J'ai défini un arrière-plan transparent pour GarbageView :



La taille de la poubelle et sa position seront déterminées dans la méthode layoutSubviews () de la classe UIView , en fonction des limites de l' UIView donnée:



Il s'agit de la version initiale de l'application de démonstration "Image Gallery", elle se trouve sur Github dans le dossier ImageGallery_beginning . Si vous exécutez cette version de l'application «Galerie d'images», vous verrez le résultat de l'application travaillant sur les données de test, que nous supprimerons ensuite et remplirons la «Galerie d'images» exclusivement À L'EXTÉRIEUR:



Le plan de mise en œuvre du mécanisme Drag & Drop dans notre application est le suivant:

  1. tout d'abord, nous doterons notre Collection View possibilité de «faire glisser» des images UIImage depuis et vers l'extérieur,
  2. Ensuite, nous apprendrons à notre collection d'images Collection View à accepter le glisser- Drag «externe» ou local de UIImage ,
  3. nous apprendrons également à notre GarbageView avec le bouton poubelle pour accepter les images UIImage glissées de la Collection View locale et les supprimer de la Collection View


Si vous allez à la fin de ce didacticiel et effectuez toutes les modifications de code nécessaires, vous recevrez la version finale de l'application de démonstration «Image Gallery», dans laquelle le mécanisme Drag & Drop a été implémenté. Il se trouve sur Github dans le dossier ImageGallery_finished .

Les performances du mécanisme Drag & Drop dans votre Collection View fournies par deux nouveaux délégués.
Les méthodes du premier délégué, dragDelegate , sont configurées pour initialiser et personnaliser les drag and drop Drags .
Les méthodes du deuxième délégué, dropDelegate , complètent le glisser-déposer de Drags et, fondamentalement, fournissent le Data transfer données et les paramètres d'animation personnalisés lorsque Drop réinitialisé, ainsi que d'autres choses similaires.

Il est important de noter que ces deux protocoles sont complètement indépendants. Vous pouvez utiliser l'un ou l'autre protocole si vous avez seulement besoin de «glisser» Drag ou seulement «déposer» Drop , mais vous pouvez utiliser les deux protocoles à la fois et simultanément Drag -déposer Drag et «déposer» Drop , ce qui ouvre des fonctionnalités supplémentaires Mécanisme Drag & Drop pour réorganiser les éléments de votre Collection View .

Faites glisser et Drag éléments de Drag Collection View


L'implémentation du protocole Drag est très simple, et la première chose que vous devez toujours faire est de vous définir vous- même en tant que délégué dragDelegate :



Et, bien sûr, tout en haut de la classe ImageGalleryCollectionViewController, vous devez dire «Oui», nous implémentons le protocole UICollectionViewDragDelegate :



Dès que nous faisons cela, le compilateur commence à "se plaindre", nous cliquons sur le cercle rouge et on nous demande: "Voulez-vous ajouter les méthodes requises du protocole UICollectionViewDragDelegate ?"
Je réponds: "Bien sûr que je veux!" et cliquez sur le bouton Fix :



La seule méthode requise du protocole UICollectionViewDragDelegate est la méthode itemsForBeginning , qui indiquera au système Drag que nous « glissons - déposons ». La méthode itemsForBeginning est appelée lorsque l'utilisateur commence à "faire glisser" ( Dragging ) une cellule dans la cellule de collection.

Notez que dans cette méthode, la Collection View a ajouté indexPath . Cela nous dira quel élément de la collection, quel indexPath , nous allons «glisser-déposer». C'est vraiment très pratique pour nous, car c'est l'application qui est responsable de l'utilisation des arguments session et indexPath pour comprendre comment gérer ce glisser-déposer de Drag .

Si le tableau [UIDragItems] des éléments « draggable » est retourné, le «drag» du Drag initialisé, si le tableau vide [] est retourné, le «drag» du Drag ignoré.

Je vais créer une petite fonction dragItems privée (at: indexPath) avec l'argument indexPath . Il renvoie le tableau [UIDragItem] dont nous avons besoin .



À quoi ressemble un UIDragItem par glisser-déposer?
Il n'a qu'une chose très IMPORTANTE appelée itemProvider . itemProvider est juste quelque chose qui peut fournir des données qui seront glissées.

Et vous avez le droit de demander: "Qu'en est-il du" glisser-déposer "d'un élément UIDragItem qui n'a tout simplement pas de données?" L'élément que vous souhaitez faire glisser peut ne pas contenir de données, par exemple, car la création de ces données est une opération coûteuse. Il peut s'agir d'une image ou de quelque chose qui nécessite le téléchargement de données depuis Internet. La grande chose est que l'opération Drag & Drop est complètement asynchrone. Lorsque vous commencez à faire glisser et Drag , c'est vraiment un objet très léger ( lift preview ), vous le faites glisser partout et rien ne se passe pendant ce "glisser". Mais dès que vous «déposez» Drop quelque part votre objet, puis, en tant que fournisseur d' articles , il doit vraiment fournir à votre objet «traîné» et «lancé» des données réelles, même si cela prend un certain temps.

Heureusement, il existe de nombreux fournisseurs d'éléments intégrés . Ce sont des classes qui existent déjà dans iOS et qui sont des itemPoviders , comme, par exemple, NSString , qui vous permet de faire glisser et déposer du texte sans polices. Bien sûr, il s'agit d'une image UIImage . Vous pouvez sélectionner et faire glisser et déposer des images UIImages tout au long . La classe NSURL , ce qui est absolument merveilleux. Vous pouvez accéder à la page Web , sélectionner l' URL et la «déposer» où vous le souhaitez. Cela peut être un lien vers un article ou une URL pour une image, comme ce sera le cas dans notre démo. Ce sont les classes de couleurs de UIColor , l' élément de carte MKMapItem, le contact CNContact depuis le carnet d'adresses, vous pouvez sélectionner et faire glisser beaucoup de choses. Tous sont des fournisseurs d'articles .

Nous allons «glisser-déposer» l'image UIImage . Il est situé dans la cellule de la cellule Collection View avec indexPath , ce qui m'aide à sélectionner la cellule de cellule , à en extraire l'image Outlet et à obtenir son image .

Exprimons cette idée avec quelques lignes de code.
Tout d'abord, je demande ma Collection View sur une cellule pour l'élément item correspondant à cet indexPath .



La méthode cellForItem (at: IndexPath) pour la Collection View ne fonctionne que pour les cellules visibles, mais, bien sûr, cela fonctionnera dans notre cas, car je «fais glisser-déposer» l'élément de collection Drag à l'écran et il est visible.

Donc, j'ai obtenu une cellule de cellule "déplaçable".
Ensuite, j'utilise l'opérateur as? à cette cellule afin qu'elle ait un TYPE de ma subclass personnalisée. Et si cela fonctionne, je reçois une image Outlet , à partir de laquelle je prends son image . Je viens de «capturer» l'image de l' image pour cet indexPath .

Maintenant que j'ai une image, je n'ai plus qu'à créer l'un de ces UIDragItems en utilisant l'image résultante comme itemProvider , c'est-à-dire la chose qui nous fournit les données.
Je peux créer dragItem en utilisant le constructeur UIDragItem , qui prend itemProvider comme argument:



Ensuite, nous créons un itemProvider pour l'image image en utilisant également le constructeur NSItemProvider . Il existe plusieurs constructeurs pour NSItemProvider , mais parmi eux il y en a un vraiment merveilleux - NSItemProvider (objet: NSItemProviderWriting) :



Vous donnez simplement l'objet objet à ce constructeur NSItemProvider , et il sait comment en faire itemProvider . En tant que tel objet, je donne à l'image l'image que j'ai reçue de la cellule et j'obtiens itemProvider pour UIImage .
Et c'est tout. Nous avons créé dragItem et devons le renvoyer comme un tableau ayant un élément.

Mais avant mon retour dragItem , je vais faire une chose, à savoir, pour définir la variable localObject pour dragItem , égale à l'image résultante l' image .



Qu'est-ce que cela signifie?
Si vous effectuez un «glisser-déposer» Draglocalement, c'est-à-dire à l'intérieur de votre application, vous n'avez pas besoin de parcourir tout ce code associé à itemProvider , par une récupération de données asynchrone. Vous n'avez pas besoin de le faire, il vous suffit de prendre localObject et de l'utiliser. Il s'agit d'une sorte de «court-circuit» avec «glisser-déposer» local Drag.

Le code que nous avons écrit fonctionnera lors du «glissement» Draghors de notre collection Collection Viewvers d'autres applications, mais si nous « glissons » Draglocalement, nous pouvons utiliser localObject . Ensuite, je retourne un tableau d'un élément dragItem .

Soit dit en passant, si je ne pouvais pas obtenir pour une raison quelconque l' image pour cette cellule cellule , je retourne un tableau vide [] , cela signifie que le « glisser » Dragest annulée.



En plus de l' objet local localObject , vous pouvez vous rappeler le contexte local localContext pour notre Dragsession de la session . Dans notre cas , ce sera une collection de CollectionView et il est utile de nous après:



Avoir « glisser-déposer » Drag, vous pouvez ajouter plus d' articles articles à ce « glisser-déposer », juste faire le geste tap sur eux. En conséquence, vous pouvez faire glisserDragde nombreux éléments à la fois. Et cela est facile à implémenter avec une autre méthode déléguée , UICollectionViewDragDelegate , très similaire à la méthode itemsForeginning , une méthode nommée itemsForAddingTo . La méthode itemsForAddingTo ressemble exactement à la méthode itemsForeginning et renvoie exactement la même chose, car elle nous donne également un indexPath de ce que l'utilisateur a «enregistré» pendant le processus de «glisser Drag- déposer» , et j'ai juste besoin de récupérer l' image de la cellule sur que l'utilisateur a «enregistré» et le retourner.



Renvoie un tableau vide [] à partir de la méthode itemsForAddingToconduit au fait que le geste de tapotement sera interprété de la manière habituelle, c'est-à-dire comme le choix de cette cellule cellulaire .
Et c'est tout ce dont nous avons besoin pour glisser-déposer Drag.
Nous lançons l'application.
Je sélectionne l'image "Venise", la maintiens pendant un moment et commence à bouger ...



... et nous pouvons vraiment faire glisser cette image dans l'application Photos, comme vous voyez le signe plus vert "+" dans le coin supérieur gauche de l'image "glissable". Je peux effectuer un geste de toucher sur une autre image Artika de la collection Collection View...



... et maintenant nous pouvons déposer deux images dans l'application Photos:



Puisque le Photosmécanisme est déjà intégré dans l'applicationDrag & Dropalors tout fonctionne très bien et c'est cool.
Donc, le "glisser Drag-déposer " et le "dumping" de l' Dropimage de la Galerie dans d'autres applications fonctionnent pour moi , je n'ai pas eu à faire grand-chose dans mon application, sauf pour livrer l' image sous forme de tableau [UIDragItem] . C'est l'une des nombreuses fonctionnalités intéressantes du mécanisme Drag & Drop- il est très facile de le faire fonctionner dans les deux sens.

Réinitialiser les Dropimages à la collectionCollection View


Maintenant, nous devons créer une Droppartie pour ma collection Collection Viewafin que nous puissions «vider» Droptoutes les images «glissées» À L'INTÉRIEUR de cette collection. Une image «déplaçable» peut «provenir» DE L'EXTÉRIEUR ou directement À L'INTÉRIEUR de cette collection.
Pour ce faire, nous faisons la même chose fait de déléguer dragDelegate , à savoir nous faire, l'auto , délégué dropDelegate dans la méthode de l'viewDidLoad :



Encore une fois, il faut monter au sommet de notre classe ImageGalleryCollectionViewController et de vérifier l' application du Protocole UICollectionViewDropDelegate :



Dès que nous avons ajouté notre nouveau protocole, le compilateur a de nouveau commencé à «se plaindre» que nous n'avions pas implémenté ce protocole. Nous cliquons sur le bouton Fixet les méthodes requises de ce protocole apparaissent devant nous. Dans ce cas, nous sommes informés que nous devons implémenter la méthode performDrop :



Nous devons le faire, sinon une «réinitialisation» ne se produira pas Drop. En fait, je vais implémenter la méthode performDrop en dernier, car il existe quelques autres Appleméthodes hautement recommandées que vous devez implémenter pour la Droppièce. C'est canHandle et dropSessionDidUpdate :



Si nous mettons en œuvre ces deux méthodes, nous pouvons obtenir un peu de billets verts signe plus « + » quand nous traînons et des images de chute de l'extérieur de notre collection ollection View, et en plus, nous ne tenterons pas de vider ce que nous ne comprenons pas.

Implémentons canHandle . Nous devons vous pouvez utiliser la version de la méthode canHandle , qui est destinée à la collecte ollection View, mais cette méthode ollection Viewressemble exactement à la méthode similaire pour UIView normal , il n'y a pas d' indexPath là - bas , nous avons juste besoin de retourner session.canLoadObjects (ofClass: UIImage.self) , et cela signifie que j'accepte le "reset" des objets de ce cl PAS dans ma collection ollection View:



Mais cela ne suffit pas pour "vider" l' Dropimage dans ma collection Collection ViewEXTÉRIEUR.
Si le « reset » Dropl'image se déroule dans la collection Collection View, l'utilisateur réorganise ses propres éléments d' éléments à travers le mécanisme Drag & Drop, alors qu'une seule image une UIImage et la mise en œuvre de la méthode canHandle ressemblera à de la manière ci - dessus.

Mais si le «dumping» de l' Dropimage se produit À L'EXTÉRIEUR, alors nous ne devrions gérer que le «glisser-déposer» Dragqui est l'image UIImage avec URLcette image, car nous n'allons pas stocker directement les images UIImagedans le modèle. Dans ce cas, je ne retournerai true dans la méthode canHandle que si deux conditions sont remplies en même temps : session.canLoadObjects (ofClass: NSURL.self) && session.canLoadObjects (ofClass: UIImage.self) :



je dois déterminer si j'ai affaire à une «réinitialisation» À L'EXTÉRIEUR OU À L'INTÉRIEUR. Je vais le faire en utilisant la constante calculée isSelf , pour le calcul de laquelle je peux utiliser une telle chose dans une Dropsession de session comme sa Dragsession locale localDragSession . Cette Dragsession locale à son tour a un contexte local localContext .
Si vous vous souvenez, nous définissons ce contexte local dans la méthodeitemsForBeginning Drag délégué UICollectionViewDragDelegate : J'examinerai



le contexte local localContext pour l'égalité de ma collection collectionView . Vrai, TYPE de localContext sera Any , et je dois faire un transtypage de TYPE Any en utilisant l'opérateur as? UICollectionView :



si le contexte local (session.localDragSession? .LocalContext as? UICollectionView) est égal à ma collection collectionView , alors la variable calculée isSelf est vraieet il y a une «réinitialisation» locale À L'INTÉRIEUR de ma collection. Si cette égalité est violée, alors nous avons affaire à une "réinitialisation" DropÀ L'EXTÉRIEUR.

La méthode canHandle indique que nous ne pouvons gérer que ce type de «glisser-déposer» Dragsur notre collection Collection View. Sinon, plus loin, cela n'a aucun sens de parler de "dumping" Drop.

Si nous continuons à « reset » Drop, il est encore jusqu'au moment où l'utilisateur soulèvera vos doigts sur l'écran et il y aura un véritable « remise à zéro » Drop, nous devons signaler en iOSutilisant la méthode dropSessionDidUpdate délégué UICollectionViewDropDelegate sur notre offre UIDropProposal pour mettre en œuvre la réinitialisation Drop.

Dans cette méthode, nous devons renvoyer une Dropphrase qui peut avoir des valeurs .copy ou .move ou .cancel ou .forbidden pour l'argument opération . Et ce sont toutes les possibilités que nous avons dans le cas ordinaire lorsqu'il s'agit d'une UIView régulière .

Mais la collection Collection Viewva plus loin et propose de renvoyer l'offre spécialisée UICollectionViewDropProposal , qui est une subclassclasse de UIDropProposal et vous permet de spécifier, en plus de l' opération, un paramètre d' intention supplémentaire pour la collection Collection View.

Paramètrel'intention indique à la collectionCollection Viewsi nous voulons que l'élément «jeté» soit placé à l'intérieur d'une cellule existante , ou si nous voulons ajouter une nouvelle cellule. Vous voyez la différence? Dans le cas de la collecte,Collection Viewnous devons communiquer notre intention .

Dans notre cas, nous voulons toujours ajouter une nouvelle cellule, vous verrez donc à quoi notre paramètre d' intention sera égal.
Nous sélectionnons le deuxième constructeur pour UICollectionViewDropProposal :



Dans notre cas, nous voulons toujours ajouter une nouvelle cellule et le paramètre d' intention prendra la valeur .insertAtDestinationIndexPath par opposition.insertIntoDestinationIndexPath .



J'ai de nouveau utilisé la constante calculée isSelf , et s'il s'agit d'une auto- réorganisation, alors je déplace .move , sinon je copie .copy . Dans les deux cas, nous utilisons .insertAtDestinationIndexPath , c'est-à-dire l'insertion de nouvelles cellules .

Jusqu'à présent, je n'ai pas implémenté la méthode performDrop , mais regardons ce qu'une collection peut déjà faireCollection Viewavec cette petite information que nous lui avons fournie.

Je fais glisser l'image depuisSafarile moteur de rechercheGoogle, et un signe «+» vert apparaît au-dessus de cette image, indiquant que notre galerie d'images est non seulement prête à accepter et copier cette image avec elle URL, mais aussi à fournir une place à l'intérieur de la collection Collection View:



je peux cliquer sur une autre paire d'images dans Safari, et Il y aura déjà 3 images "glissées":



Mais si je lève le doigt et "dépose" Dropces images, elles ne seront pas placées dans notre Galerie, mais reviendront simplement à leurs emplacements précédents, car nous n'avons pas encore implémenté la méthode performDrop .



Vous pouvez voir que la collection Collection Viewsait déjà ce que je veux faire.
La collection Collection Viewest une chose absolument merveilleuse pour le mécanisme.Drag & Drop, elle a des fonctionnalités très puissantes pour cela. Nous l'avons à peine touchée en écrivant 4 lignes de code, et elle a déjà beaucoup avancé dans la perception du "reset" Drop.
Revenons au code et implémentons la méthode performDrop .



Dans cette méthode, nous ne pourrons pas nous débrouiller avec 4 lignes de code, car la méthode performDrop est un peu plus compliquée, mais pas trop.
Lorsque la «réinitialisation» se produit Drop, alors dans la méthode performDrop , nous devons mettre à jour notre modèle, qui est la galerie d' images imageGallery avec une liste d'images images , et nous devons mettre à jour notre collection visuelle collectionView .

Nous avons deux scénarios de «réinitialisation» différents Drop.

S'il y a une «réinitialisation» Dropde ma collection collectionView , alors je dois «réinitialiser» l' Dropélément de collection dans un nouvel emplacement et le supprimer de l'ancien emplacement, car dans ce cas, je déplace ( .move ) cet élément de collection. C'est une tâche insignifiante.

Il y a une «réinitialisation» Dropd'une autre application, alors nous devons utiliser la propriété itemProvider de l' élément item «glissé» pour sélectionner les données.

Lorsque nous effectuons une «réinitialisation» Dropdans la collection collectionView , la collection nous fournit un coordinateur coordinateur. Tout d'abord, nous avons signalé le coordonnateur coordonnateur , il destinationIndexPath , à savoir indexPath « -destination », « remise à zéro » Drop, qui est où nous serons « remise à zéro ».



Mais destinationIndexPath peut être nul , car vous pouvez faire glisser l'image «supprimée» vers la partie de la collection Collection Viewqui n'est pas la place entre certaines cellules existantes , elle pourrait donc être nulle . Si cette situation se produit, je crée un IndexPath avec l'élément 0th item dans la section 0th section .



Je pourrais choisir n'importe quel autre indexPath , mais j'utiliserai cet indexPath par défaut.

Nous savons maintenant où nous allons effectuer la «réinitialisation» Drop. Nous devons parcourir tous les éléments de coordinateur «réinitialisables» fournis par le coordinateur . Chaque élément de cette liste a un TYPE UICollectionViewDropItem et peut nous fournir des informations très intéressantes.

Par exemple, si je peux obtenir sourceIndexPath à partir de item.sourceIndexPath , alors je saurai avec certitude que ce «glisser-déposer» Dragest effectué à partir de lui-même, self, et la source du glissement Dragest l'élément de collection avec indexPath égal à sourceIndexPath :



je n'ai même pas besoin de regarder localContext dans ce cas pour savoir que ce "glisser-déposer" a été effectué à L'INTÉRIEUR de la collection collectionView . Ouah!

Maintenant, je connais le sourceIndexPath source et le destinationIndexPath « destination » Drag & Drop, et la tâche devient triviale. Tout ce que je dois faire est de mettre à jour le modèle afin que la source et la «destination» soient échangées, puis de mettre à jour la collection collectionView , dans laquelle vous devrez supprimer l'élément de collection avec sourceIndexPath et l'ajouter à la collection avec destinationIndexPath .

Notre cas local est le plus simple, car dans ce cas, le mécanisme Drag & Dropfonctionne non seulement dans la même application, mais dans la même collection collectionView , et je peux obtenir toutes les informations nécessaires en utilisant le coordinateur coordinateur. Implémentons-le dans ce cas local le plus simple:



dans notre cas, je n'ai même pas besoin de localObject , que j'ai «caché» plus tôt lorsque j'ai créé dragItem et que je peux maintenant emprunter à l' élément « dragged » dans la collection d' éléments sous la forme item.localObject . Nous en aurons besoin lors du «dumping» d' Dropimages dans la «poubelle», qui se trouve dans la même application, mais qui n'est pas la même collection collectionView . Deux IndexPathes me suffisent maintenant : le source sourceIndexPath et le «destination» destinationIndexPath .

Je reçois d'abord des informationsimageInfo sur l'image à l'ancien emplacement du modèle, en la supprimant de là. Et puis insérez dans un tableau d' images de mes modèles imageGallery informations ImageInfo une image avec un nouvel indice destinationIndexPath.item . Voici comment j'ai mis à jour mon modèle:



je dois maintenant mettre à jour la collection collectionView elle-même. Il est très important de comprendre que je ne veux pas surcharger toutes les données de ma collection collectionView avec reloadData () au milieu du processus de «glisser-déposer»Drag, car il réinstalle tout le «monde» de notre galerie d'images, ce qui est très mauvais, NE LE FAITES PAS. Au lieu de cela, je vais ranger et insérer des élémentséléments individuellement:



j'ai supprimé l'élément de collection collectionView avec sourceIndexPath et inséré un nouvel élément de collection avec destinationIndexPath .

Il semble que ce code fonctionne très bien, mais en réalité, ce code peut «planter» votre application. La raison en est que vous apportez de nombreuses modifications à votre collection collectionView , et dans ce cas, chaque étape de modification de la collection doit être synchronisée avec le modèle normalement, ce qui n'est pas observé dans notre cas, car nous effectuons les deux opérations en même temps: supprimer et insérer. D'où la collection collectionVoirsera à un moment donné dans un état NON synchronisé avec le modèle.

Mais il y a un moyen vraiment cool de contourner cela, c'est que la collection collectionView a une méthode appelée performBatchUpdates qui a une fermeture ( closure) et à l'intérieur de cette fermeture, je peux placer n'importe quel nombre de ces deleteItems , insertItems , moveItems et tout ce que je veux:



Maintenant, deleteItems et insertItems seront exécutés en une seule opération, et il n'y aura jamais un manque de synchronisation de votre modèle avec la collection collectionView .

Et enfin, la dernière chose que nous devons faire est de demander au coordinateur d' implémenter et d'animer le «reset» lui Drop- même :



dès que vous soulevez votre doigt de l'écran, l'image bouge, tout se passe en même temps: «reset», l'image disparaît dans un endroit et l'apparence dans un autre.
Essayons de déplacer l'image de test «Venise» dans notre galerie d'images à la fin de la première ligne ...



... et de la «réinitialiser»:



comme nous le voulions, elle a été placée à la fin de la première ligne.
Hourra!Tout fonctionne!

Maintenant, nous ne traiterons PAS du cas local, c'est-à-dire lorsque l'élément "reset" vient à l'extérieur, c'est-à-dire d'une autre application.
Pour ce faire, nous écrivons else dans le code par rapport à sourceIndexPath . Si nous n'avons pas sourceIndexPath , cela signifie que l'élément «réinitialisable» provenait de l'extérieur et nous devrons utiliser le transfert de données à l'aide de l' itemProver de l' élément item.dragItem.itemProvider réinitialisable :



si vous «glissez Draget déposez» à l' extérieur et «déposez» "Drop, ces informations deviennent-elles disponibles instantanément? Non, vous sélectionnez les données de la chose «traînée» ASYNCHRONE. Mais que faire si l'échantillon prend 10 secondes? Que fera la collection en ce moment ollection View? De plus, les données peuvent ne pas arriver dans l'ordre dans lequel nous les avons demandées. Pour gérer cela n'est pas facile, et a Appleproposé pour ollection Viewce cas une technologie complètement nouvelle pour l'utilisation de substituts Placeholders.

Vous placez un Collection Viewespace réservé dans votre collection Placeholder, et la collection Collection Viewgère tout cela pour vous, donc tout ce que vous avez à faire lorsque les données sont finalement sélectionnées est de demander à l'espace réservé d' Placeholderappeler son contexte placeholderContextet dites-lui que vous avez reçu l'information. Mettez ensuite à jour votre modèle et contexte placeholderContext échange AUTOMATIQUEMENT la cellule avec l'espace réservé Placeholderavec l'une de vos cellules , ce qui correspond au type de données que vous avez reçues.

Nous effectuons toutes ces actions en créant un contexte d' espace réservé placeholderContext qui gère l'espace réservé Placeholderet que vous obtenez du coordinateur coordinateur , vous demandant de «réinitialiser» Dropl'élément item dans l'espace réservé Placeholder.

J'utiliserai l'initialiseur pour le contexte d' espace réservé placeholderContextqui "jette" dragItem vers un UICollectionViewDropPlaceholder :



l'objet que je vais "lancer" Dropest item.dragItem , où item est un élément for d'une boucle, puisque nous pouvons lancer de Dropnombreux coordinator.items . Nous les «jetons» un par un. Donc, item.dragItem est ce que nous « glissons - déposonsDrag » Drop. L'argument suivant de cette fonction est l'espace réservé, et je vais le créer à l'aide de l'initialiseur UICollectionViewDropPlaceholder :



pour ce faire, j'ai besoin de savoir OERE je vais insérer l'espace réservéPlaceholder, c'est-à-dire insertionIndexPath , ainsi que l'identifiant de la cellule réutilisée reuseIdentifier .
L'argument insertionIndexPath est évidemment égal à destinationIndexPath , c'est l' IndexPath pour placer l'objet «glissé», il est calculé au tout début de la méthode performDropWith .

Examinons maintenant l' ID de cellule reuseIdentifier . VOUS devez décider quel type de cellule est votre localisateur Placeholder. Le coordinateur coordinateur n'a pas de cellule «pré- emballée » pour le localisateurPlaceholder. C'est VOUS qui devez décider de cette cellule cellulaire . Par conséquent, l'identifiant de la cellule réutilisée reuseIdentifiercell est demandé au vôtre storyboardafin qu'il puisse être utilisé comme PROTOTYPE.

Je l'appellerai "DropPlaceholderCell", mais en gros, je pourrais le nommer.
Ceci est juste la chaîne String que je vais utiliser sur la mienne storyboardpour créer cette chose.
Revenez à la nôtre storyboardet créez une cellule de cellule pour l'espace réservé Placeholder. Pour ce faire, il suffit de sélectionner une collection Collection Viewet de l'inspecter. Dans le tout premier domaine, Itemsje passe 1à2. Cela crée immédiatement une deuxième cellule pour nous, qui est une copie exacte de la première.



Nous sélectionnons notre nouvelle cellule ImageCell, définissons l'identifiant « DropPlaceholderCell», en supprimons tous les UIéléments, y compris Image View, puisque ce PROTOTYPE est utilisé lorsque l'image n'est pas encore arrivée. Nous y ajoutons un nouvel indicateur d'activité à partir de la palette d'objets Activity Indicator, il tournera, permettant aux utilisateurs de savoir que j'attends des données de «réinitialisation». Également modifier la couleur de fond Backgroundpour comprendre que lorsque « Reset » de l'image fonctionne exactement cette cellule cellulaire comme des prototypes:



Outre le type de nouvelle cellule ne doit pas être ImageCollectionVewCell, car il n'y aura pas d'images dedans. Je ferai de cette cellule une cellule régulière de TYPE UIollectionCiewCell , car nous n'en avons pas besoin Outletspour le contrôle:



configurons l'indicateur d'activité Activity Indicatorafin qu'il commence à s'animer dès le début, et je n'aurais rien à écrire dans le code pour le démarrer. Pour ce faire, cliquez sur l'option Animating:



Et c'est tout. Donc, nous avons fait tous les réglages pour cette cellule DropPlaceholderCell, nous revenons à notre code. Maintenant, nous avons un excellent localisateur Placeholderprêt à l'emploi.

Tout ce que nous avons à faire est d'obtenir les données, et lorsque les données sont reçues, nous informons simplement le placeholderContext de ce contexte et il échangera le placeholderPlaceholderet notre cellule "native" avec des données, et nous apporterons des modifications au modèle.

Je vais «charger» UN objet, qui sera mon élément en utilisant la méthode loadObject (ofClass: UIImage.self) (singulier). J'utilise le code item.dragItem.itemProvider fournisseur ItemProvider , qui fournira des éléments de données ont produit de manière asynchrone. Il est clair que si iitemProvider est connecté , nous obtenons l' objet «reset» iitem en dehors de cette application. Ce qui suit est la méthode loadObject (ofClass: UIImage.self) (singulier):



Cette fermeture particulière n'est PAS effectuée surmain queue. Et, malheureusement, nous avons dû passer à l' main queueutilisation de DispatchQueue.main.async {} afin de «capturer» le rapport hauteur / largeur de l'image dans la variable aspectRatio locale .

Nous avons vraiment introduit deux variables locales imageURL et aspectRatio ...



... et nous les "attraperons" lors du chargement de l'image image et de l'URL URL :



si les deux variables locales imageURL et aspectRatio ne sont pas nulles , nous demanderons le contexte de l' espace réservé placeholderContext en utilisant la méthode commitInsertiondonnez-nous la possibilité de changer notre modèle imageGallery :



Dans cette expression, nous avons insertionIndexPath - c'est l' indexPath à insérer, et nous changeons notre modèle imageGallery . C'est tout ce que nous devons faire, et cette méthode remplacera AUTOMATIQUEMENT un espace réservé Placeholderpar une cellule en appelant la méthode cellForItemAt normale .

Notez que insertionIndexPath peut être très différent de destinationIndexPath . Pourquoi?Parce que l'échantillonnage des données peut prendre 10 secondes, bien sûr, c'est peu probable, mais cela peut prendre 10 secondes. Pendant ce temps, Collection Viewbeaucoup de choses peuvent se produire dans la collection . Possibilité d' ajouter de nouvelles cellules cellules , tout se passe assez vite.

Utilisez TOUJOURS insertionIndexPath et UNIQUEMENT insertionIndexPath pour mettre à jour votre modèle.

Comment mettre à jour notre modèle?

Nous allons insérer la structure imageModel dans le tableau imageGallery.images , composé du rapport d'aspect de l'image aspectRatio et de l'URL de l'image imageURL que le fournisseur correspondant nous a renvoyé .

Cela met à jour notre modèle imageGallery et la méthode commitInsertion fait le reste pour nous. Vous n'avez plus besoin de faire quoi que ce soit de plus, pas d'insertions, pas de suppressions, rien de tout cela. Et, bien sûr, puisque nous sommes dans une fermeture, nous devons nous ajouter . .



Si nous sommes pour une raison de ne pas en mesure d'obtenir le rapport d'aspect aspectRatio et l' URLimage imageURL du correspondant par le fournisseur , une erreur aurait été reçu erreur à la place par le fournisseur , nous devons leur faire connaître le contexte placeholderContext , vous devez détruire ce un espace réservé Placeholder, parce que nous sommes tous les mêmes, nous ne pouvons pas obtenir d'autres données:



une chose à garder à l'esprit est URLsqu'elles proviennent de lieux comme celui-ci Google, en réalité, elles ont besoin de transformations mineures pour être «propres»URLpour l'image. Comment ce problème est résolu peut être vu dans cette application de démonstration dans un fichier Utilities.swiftsur Github .
Par conséquent, lorsque URLnous recevons une image, nous utilisons la propriété imageURL de la classe URL :



et c'est tout ce que vous devez faire pour accepter quelque chose à l'extérieur de la collection Collection View.

Voyons cela en action. Nous lançons simultanément en mode multitâche notre application de démonstration ImageGalleryet Safariavec un moteur de recherche Google. Comme Googlenous cherchons des images sur le thème de « Dawn» (lever du soleil). Dans Safaridéjà construitDrag & Drople mécanisme, afin que nous puissions sélectionner l'une de ces images, la maintenir longtemps, la déplacer un peu et la faire glisser dans notre galerie d'images.



La présence d'un signe plus vert "+" indique que notre application est prête à accepter une image tierce et à la copier dans votre collection à l'emplacement spécifié par l'utilisateur. Après l'avoir «réinitialisée», il faut un certain temps pour télécharger l'image, et à ce moment cela fonctionne Placeholder: Une



fois le téléchargement terminé, l'image «réinitialisée» est placée au bon endroit et Placeholderdisparaît:



nous pouvons continuer à «réinitialiser» les images et les placer dans notre collections d'images encore plus nombreuses:



Après le travail de "réinitialisation" Placeholder:



En conséquence, notre galerie d'images est remplie de nouvelles images:



Maintenant qu'il est clair que nous sommes en mesure de prendre des photos de l'extérieur, on n'a pas besoin de l'image de test, et nous supprimerons:



Notre viewDidLoad devient très simple: il est que nous faisons notre Controller Draget Dropdélégué et ajoute reconnaisseur geste de pincement , qui régule le nombre d'images par ligne:



Bien sûr , nous pouvons ajouter un cache pour les images imageCache :



Nous remplirons l' imageCache lors de la "réinitialisation" Dropdans la méthode performDrop ...



et lors de la récupération à partir du "réseau" dans la classe ImageCollectionViewCell personnalisée :



Et nous utiliserons le cache imageCache lors de la lecture d'une cellulecellule de notre galerie d'images dans la classe personnalisée ImageCollectionViewCell :



Maintenant, nous commençons avec une collection vide ...



... puis "déposons" une nouvelle image dans notre collection ...



... l'image est téléchargée etPlaceholderfonctionne ...



... et l'image apparaît au bon endroit:



Nous continuons à remplir notre collection À L'EXTÉRIEUR:



Vient charger des images etPlaceholderstravailler ...



Et les images apparaissent au bon endroit:



Donc, nous pouvons faire beaucoup avec notre galerie d'images: remplissez-la À L'EXTÉRIEUR, réorganisez les éléments À L'INTÉRIEUR, partagez des images avec d'autres applications niyami.
Nous devons juste lui apprendre à se débarrasser des images inutiles en les «réinitialisant»Dropdans la "poubelle" présentée sur la barre de navigation à droite. Comme décrit dans la section «Caractéristiques de l'application de démonstration d'Image Gallery», la «poubelle» est représentée par la classe GabageView , qui hérite de UIView et nous devons lui apprendre à accepter les images de notre collection ollection View.

Réinitialisez les Dropimages de la galerie dans la corbeille.


Immédiatement de l'endroit - à la carrière. Je vais ajouter à GabageView « interaction » Interaction et il sera UIDropInteraction , depuis que je suis en train de « réinitialiser » Dropcertaines choses. Tout ce dont nous avons besoin pour fournir cet UIDropInteraction est un délégué délégué , et je vais m'attribuer moi- même à ce délégué délégué :



Naturellement, notre classe GabageView doit confirmer que nous implémentons le protocole UIDropInteractionDelegate :



tout ce que nous devons faire pour le faire fonctionner Drop, il s'agit d'implémenter les méthodes canHandle que nous connaissons déjà ,sessionDidUpdate et performDrop .



Cependant, contrairement à des méthodes similaires pour la collecteCollection View, nous ne disposons d'aucune information supplémentaire sous la forme d'un indexPath du lieu de dumping.

Implémentons ces méthodes.
Dans la méthode canHandle sera traitée que la « glisser-déposer »Drag, qui représentent l'image d' un UIImage . Par conséquent, jeneretournerai true que si session.canLoadObjects (ofClass: UIImage.self) :



Dans la méthode canHandle ,vous dites essentiellement que si l'objet "déplaçable" n'est pas une image UIImage, puis en outre, cela n'a aucun sens de continuer la suppression "reset" et d'appeler les méthodes suivantes.
Si l'objet «glissable» est une image UIImage , alors nous exécuterons la méthode sessionDidUpdate . Tout ce que nous devons faire dans cette méthode est de renvoyer notre offre de «réinitialisation» UIDropProposalDrop . Et je suis prêt à n'accepter que l'objet LOCALEMENT «glissé» du TYPE UIImage de l'image , qui peut être «déposé» Dropn'importe où dans mon GarbageView . Mon GarbageView n'interagira pas avec les images exportées À L'EXTÉRIEUR. Donc j'analyse en utilisant la variable session.localDragSessions'il y a une «réinitialisation» locale Drop, et je renvoie la phrase «réinitialisation» sous la forme du constructeur UIDropProposal avec l'argument opération prenant la valeur .copy , car le «glisser-déposer» TOUJOURS LOCAL Dragdans mon application proviendra de la collection Collection View. Si «glisser Drag- déposer» et «réinitialiser» Dropse produisent À L'EXTÉRIEUR, je renvoie la phrase «réinitialiser» sous la forme du constructeur UIDropProposal avec l'argument d' opération prenant la valeur .fobbiden , c'est-à-dire «interdit» et nous obtiendrons un signe d'interdiction «réinitialiser» au lieu du signe plus vert .



Copie d'une image UIImage, nous simulerons une diminution de son échelle à presque 0, et lorsque la "réinitialisation" se produira, nous supprimerons cette image de la collection Collection View.
Afin de créer l'illusion d'images «vidage et disparition» dans la «poubelle» pour l'utilisateur, nous utilisons la méthode previewForDropping , nouvelle pour nous , qui nous permet de rediriger le «vidage» Dropvers un autre endroit et en même temps de transformer l'objet «vidage» lors de l'animation:



Dans cette méthode, en utilisant l'initialiseur UIDragPreviewTarget, nous obtenons une nouvelle pré-vue pour l'objet cible à supprimer et le redirige en utilisant la méthode retargetedPreviewà un nouvel endroit, à la "poubelle", avec son échelle jusqu'à presque zéro:



si l'utilisateur lève le doigt, une "réinitialisation" se produit Dropet je reçois (comme GarbageView ) un message performDrop . Dans le message performDrop, nous effectuons la «réinitialisation» réelle Drop. Honnêtement, l' image qui a été transférée sur GarbageView lui-même ne nous intéresse plus, car nous la rendrons pratiquement invisible, le fait même de l'achèvement de la «réinitialisation» Dropsignalera probablement que nous supprimons cette image de la collection Collection View. Pour ce faire, nous devons connaître la collection elle - même et la collection indexPathjeté l'image en elle. D'où pouvons-nous les obtenir?

Parce que le processus Drag & Dropse déroule dans une seule application, il est disponible pour nous tous local: locale Dragsession de localDragSession notre Dropsession de la session , le contexte local localContext , qui est notre collection de sollectionView et objet local localObject , que nous pouvons faire par lui - même est l' image remis à zéro l' image de « Galerie » ou indexPath . En raison de cela , nous pouvons obtenir dans la méthode performDrop classe GarbageView collection collection , et utiliserdataSource comment ImageGalleryCollectionViewController et modèle imageGallery notreController, nous pouvons obtenir un tableau d'images d' images TYPE [ImageModel]:



Avec l'aide de la section localeDragsession de localDragSession notreDropsession de la session , nous avons pu obtenir toutes les « glisser » sur GarbageView Drag éléments des éléments , et il peut y avoir beaucoup, comme noussavons, et tous sont des images de notre collection collectionView . CréationDragéléments dragItems notre collectionCollection View, nous avons fourni pour chaque « glisser »DragélémentdragItem objet local localObject , qui est l'image de l' image , mais il est on ne vient pasportéemain lorscollecte de réorganisation interne CollectionView , mais la « remisezéro » Galeries d'images « POUBELLE » nousdésespérément besoin dans l'installation locale localObject objet « drag » dragItem , après tout ce temps nous n'avons pas de coordinateur , qui partage si généreusement les informations sur ce qui se passe dans la collection collectionView .conséquent, nous voulonsl'objet local localObject index était indexPath dans le tableau d'images images de nos modèlesimageGallery . Effectuez les modifications nécessaires dans la méthode dragItems (à indexPath: indexPath) classe ImageGalleryCollectionViewController :



Maintenantnous pouvons prendre touséléments « pretaskivaemogo » point il localObject , qui est l'indice indexPath dans le tableau d'images images de nos modèles imagegallery , etenvoyer aux indices de tableau des index et tableau d' indexPahes d' images supprimées:



Connaître le tableau d'indexes d' index et le tableau d' indexPahes d' images supprimées, dans la méthode performBatchUpdatescollection collection nous supprimer toutes les images supprimées de modèles d' images et de la collection de collection :



Exécutez l'application, remplissez la galerie avec de nouvelles images:



Sélectionnez une paire d'images que nous voulons retirer de notre galerie ...



... « jeter » les sur l'icône avec la « poubelle » ...



Ils ont réduit presque à 0 ...



... et disparaissent de la collection Collection View, se cachant dans la "poubelle":



Enregistrement d'images entre les démarrages.



Pour enregistrer la galerie d'images entre les exécutions, nous utiliserons UserDefaults , après avoir converti notre modèle en un JSONformat. Pour ce faire, nous allons ajouter à notre Controllervariable de defailts var ...



... et dans le modèle ImageGallery et ImageModel protocole codable :



chaîne chaîne , des tableaux de la matrice , l'URL et double appliquent déjà le protocole codable , donc nous n'avons rien d' autre à faire pour se rendre à l' encodage de travail et décodage en modèles ImageGallery au JSONformat.
Comment obtenir la JSONversion d' ImageGallery ?
Pour ce faire, créez une variable calculée var json , qui renvoie le résultat d'une tentative de conversion elle- même , en utilisant JSONEncoder.encode () au JSONformat:



et c'est tout. Soit les données seront retournées à la suite de la conversion de soi au format JSON, soit nil si cette conversion échoue, bien que cette dernière ne se produise jamais, car ce TYPE est 100% encodable . La variable json facultative est utilisée uniquement pour des raisons de symétrie.
Nous avons maintenant un moyen de convertir les modèles ImageGallery au format DataJSON . La variable json a-t-elle des données TYPE ? qui peut être mémorisé dans UserDefaults .
Imaginez maintenant que d'une manière ou d'une autre nous avons réussi à obtenir les JSONdonnées json , et j'aimerais recréer à partir d'eux notre modèle, une instance de la structure ImageGallery . Pour ce faire, il est très facile d'écrire un INITIALIZER pour ImageGallery , dont l'argument d'entrée est des JSONdonnées json . Cet initialiseur sera un initialiseur "tombant" (failable) Si elle ne parvient pas à initialiser, il « tombe » et retourne nil :



Je suis juste une nouvelle valeur newValue par le décodeur JSONDecoder , en essayant de décoder les données JSON , qui sont transférés à mon initialiseur, puis l' affecte à l'auto .
Si j'ai réussi à le faire, alors j'obtiens une nouvelle instance d' ImageGallery , mais si ma tentative échoue, je retourne nil , car mon initialisation "a échoué".
Je dois dire que nous avons ici beaucoup plus de raisons d’échouer ( fail), car il est fort possible que les JSONdonnées jsonpeut être gâté ou vide, tout cela peut conduire à la «chute» ( fail) de l'initialiseur.

Maintenant , nous pouvons mettre en œuvre les READ JSONdonnées et modèle de récupération imagegallery la méthode viewWillAppear notre Controller...



... ainsi que d' une entrée dans l'observateur didSet {} propriétés imagegallery :



Lançons l'application et remplissez notre galerie d'images:



Si nous fermons l'application et de l' ouvrir à nouveau, nous pouvons voir notre galerie précédente Images enregistrées dans UserDefaults .

Conclusion


Cet article montre à quel point il est facile d'intégrer la technologie Drag & Dropdans une iOSapplication en utilisant l'exemple d'une application de démonstration très simple «Galerie d'images» . Cela a permis de modifier entièrement la galerie d'images, de «lancer» de nouvelles images à partir d'autres applications, de déplacer celles existantes et de supprimer celles inutiles. Et aussi de distribuer les images accumulées dans la Galerie à d'autres applications.

Bien sûr, nous aimerions créer de nombreuses collections d'images thématiques pittoresques et les enregistrer directement sur l'iPad ou iCloud Drive. Cela peut être fait si chacune de ces galeries est interprétée comme un UIDocument stocké en permanence. Une telle interprétation nous permettra de passer au niveau d'abstraction suivant et de créer une application qui fonctionne avec des documents. Dans une telle application, vos documents seront affichés par le composant DocumentBrowserViewController , très similaire à l'application Files. Il vous permettra de créer des images UIDocument de type «Galerie d'images» à la fois sur vous iPadet sur iCloud Drive, ainsi que de sélectionner le document souhaité pour l'affichage et l'édition.
Mais c'est le sujet du prochain article.

PS Le code de l'application de démonstration avant l'implémentation du mécanisme Drag & Dropet après est sur Github .


Source: https://habr.com/ru/post/fr425817/


All Articles