【iOS】UIViewをタッチして移動させる

投稿者: | 2016年10月24日
使用環境
Xcode:8.0
Swift:3.0

UIViewをタッチして移動させてみよう。完成イメージは以下の通りである。
20161022_viewtouchmove
※クリックでGIFアニメーションを表示

UIViewControllerが用意したデフォルトのUIViewにサブビューを追加し、赤いビューと青いビューをタッチして動かせるようになっている。

準備

Xcodeを起動したら「Single View Application」テンプレートで新しいプロジェクトを作成する。

実装

プロジェクト内にあるViewController.swiftに処理を書いていく。
以下にコードを示す。

ViewController.swift

import UIKit

// ユーティリティメソッド CGPoint同士の足し算を+で書けるようにする
func -(_ left:CGPoint, _ right:CGPoint)->CGPoint{
    return CGPoint(x:left.x - right.x, y:left.y - right.y)
}
// ユーティリティメソッド CGPoint同士の引き算を-で書けるようにする
func +(_ left:CGPoint, _ right:CGPoint)->CGPoint{
    return CGPoint(x:left.x + right.x, y:left.y + right.y)
}

class ViewController: UIViewController {

    // タッチ開始時のUIViewのorigin
    var orgOrigin: CGPoint!
    // タッチ開始時の親ビュー上のタッチ位置
    var orgParentPoint : CGPoint!
    
    // Viewのパンジェスチャーに反応し、処理するためのメソッド
    func handlePanGesture(sender: UIPanGestureRecognizer){
        switch sender.state {
        case UIGestureRecognizerState.began:
            // タッチ開始:タッチされたビューのoriginと親ビュー上のタッチ位置を記録しておく
            orgOrigin = sender.view?.frame.origin
            orgParentPoint = sender.translation(in: self.view)
            break
        case UIGestureRecognizerState.changed:
            // 現在の親ビュー上でのタッチ位置を求める
            let newParentPoint = sender.translation(in: self.view)
            // パンジャスチャの継続:タッチ開始時のビューのoriginにタッチ開始からの移動量を加算する
            sender.view?.frame.origin = orgOrigin + newParentPoint - orgParentPoint
            break
        default:
            break
        }
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // ビューを2つ作成し、Subviewとして追加する
        // !!注意!!
        // UIGestureRecognizerのインスタンスは複数のViewで使いまわせないので、各View毎に作成する
        let view1 = UIView(frame: CGRect(x:10,y:20,width:100,height:50))
        view1.backgroundColor = UIColor.red
        view1.addGestureRecognizer(
            UIPanGestureRecognizer(target:self, action:#selector(handlePanGesture)))
        self.view.addSubview(view1)
        
        let view2 = UIView(frame: CGRect(x:10,y:100,width:100,height:50))
        view2.backgroundColor = UIColor.blue
        view2.addGestureRecognizer(
            UIPanGestureRecognizer(target:self, action:#selector(handlePanGesture)))
        self.view.addSubview(view2)
        
    }
}

アプリ起動時にまず呼ばれるのは、38行目からのviewDidLoadである。
ここでは背景色が赤と青のビューを一つずつ作成し、親にサブビューとして追加している。

これらのビューでタッチイベントを受けられるようにするため、UIGestureRecognizerの一種であるUIPanGestureRecognizerをサブビューに追加する。これはパンジェスチャ、すなわちタッチ後にドラッグするような操作を認識するためのRecognizerである。
ここで注意が必要なのはGestureRecognizerのインスタンスは複数のUIViewで使い回せない、ということである。下のようについ書きたくなるのだが、このように書くとタッチが有効になるのは最後に追加したViewの方のみとなってしまう。

(GestureRecognizer追加時の失敗例)

let recognizer = UIPanGestureRecognizer(target: self, action: #selector(handlePanGesture))
let view1 = UIView()
view1.addGestureRecognizer(recognizer)

// こちらのViewだけがタッチを受け取る
let view2 = UIView()
view2.addGestureRecognizer(recognizer)

タッチ時の処理を扱うのは20行目からのhandlePanGestureメソッドである。ここで行っているのは以下の処理である。

  • タッチ開始時:タッチされたビューの位置(origin)と親ビュー上のタッチ位置を記録しておく。
  • タッチ移動時:タッチ開始時点のビューの位置(origin)に、タッチ開始点から現在のタッチまでの移動量を足してビューの新しい位置(origin)とする。

タッチの移動量は
「現在の親ビューにおけるタッチ位置」-「タッチ開始時点の親ビューにおける位置」
で求めることができる。サブビューの位置はタッチの移動によってどんどん変わっていくから、計算には親ビュー上のタッチ位置の値を使うことがポイントである。

31行目でCGPointに対して+や-の演算子を使っている。本来はCGPointにこのような演算子はないのだが、4行目、6行目でユーティリティメソッドを用意しているのでこのように書くことができる。これは、ソースコードを見やすくするための工夫である。もし、普通に書くならば以下のようになるだろう。

sender.view?.frame.origin = 
    CGPoint(x:(orgOrigin.x + newParentPoint.x - orgParentPoint.x,
            y:(orgOrigin.y + newParentPoint.y - orgParentPoint.y ))

CGPointのユーティリティメソッドについて、さらに詳しく知りたいときは以下の記事が参考になる。
http://qiita.com/koher/items/7b4f23d749d973d409c9

さて、今回の内容は以前にAndroidでもやったことがある。Androidでの実装に興味がある方は以下の記事も参考にしてほしい。
https://zawapro.com/?p=305

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です