【Swift】ドラッグ＆ドロップもどきを作ろう！

##ドラッグ&ドロップとは？
ユーザー視点からは次のような動作をするものである。

１. 移動させたいものを長押ししすると、それが半透明に浮き上がる。
２. 指を画面から離さないまま動かすと、それが指の動きについていく。
３. 指を離すと指を離した場所への移動が完了する。

##今回作るもの
Swiftでドラッグ＆ドロップを実装しようと調べると、[この記事](https://qiita.com/hironytic/items/a8ffdf37cfa768d92ced)や[この公式ドキュメント](https://developer.apple.com/documentation/uikit/drag_and_drop/adopting_drag_and_drop_in_a_custom_view)が見つかると思う。これらは大変素晴らしいが、初心者の私には難しかった。しかし、ドラッグ&ドロップが何としても必要。。。そこで思いついたのが今から説明するドラッグ&ドロップもどきである。上で説明したドラッグ&ドロップと比較すると以下のようになる。

１. 移動させたいものをタップする。
２. 同じ！
３. 同じ！

そう、最初のドラッグの対象を確定するところ以外は全て同じなのである！なので、実際のアプリ開発でも活用することはできるのではないかと思い記事にさせて頂いた。

##全コード
最初に全コードを記載しそれを解説する。

```swift
import UIKit
class ViewController: UIViewController {
    let link = UIImageView()
    override func viewDidLoad() {
        super.viewDidLoad()
        link.image = UIImage(named: "Link")
        link.frame = CGRect(x: 0, y: 0, width: 128, height: 128)
        link.center = CGPoint(x: view.frame.width/2, y: view.frame.height/2)
        link.isUserInteractionEnabled = true
        self.view.addSubview(link)
    }
    override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
        let touchEvent = touches.first!
        let newx = touchEvent.location(in: self.view).x
        let newy = touchEvent.location(in: self.view).y
        if touchEvent.view == link { link.center = CGPoint(x: newx,y: newy) }
    }
}
```
##なぜtouchesMoved?
ドラッグ&ドロップもどきの処理は、touchesMovedメソッド内で行われている。このメソッドは[タッチイベント](https://developer.apple.com/documentation/webkitjs/touchevent/)が発生した時に呼び出される。タッチイベントに関わる代表的なメソッドとして以下の３つがある。

```swift
func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?)
func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?)
func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?)
```
この３つの中からtouchesMovedを採用する理由は2点ある。
１点目は、タッチした指の動きに合わせてドラッグ対象が動くという処理を書く必要があるからだ。上記３つは順番に`タッチした時`、`タッチした指が動いた時`、`タッチした指が離れた時`に呼び出される。この中で「タッチした指が動いた時」というのが最も欲しいタイミングだ。
２点目は、[UITouchクラス](https://developer.apple.com/documentation/uikit/uitouch)の仕様によるものだ。上記３つのメソッドの引数touchesはUITouchクラスのインスタンスを要素としたSet型だ。このUITouchクラスにはタッチした時間やタッチした位置を返す様々なタッチに関するプロパティやメソッドが備わっている。中でも注目して欲しいのがviewプロパティだ。このviewプロパティはタッチが起きたUIView型を返すのだが、この「タッチが起きた」というのが最初のタッチの時のことを表す。つまり、最初タッチした場所にはドラッグ対象のものがなかったが、そのまま指を離さずに動かした時にドラッグ対象のものに当たったとしてもそれはviewには入らない。あくまでも最初のタッチの時にドラッグ対象のUIViewがあれば入るというものだ。
以上２つの理由により、touchesMovedは呼び出されるタイミング、受け取れるものに置いてドラッグ&ドロップに非常に適したメソッドだと言える。
##ドラッグ&ドロップもどきの仕組み

```swift
let touchEvent = touches.first!
```
Set<UITouch>の要素UITouchを取得し、

```swift
let newx = touchEvent.location(in: self.view).x
let newy = touchEvent.location(in: self.view).y
```
現在の指の位置の座標を取得し、

```swift
link.center = CGPoint(x: newx,y: newy)
```
移動させたいものの座標に入れてやれば、指の動きについていき移動ができるドラッグ&ドロップもどきが完成！とはいかない。なぜならこのままだと、指を動かした場所どこにでもついてくるため、「移動させたいものをタップ」してというドラッグ&ドロップもどきの要件と異なってしまう。そこで出てくるのが前項で説明したviewプロパティだ。このviewプロパティは最初のタッチの時にUIViewがあれば入るので、このviewと移動させたいものが一致してれば座標に入れると書き換えてやれば要件を満たす。

```swift
if touchEvent.view == link { link.center = CGPoint(x: newx,y: newy) }
```
ただ、ここで注意して欲しいのが

```swift
link.isUserInteractionEnabled = true
```
の１文を移動させたいものに入れることだ。[isUserInteractionEnabledプロパティ](https://developer.apple.com/documentation/uikit/uiview/1622577-isuserinteractionenabled/)はユーザーイベントを無視するかどうかを設定するもので、デフォルトはfalseになっている。タッチイベントはこのユーザーイベントを通じて作られているため、ユーザーイベントを無視するとなるとタッチイベントが作られない。つまり、touchEvent.viewで移動させたいものを取得しようとしても出来なくなってしまうので必ず有効にする。
##ドラッグ&ドロップもどきで作ったアプリ
最後にこのドラッグ&ドロップもどきを使って作ったアプリを紹介する。これはセンサーライトのビジネスロジックを表したものだ。画像のように問題なく動くのが分かると思う。 [GitHub](https://github.com/YukiMaitani/SensorLight)
![ezgif.com-gif-maker (1).gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1323645/b53bf913-d855-87a1-4d49-ce4d8172a744.gif)
##最後に
初めてのQiita投稿、Swiftを学び始めてまだ１ヶ月ほどということもあり、間違ったことを書いてしまっている所もあると思います。その際は、コメントにてご指摘頂けたら嬉しいです。最後まで読んでくださりありがとうございました。