5
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

iOSAdvent Calendar 2021

Day 19

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

Posted at

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

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

##今回作るもの
Swiftでドラッグ&ドロップを実装しようと調べると、この記事この公式ドキュメントが見つかると思う。これらは大変素晴らしいが、初心者の私には難しかった。しかし、ドラッグ&ドロップが何としても必要。。。そこで思いついたのが今から説明するドラッグ&ドロップもどきである。上で説明したドラッグ&ドロップと比較すると以下のようになる。

1. 移動させたいものをタップする。
2. 同じ!
3. 同じ!

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

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

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メソッド内で行われている。このメソッドはタッチイベントが発生した時に呼び出される。タッチイベントに関わる代表的なメソッドとして以下の3つがある。

func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?)
func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?)
func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?)

この3つの中からtouchesMovedを採用する理由は2点ある。
1点目は、タッチした指の動きに合わせてドラッグ対象が動くという処理を書く必要があるからだ。上記3つは順番にタッチした時タッチした指が動いた時タッチした指が離れた時に呼び出される。この中で「タッチした指が動いた時」というのが最も欲しいタイミングだ。
2点目は、UITouchクラスの仕様によるものだ。上記3つのメソッドの引数touchesはUITouchクラスのインスタンスを要素としたSet型だ。このUITouchクラスにはタッチした時間やタッチした位置を返す様々なタッチに関するプロパティやメソッドが備わっている。中でも注目して欲しいのがviewプロパティだ。このviewプロパティはタッチが起きたUIView型を返すのだが、この「タッチが起きた」というのが最初のタッチの時のことを表す。つまり、最初タッチした場所にはドラッグ対象のものがなかったが、そのまま指を離さずに動かした時にドラッグ対象のものに当たったとしてもそれはviewには入らない。あくまでも最初のタッチの時にドラッグ対象のUIViewがあれば入るというものだ。
以上2つの理由により、touchesMovedは呼び出されるタイミング、受け取れるものに置いてドラッグ&ドロップに非常に適したメソッドだと言える。
##ドラッグ&ドロップもどきの仕組み

let touchEvent = touches.first!

Setの要素UITouchを取得し、

let newx = touchEvent.location(in: self.view).x
let newy = touchEvent.location(in: self.view).y

現在の指の位置の座標を取得し、

link.center = CGPoint(x: newx,y: newy)

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

if touchEvent.view == link { link.center = CGPoint(x: newx,y: newy) }

ただ、ここで注意して欲しいのが

link.isUserInteractionEnabled = true

の1文を移動させたいものに入れることだ。isUserInteractionEnabledプロパティはユーザーイベントを無視するかどうかを設定するもので、デフォルトはfalseになっている。タッチイベントはこのユーザーイベントを通じて作られているため、ユーザーイベントを無視するとなるとタッチイベントが作られない。つまり、touchEvent.viewで移動させたいものを取得しようとしても出来なくなってしまうので必ず有効にする。
##ドラッグ&ドロップもどきで作ったアプリ
最後にこのドラッグ&ドロップもどきを使って作ったアプリを紹介する。これはセンサーライトのビジネスロジックを表したものだ。画像のように問題なく動くのが分かると思う。 GitHub
ezgif.com-gif-maker (1).gif
##最後に
初めてのQiita投稿、Swiftを学び始めてまだ1ヶ月ほどということもあり、間違ったことを書いてしまっている所もあると思います。その際は、コメントにてご指摘頂けたら嬉しいです。最後まで読んでくださりありがとうございました。

5
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?