iOSのドラッグ＆ドロップ（一歩先のドロップ編）

「[一歩先のドラッグ編](//qiita.com/hironytic/items/86d7df74ba08abb0b008)」と同様で、ドロップに関しても基本編で紹介した以上の細かい制御が行なえます。

ここではUIDropInteractionDelegateで定義された他のいくつかのメソッドについて、これを実装することで何ができるのか、どのように実装するのかを解説します。


## ドロップできないものをさっさと弾く

「[基本編](//qiita.com/hironytic/items/a8ffdf37cfa768d92ced)」ではdropInteraction(_:sessionDidUpdate:) でドロップの可否を判断していましたが、このメソッドはドラッグ位置が変わるたびに呼ばれます。
しかし、そもそもドラッグ中のアイテムに扱えるデータがまったくないのなら、位置が変わるたびに何度も判定するのは非効率ですね。

そこで、 `dropInteraction(_:canHandle:)` を実装しておくといいでしょう。

### dropInteraction(_:canHandle:)

このメソッドでは渡されたセッションを確認して、ドロップできるかどうかのBool値を返します。
ここでfalseを返せば、dropInteraction(_:sessionDidUpdate:)は呼ばれなくなります。

#### 実装例

```swift
func dropInteraction(_ interaction: UIDropInteraction,
                     canHandle session: UIDropSession) -> Bool {
    // 文字列を取り出せるものしかドロップできない
    return session.canLoadObjects(ofClass: NSString.self)
}
```


## ドロップ時のプレビューの制御

ドロップすると、デフォルトではドラッグ中のプレビューがその場に吸い込まれていくように、少しずつ小さくなりながら徐々に消えるアニメーションが行われます。この挙動は `dropInteraction(_:previewForDropping:withDefault:)` を実装すれば変更できます。

before
![previewForDropping1.gif](https://qiita-image-store.s3.amazonaws.com/0/33397/d9b3b3c1-d056-0c41-b60f-971e1d1ff50a.gif)

after
![previewForDropping2.gif](https://qiita-image-store.s3.amazonaws.com/0/33397/8f889f63-14be-93b3-610c-b811ee138aeb.gif)

### dropInteraction(_:previewForDropping:withDefault:)

このメソッドの実装方法は、「[一歩先のドラッグ編](//qiita.com/hironytic/items/86d7df74ba08abb0b008)」で紹介したドラッグキャンセル時のプレビュー（`UIDragInteractionDelegate` の `dragInteraction(_:previewForCancelling:withDefault:`)）とよく似ています。

第3引数に渡されるデフォルトのプレビューをそのまま返すと、単純にドロップ場所でしばらく留まるような挙動をします。
nilを返すと、標準の挙動（その場で少しずつ小さくなりながら徐々に消える）をします。

ここで全く新しいUITargetedDragPreviewを返してもいいですし、デフォルトプレビューの `retargetedPreview(with:)` を使ってターゲットだけを変更すれば、別の場所へ吸い込まれていくようにすることもできますし、ドロップ後に落としたアイテムが配置されるのであれば、その配置される位置へ移動させるのもよいでしょう。

#### 実装例

この例では、ドロップ対象の `droppableView` （`view` の子ビュー） の中心へ吸い込まれていくような挙動になります。

```swift
func dropInteraction(_ interaction: UIDropInteraction,
                     previewForDropping item: UIDragItem,
                     withDefault defaultPreview: UITargetedDragPreview) -> UITargetedDragPreview? {
    let target = UIDragPreviewTarget(container: view,
                                     center: droppableView.center,
                                     transform: CGAffineTransform(scaleX: 0.2, y: 0.2))
    return defaultPreview.retargetedPreview(with: target)
}
```

## ドロップ時に一緒にアニメーションさせる

ドロップ時のアニメーションに合わせて他のUI要素をアニメーションさせるには、 `dropInteraction(_:item:willAnimateDropWith:)` メソッドを実装します。これはドロップのアニメーションが開始する直前に呼ばれます。

また、ドロップのアニメーションが終わったあとで `dropInteraction(_:concludeDrop:)` が呼ばれます。このタイミングでアニメーションさせたものを元に戻したりと、追加の処理を行うことができます。

![willAnimateDropWith.gif](https://qiita-image-store.s3.amazonaws.com/0/33397/a69d9f87-b93c-1a74-e7be-3ca50e08657e.gif)

### dropInteraction(_:item:willAnimateDropWith:)

ドロップのアニメーションが開始する直前に呼ばれます。
第2引数に渡されるUIDragAnimatingオブジェクトを使って、ドロップ時に一緒に行うアニメーションを設定できます。

#### 実装例

この例では、 `dropIcon` をドロップ対象の `droppableView` の中心へ移動しつつ、拡大します。

```swift
func dropInteraction(_ interaction: UIDropInteraction,
                     item: UIDragItem,
                     willAnimateDropWith animator: UIDragAnimating) {
    animator.addAnimations {
        let offsetX = self.droppableView.bounds.width / 2 - self.dropIcon.center.x
        let offsetY = self.droppableView.bounds.height / 2 - self.dropIcon.center.y
        self.dropIcon.transform = CGAffineTransform(translationX: offsetX, y: offsetY)
                                  .scaledBy(x: 3, y: 3)
    }
}
```

### dropInteraction(_:concludeDrop:)

ドロップのアニメーションが終わったときに呼ばれます。

#### 実装例

`dropIcon` を元の位置に戻しています。

```swift
func dropInteraction(_ interaction: UIDropInteraction,
                     concludeDrop session: UIDropSession) {
    dropIcon.transform = CGAffineTransform.identity
}
```


## See Also
- [iOSのドラッグ＆ドロップ（基本編）](//qiita.com/hironytic/items/a8ffdf37cfa768d92ced)
- [iOSのドラッグ＆ドロップ（一歩先のドラッグ編）](//qiita.com/hironytic/items/86d7df74ba08abb0b008)