
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:
- tout d'abord, nous doterons notre
Collection View
possibilité de «faire glisser» des images UIImage depuis et vers l'extérieur, - Ensuite, nous apprendrons à notre collection d'images
Collection View
à accepter le glisser- Drag
«externe» ou local de UIImage , - 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» Drag
localement, 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» Drag
hors de notre collection Collection View
vers d'autres applications, mais si nous « glissons » Drag
localement, 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 » Drag
est annulée.
En plus de l' objet local localObject , vous pouvez vous rappeler le contexte local localContext pour notre Drag
session 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 glisserDrag
de 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 Photos
mécanisme est déjà intégré dans l'applicationDrag & Drop
alors tout fonctionne très bien et c'est cool.Donc, le "glisser Drag
-déposer " et le "dumping" de l' Drop
image 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 Drop
images à la collectionCollection View
Maintenant, nous devons créer une Drop
partie pour ma collection Collection View
afin que nous puissions «vider» Drop
toutes 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 Fix
et 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 Apple
méthodes hautement recommandées que vous devez implémenter pour la Drop
piè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 View
ressemble 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' Drop
image dans ma collection Collection View
EXTÉRIEUR.Si le « reset » Drop
l'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' Drop
image se produit À L'EXTÉRIEUR, alors nous ne devrions gérer que le «glisser-déposer» Drag
qui est l'image UIImage avec URL
cette 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 Drop
session de session comme sa Drag
session locale localDragSession . Cette Drag
session 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» Drag
sur 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 iOS
utilisant 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 Drop
phrase 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 View
va plus loin et propose de renvoyer l'offre spécialisée UICollectionViewDropProposal , qui est une subclass
classe 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 View
si 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 View
nous 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 View
avec cette petite information que nous lui avons fournie.Je fais glisser l'image depuisSafari
le 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" Drop
ces 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 View
sait déjà ce que je veux faire.La collection Collection View
est 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» Drop
de 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» Drop
d'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» Drop
dans 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 View
qui 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» Drag
est effectué à partir de lui-même, self, et la source du glissement Drag
est 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 & Drop
fonctionne 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' Drop
images 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 Drag
et 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 Apple
proposé pour ollection View
ce cas une technologie complètement nouvelle pour l'utilisation de substituts Placeholders
.Vous placez un Collection View
espace réservé dans votre collection Placeholder
, et la collection Collection View
gè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' Placeholder
appeler 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é Placeholder
avec 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é Placeholder
et que vous obtenez du coordinateur coordinateur , vous demandant de «réinitialiser» Drop
l'é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" Drop
est item.dragItem , où item est un élément for d'une boucle, puisque nous pouvons lancer de Drop
nombreux 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 storyboard
afin 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 storyboard
pour créer cette chose.Revenez à la nôtre storyboard
et créez une cellule de cellule pour l'espace réservé Placeholder
. Pour ce faire, il suffit de sélectionner une collection Collection View
et de l'inspecter. Dans le tout premier domaine, Items
je 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 Background
pour 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 Outlets
pour le contrôle:
configurons l'indicateur d'activité Activity Indicator
afin 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 Placeholder
prê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 placeholderPlaceholder
et 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 queue
utilisation 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é Placeholder
par 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 View
beaucoup 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' URL
image 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 URLs
qu'elles proviennent de lieux comme celui-ci Google
, en réalité, elles ont besoin de transformations mineures pour être «propres»URL
pour l'image. Comment ce problème est résolu peut être vu dans cette application de démonstration dans un fichier Utilities.swift
sur Github .Par conséquent, lorsque URL
nous 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 ImageGallery
et Safari
avec un moteur de recherche Google
. Comme Google
nous cherchons des images sur le thème de « Dawn» (lever du soleil). Dans Safari
déjà construitDrag & Drop
le 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 Placeholder
disparaî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
Drag
et Drop
dé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" Drop
dans 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 etPlaceholder
fonctionne ...
... et l'image apparaît au bon endroit:
Nous continuons à remplir notre collection À L'EXTÉRIEUR:
Vient charger des images etPlaceholders
travailler ...
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»Drop
dans 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 Drop
images 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 » Drop
certaines 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é» Drop
n'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 Drag
dans mon application proviendra de la collection Collection View
. Si «glisser Drag
- déposer» et «réinitialiser» Drop
se 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» Drop
vers 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 Drop
et 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» Drop
signalera 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 & Drop
se déroule dans une seule application, il est disponible pour nous tous local: locale Drag
session de localDragSession notre Drop
session 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 localeDrag
session de localDragSession notreDrop
session 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 JSON
format. Pour ce faire, nous allons ajouter à notre Controller
variable 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 JSON
format.Comment obtenir la JSON
version 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 JSON
format:
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 JSON
donné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 JSON
donné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 JSON
donné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 JSON
donné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 & Drop
dans une iOS
application 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 iPad
et 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 & Drop
et après est sur Github .