Drag and Drop en français

Introduction au Drag and Drop en SwiftUI

Le concept de Drag and Drop (glisser-déposer) dans SwiftUI permet de déplacer et de déposer des objets, comme des éléments d'interface utilisateur, sur l'écran. Cette fonctionnalité enrichit l'expérience utilisateur en facilitant la réorganisation et le transfert d'objets à travers différentes parties de l'interface de l'application.

Déplacement d'éléments transférables

Le protocole Transferable de SwiftUI simplifie l'ajout de l'expérience de glisser-déposer dans une application. Cela peut être facilement réalisé en utilisant les modificateurs .draggable() et .dropDestination().

Glisser des éléments

Pour activer la fonctionnalité de glisser, il suffit d'attacher le modificateur de vue .draggable() et de spécifier l'objet que l'on souhaite déplacer. Cet objet doit se conformer au protocole Transferable. Voici un exemple de code qui affiche quatre chaînes représentant des produits Apple et permet à l'utilisateur de les faire glisser vers une boîte en dessous :

struct ContentView: View {
    @State private var appleProducts: [String] = ["iphone", "macbook.and.iphone", "ipad", "applewatch.watchface"]
    @State private var dropImage = Image(systemName: "square.and.arrow.down")

    var body: some View {
        VStack {
            HStack {
                ForEach(appleProducts, id: \.self) { product in
                    Image(systemName: product)
                        .frame(minWidth: 50, minHeight: 50)
                        .background(.white)
                        .foregroundStyle(.black)
                        .cornerRadius(10)
                        .draggable(product)
                }
            }
            .frame(minWidth: 250, minHeight: 70)
            .cornerRadius(10)
            .background(.yellow)
        }
    }
}

Déposer des éléments

Pour gérer l'action de dépôt, utilisez le modificateur .dropDestination(), qui a deux paramètres : - items : un tableau contenant tous les éléments glissés. Si un seul élément est glissé, ce tableau contiendra uniquement un élément. - location : position de l'élément glissé dans la vue de dépôt.

struct ContentView: View {
    @State private var appleProducts: [String] = ["iphone", "macbook.and.iphone", "ipad", "applewatch.watchface"]
    @State private var dropImage = Image(systemName: "square.and.arrow.down")

    var body: some View {
        VStack {
            HStack {
                ForEach(appleProducts, id: \.self) { product in
                    Image(systemName: product)
                        .frame(minWidth: 50, minHeight: 50)
                        .background(.white)
                        .foregroundStyle(.black)
                        .cornerRadius(10)
                        .draggable(product)
                }
            }
            .frame(minWidth: 250, minHeight: 70)
            .cornerRadius(10)
            .background(.yellow)

            Text("Déposez les éléments ici :")
            dropImage
                .frame(width: 200, height: 150)
                .background(.blue)
                .foregroundStyle(.white)
                .cornerRadius(10)
                .dropDestination(for: Image.self) { items, location in
                    dropImage = items.first ?? Image(systemName: "square.and.arrow.down")
                    print(location)
                    return true
                }
        }
    }
}

Utiliser des fournisseurs d’éléments

DropDelegate est un protocole dans SwiftUI qui permet aux développeurs de gérer les opérations de glissement et de dépôt de manière flexible. Pour permettre à une vue d'accepter des dépôts, utilisez la méthode onDrop() et spécifiez un délégué de dépôt.

Ce code définit un délégué de dépôt qui gère différents événements liés au dépôt d'objets sur une vue :

struct CDDropDelegate: DropDelegate {
    func dropEntered(info: DropInfo) {
        // Déclenché lorsqu'un objet entre dans la vue.
    }
    func dropExited(info: DropInfo) {
        // Déclenché lorsqu'un objet sort de la vue.
    }
    func dropUpdated(info: DropInfo) -> DropProposal? {
        // Déclenché lorsqu'un objet se déplace dans la vue.
    }
    func validateDrop(info: DropInfo) -> Bool {
        // Détermine si le dépôt doit être accepté ou rejeté.
    }
    func performDrop(info: DropInfo) -> Bool {
        // Gère le dépôt lorsque l'utilisateur dépose un objet dans la vue.
    }
}

Proposition de dépôt

DropProposal est une structure de SwiftUI qui aide à contrôler comment les objets et les opérations de dépôt doivent être gérés lors du dépôt dans une vue SwiftUI. Elle est utilisée avec le modificateur .onDrop() pour déterminer ce qui se passe lorsque l'utilisateur effectue un dépôt. Cette proposition peut être personnalisée via la propriété operation pour spécifier l'opération de dépôt (ex: .move ou .copy).

Voici un exemple :

struct ContentView: View {
    @State private var text: String = ""
    var body: some View {
        VStack {
            Text("Exemple de DropProposal")
                .font(.largeTitle)
                .padding()
            Text("Déposez le texte ici :")
            Text(text)
                .padding()
                .background(Color.yellow)
                .onDrop(of: [UTType.text], isTargeted: $text?, perform: { providers, isTargeted in
                    // Créer une proposition de dépôt personnalisée
                    let dropProposal = DropProposal(operation: .copy)

                    if isTargeted {
                        if let itemProvider = providers.first {
                            itemProvider.loadObject(ofClass: String.self) { item, error in
                                if let droppedText = item as? String {
                                    DispatchQueue.main.async {
                                        self.text = droppedText
                                    }
                                }
                            }
                            return dropProposal
                        }
                    }
                    return nil
                })
                .frame(width: 200, height: 100)
                .border(Color.gray)
        }
    }
}

Informations sur le dépôt

DropInfo est une structure qui fournit des informations sur une action de dépôt. Il est souvent utilisé lors de la gestion des dépôts avec le modificateur onDrop(). DropInfo fournit des détails sur l'emplacement du dépôt, les types de données déposées, et d'autres informations importantes.

Exemple utilisant .onDrag() et .onDrop()

Voici un exemple illustrant un LazyVGrid simple avec quatre éléments. Le modificateur de vue .onDrag() est appliqué à chaque élément pour gérer le glissement, tandis que le modificateur .onDrop() permet de déplacer et de réorganiser les éléments, le résultat étant conservé dans une variable d'état nommée draggedItem.

struct ContentView: View {
    @State private var items = ["Apple", "Banana", "Lemon", "Orange"]
    @State private var draggedItem: String?
    var body: some View {
        VStack {
            LazyVGrid(columns: [GridItem(.adaptive(minimum: 160), spacing: 15)], spacing: 15) {
                ForEach(items, id: \.self) { item in
                    Text(item)
                        .background(Color.green)
                        .font(.headline)
                        .foregroundColor(.white)
                        .shadow(color: .black, radius: 3)
                        .frame(maxWidth: 136)
                        .padding()
                        .onDrag({
                            self.draggedItem = item
                            return NSItemProvider(item: nil, typeIdentifier: item)
                        })
                        .onDrop(of: [UTType.text], delegate: MyDropDelegate(item: item, items: $items, draggedItem: $draggedItem))
                }
            }
        }
    }
}

struct MyDropDelegate: DropDelegate {
    let item: String
    @Binding var items: [String]
    @Binding var draggedItem: String?

    func performDrop(info: DropInfo) -> Bool {
        return true
    }

    func dropEntered(info: DropInfo) {
        guard let draggedItem = self.draggedItem else { return }
        if draggedItem != item {
            let from = items.firstIndex(of: draggedItem)! 
            let to = items.firstIndex(of: item)!  
            withAnimation(.default) {
                self.items.move(fromOffsets: IndexSet(integer: from), toOffset: to > from ? to + 1 : to)
            }
        }
    }
}