iOS 11: Drag & Drop with Custom View (part 2)

Drag & Drop with Custom View

Overview

Ở bài viết trước, tôi đã đề cập đến việc adopt tính năng drag và drop trên đối tượng UITableView, các bạn có thể tham khảo tạo đây: iOS 11: Drag & Drop with UITableView (part 1). Trong khuôn khổ của bài viết này tôi xin đề cập đến cách mà chúng ta có thể enable tính năng drag và drop trên một custom view đó là UIImageView.

Getting Started

Việc cần làm đầu tiên là tạo một project chạy trên iPad, thiết bị hỗ trợ drag và drop giữa các ứng dụng. Chúng ta sẽ tìm cách đển có thể gửi và nhận dữ liệu từ một app khác cũng hỗ trợ drag và drop ví dụ như app mặc định Photos. Bật chế độ Split View trên iPad, một bên là app của chúng ta, một bên là app Photos. Và bây giờ hãy xem làm thế nào để enable hai tính năng drag và drop.

Enable tính năng Drag và Drop

Để cho phép tính năng drag, drop, hoặc cả hai tính năng trên, chúng ta cần gắn một thuộc tính có tên UIDragInteraction, UIDropInteraction vào UIView thông qua phương thức addInteraction(). Tốt nhất là chúng ta nên gọi để hàm này trong phương thức viewDidLoad() để đảm bảo view của chúng ta đã sẵn sàng cho tính năng drag và drop ngay khi view vừa được load.

Thêm tính năng drag cho view

let dragInteraction = UIDragInteraction(delegate: self)
imageView.addInteraction(dragInteraction)

Thêm tính năng drop cho view

let dropInteraction = UIDropInteraction(delegate: self)
view.addInteraction(dropInteraction)

Ngoài ra, đối với một đối tượng UIImageView, chúng ta cần thêm muột bước nữa, đó là enable User Interaction;

imageView.isUserInteractionEnabled = true

Cung cấp dữ liệu cho một Drag Session

Phương thức dragInteraction(:itemsForBeginning:) là một phương thức cần thiết để enable tính năng drag trên view

func dragInteraction(_ interaction: UIDragInteraction, itemsForBeginning session: UIDragSession) -> [UIDragItem] {
    guard let image = imageView.image else { return [] }

    let provider = NSItemProvider(object: image)
    let item = UIDragItem(itemProvider: provider)
    item.localObject = image
    
    /*
         Returning a non-empty array, as shown here, enables dragging. You
         can disable dragging by instead returning an empty array.
    */
    return [item]
}

Mỗi khi người dùng thực hiện thao tác drag (kéo), phương thức này sẽ được tự động gọi đến. Trong phần inplement phương thức, chúng ta cần return một hoặc một mảng các đối tượng có kiểu NSItemProvider.

Nhận dữ liệu từ một Drop Session

Để nhận dữ liệu từ một drag session, chúng ta cần implement ba phương thức delegate. Đầu tiên, ứng dụng của bạn có thể lựa chọn hoặc từ chối một số dữ liệu trong drop session tùy vào kiểu của dữ liệu đó; hay đơn giản hơn là tùy vào trạng thái hiện tại ứng dụng của bạn hoặc bất kỳ một yêu cầu nào khác. Trong đoạn code mẫu dưới đây, người viết giả sử ứng dụng này chỉ tiếp nhận các đối tượng có kiểu là kUTTypeImage .

func dropInteraction(_ interaction: UIDropInteraction, canHandle session: UIDropSession) -> Bool {
    return session.hasItemsConforming(toTypeIdentifiers: [kUTTypeImage as String]) && session.items.count == 1
}

Tiếp theo, việc cần làm là cho hệ thống biết các mà chúng ta sẽ xử lý các dữ liệu nhận được trong drop session, trong trường hợp này là copy chúng.

func dropInteraction(_ interaction: UIDropInteraction, sessionDidUpdate session: UIDropSession) -> UIDropProposal {
    let dropLocation = session.location(in: view)
    updateLayers(forDropLocation: dropLocation)

    let operation: UIDropOperation

    if imageView.frame.contains(dropLocation) {
        /*
             If you add in-app drag-and-drop support for the .move operation,
             you must write code to coordinate between the drag interaction
             delegate and the drop interaction delegate.
        */
        operation = session.localDragSession == nil ? .copy : .move
    } else {
        // Do not allow dropping outside of the image view.
        operation = .cancel
    }

    return UIDropProposal(operation: operation)
}

Cuối cùng, sau khi người dùng nhấc ngón tay khỏi màn hình, phương thức func dropInteraction(_ interaction: UIDropInteraction, performDrop session: UIDropSession) sẽ được gọi đến, và chúng ta tiến hành xử lý các dữ liệu nhận được và cập nhật chúng lên view

func dropInteraction(_ interaction: UIDropInteraction, performDrop session: UIDropSession) {
    // Consume drag items (in this example, of type UIImage).
    session.loadObjects(ofClass: UIImage.self) { imageItems in
        let images = imageItems as! [UIImage]

        /*
             If you do not employ the loadObjects(ofClass:completion:) convenience
             method of the UIDropSession class, which automatically employs
             the main thread, explicitly dispatch UI work to the main thread.
             For example, you can use `DispatchQueue.main.async` method.
        */
        self.imageView.image = images.first
    }

    // Perform additional UI updates as needed.
    let dropLocation = session.location(in: view)
    updateLayers(forDropLocation: dropLocation)
}

Ngoài ba phương thức trên, iOS 11 cũng hỗ trợ thêm một số phương thức khác để chúng ta có thể customize cách mà drag và drop hoạt trộng trên custom UIView của mình, độc giả có thể tìm hiểu thêm tại đây: Adopting Drag and Drop in a Custom View

Sample

Bạn có thể tải về sample project tại đây