Drag & Drop

Vamos dar uma olhada rápida no funcionamento do Drag & Drop – Arrastar e Soltar. Essa API é bem interessante e completa, por isso não deixe de conferir a documentação oficial para todos os detalhes de seu funcionamento e essa palestra da #WWDC17.

Drag

Para habilitar a interação de arrastar – em inglês drag – basta criar um UIDragInteraction e adicioná-lo a view que desejamos arrastar. Repare que o procedimento é similar ao UIGestureRecognizer.

let drag = UIDragInteraction(delegate: self)
view.addInteraction(drag)

Na criação da interação atribuímos um delegate para receber os eventos, portanto temos que conformar com o protocolo UIDragInteractionDelegate. O único método exigido é dragInteraction(_, itemsForBeginning:), nele devemos retornar o item que está sendo arrastado.

func dragInteraction(_ interaction: UIDragInteraction, itemsForBeginning session: UIDragSession) -> [UIDragItem] {
    // Para passar o modelo do item arrastado usamos um `NSItemProvider`,
    // como a API pede um objeto, vamos transformar a estrutura String da Swift
    // em NSString do Foundation
    let item = NSItemProvider(object: "Arrastando" as NSString)

    // Note que retornamos um array pois um _drag_ pode conter múltiplos objetos
    return [ UIDragItem(itemProvider: item) ]
}

Drop

Na interação de soltar – em inglês drop – o procedimento é o mesmo, criamos um UIDropInteraction e o adicionamos na view que receberá o item.

let drop = UIDropInteraction(delegate: self)
containerView.addInteraction(drop)

No delegate UIDropInteractionDelegate temos o processamento do item a ser recebido. Primeiro vamos ajustar a operação a ser executada quando recebermos um drop, essa configuração altera o ícone que será exibido quando o item for arrastado em cima do alvo do drop.

func dropInteraction(_ interaction: UIDropInteraction, sessionDidUpdate session: UIDropSession) -> UIDropProposal {
    // Temos algumas opções (em ordem de mais utilizada para menos):
    //   - .cancel: cancela o _drag_, não tranfere nenhuma informação;
    //   - .copy: aceita o _drag_, as informações dos itens devem ser copiadas;
    //   - .move: aceita o _drag_, as informações devem ser movidas;
    //   - .forbidden: não aceita o _drag_, apesar de normalmente esse alvo
    //                 aceitar um _drag_, no momento não está aceitando;
    return UIDropProposal(operation: .copy)
}

Agora vamos processar o drop. Como o item (ou itens) de origem pode demorar para chegar – pode ser um arquivo grande ou até um download – essa operação é executada em segundo plano.

func dropInteraction(_ interaction: UIDropInteraction, performDrop session: UIDropSession) {
    // Usamos o método `loadObjects` para carregar os itens, assim que estiverem
    // disponíveis a _closure_ será executada. Como nosso exemplo de _drag_
    // passamos uma `NSString`, estamos especificando aqui.
    session.loadObjects(ofClass: NSString.self) { itens in
        // Vamos transformar a `NSString` em `String` e percorrer
        // todos os itens -- caso existam.
        for item in itens as! [String] {
          print(item)
        }
    }
}

Exemplo

Preparamos um exemplo completo para você testar. Crie um projeto iOS e no storyboard adicione uma UILabel e um UITextView. NÃO se esqueça de habilitar a propriedade User Interaction Enabled da label – para poder receber o toque – e utilizar o simulador do iPad.

class ViewController: UIViewController {
    @IBOutlet weak var label: UILabel!
    @IBOutlet weak var textView: UITextView!

    override func viewDidLoad() {
        super.viewDidLoad()

        let drag = UIDragInteraction(delegate: self)
        label.addInteraction(drag)

        let drop = UIDropInteraction(delegate: self)
        textView.addInteraction(drop)
    }
}

extension ViewController: UIDragInteractionDelegate {
    func dragInteraction(_ interaction: UIDragInteraction, itemsForBeginning session: UIDragSession) -> [UIDragItem] {
        let item = NSItemProvider(object: "Arrastando" as NSString)
        return [ UIDragItem(itemProvider: item) ]
    }
}

extension ViewController: UIDropInteractionDelegate {
    func dropInteraction(_ interaction: UIDropInteraction, sessionDidUpdate session: UIDropSession) -> UIDropProposal {
        return UIDropProposal(operation: .copy)
    }

    func dropInteraction(_ interaction: UIDropInteraction, performDrop session: UIDropSession) {
        session.loadObjects(ofClass: NSString.self) { itens in
            for item in itens as! [String] {
                let texto = self.textView.text + "\n\(item)"
                self.textView.text = texto
            }
        }
    }
}

Veja o projeto completo no GitHub.

Até a próxima! >}