見出し画像

【徒然iOS】気ままにUIKit69〜Gesture Recognizerの合わせ技 部品の拡大縮小、回転、移動が1ドラッグで出来る〜

概要

このマガジンは四十を過ぎたおっさんが、

を参考にStoryboardでiOSアプリを完全に趣味で楽しんでいるだけな記事を気ままに上げてます。

今回

をハイ、レッツゴ🕺

前準備

念の為、

  1. バックアップ

  2. 新しいクラス

  3. ビューコントローラの追加

  4. イニシャルビューの変更

をいつも通りやってから本題へ💃

本題

どうやら、

の続きみたいだから、

のローテ&ピンチを作ってから、手順を進める〜〜〜

ハイ、準備完了🕺

⒈パンリコグナイザーをラベルに追加して、アウトレット接続とアクション接続

選んで配置〜〜〜
アクション接続
アウトレット接続

⒉コード組み込み

//
//  ViewController.swift
//
import UIKit
class ViewController: UIViewController, UIGestureRecognizerDelegate {
    @IBOutlet weak var testLabel: UILabel!
    @IBOutlet var rotationRecognizer: UIRotationGestureRecognizer!
    @IBOutlet var pinchRecognizer: UIPinchGestureRecognizer!
    @IBOutlet var panRecognizer: UIPanGestureRecognizer!
    //ドラッグ終了時のアフィン変換
    var prevEndPinch:CGAffineTransform = CGAffineTransform()
    var prevEndRotate:CGAffineTransform = CGAffineTransform()
    //ドラッグ中の前回アフィン変換
    var prevPinch:CGAffineTransform = CGAffineTransform()
    var prevRotate:CGAffineTransform = CGAffineTransform()
    //最初からあるメソッド
    override func viewDidLoad() {
        super.viewDidLoad()
        //デリゲート先に自分を設定する。
        rotationRecognizer.delegate = self
        pinchRecognizer.delegate = self
        panRecognizer.delegate = self
        //アフィン変換の初期値を設定する。
        prevEndPinch = testLabel.transform
        prevEndRotate = testLabel.transform
        prevPinch = testLabel.transform
        prevRotate = testLabel.transform
    }
    //ドラッグ時の呼び出しメソッド
    @IBAction func panLabel(sender: UIPanGestureRecognizer) {
        //移動量を取得する。
        let move:CGPoint = sender.translationInView(view)
        //ドラッグした部品の座標に移動量を加算する。
        sender.view!.center.x += move.x
        sender.view!.center.y += move.y
        //移動量を0にする。
        sender.setTranslation(CGPointZero, inView:view)
    }
    //ピンチ時の呼び出しメソッド
    @IBAction func pinchLabel(sender: UIPinchGestureRecognizer) {
        //前回ドラッグ終了時の拡大縮小を引き継いだアフィン変換を行う。
        let nowPinch = CGAffineTransformScale(prevEndPinch, sender.scale, sender.scale)
        //拡大縮小と回転のアフィン変換を合わせたものをラベルに登録する。
        testLabel.transform = CGAffineTransformConcat(prevRotate, nowPinch)
        //今回の拡大縮小のアフィン変換をクラス変数に保存する。
        prevPinch = nowPinch
        if(sender.state == UIGestureRecognizerState.Ended) {
            //ドラッグ終了時の拡大終了のアフィン変換をクラス変数に保存する。
            prevEndPinch = nowPinch
        }
    }
    //回転時の呼び出しメソッド
    @IBAction func rotateLabel(sender: UIRotationGestureRecognizer) {
        //前回ドラッグ終了時の回転を引き継いだアフィン変換を行う。
        let nowRotate = CGAffineTransformRotate(prevEndRotate, sender.rotation)
        //拡大縮小と回転のアフィン変換を合わせたものをラベルに登録する。
        testLabel.transform = CGAffineTransformConcat(prevPinch, nowRotate)
        //今回の回転のアフィン変換をクラス変数に保存する。
        prevRotate = nowRotate
        if(sender.state == UIGestureRecognizerState.Ended) {
            //ドラッグ終了時の回転のアフィン変換をクラス変数に保存する。
            prevEndRotate = nowRotate
        }
    }
    //リコグナイザーの同時検知を許可するメソッド
    func gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWithGestureRecognizer otherGestureRecognizer: UIGestureRecognizer) -> Bool {
        return true
    }
}

を参考に👀コード組み換え〜〜〜

今回のコード(並行移動なし)

class PinRotePanGestureViewController: UIViewController, UIGestureRecognizerDelegate {
    @IBOutlet weak var myLabel: UILabel!
    @IBOutlet var myRotateGesture: UIRotationGestureRecognizer!
    @IBOutlet var myPinchGesture: UIPinchGestureRecognizer!
    @IBOutlet var myPangesture: UIPanGestureRecognizer!
    
    //ドラッグ終了時のアフィン変換
    var prevEndPinch:CGAffineTransform = CGAffineTransform()
    var prevEndRotate:CGAffineTransform = CGAffineTransform()
    //ドラッグ中の前回アフィン変換
    var prevPinch:CGAffineTransform = CGAffineTransform()
    var prevRotate:CGAffineTransform = CGAffineTransform()
    //最初からあるメソッド
    override func viewDidLoad() {
        super.viewDidLoad()
        //デリゲート先に自分を設定する。
        myRotateGesture.delegate = self
        myPinchGesture.delegate = self
        myPangesture.delegate = self
        //アフィン変換の初期値を設定する。
        prevEndPinch = myLabel.transform
        prevEndRotate = myLabel.transform
        prevPinch = myLabel.transform
        prevRotate = myLabel.transform
    }
    //ピンチ時の呼び出しメソッド
    @IBAction func myPinch(_ sender: UIPinchGestureRecognizer) {
        //前回ドラッグ終了時の拡大縮小を引き継いだアフィン変換を行う。
        let nowPinch = CGAffineTransformScale(prevEndPinch, sender.scale, sender.scale)
        //拡大縮小と回転のアフィン変換を合わせたものをラベルに登録する。
        myLabel.transform = CGAffineTransformConcat(prevRotate, nowPinch)
        //今回の拡大縮小のアフィン変換をクラス変数に保存する。
        prevPinch = nowPinch
        if(sender.state == UIGestureRecognizer.State.ended) {
            //ドラッグ終了時の拡大終了のアフィン変換をクラス変数に保存する。
            prevEndPinch = nowPinch
        }
    }
    //回転時の呼び出しメソッド
    @IBAction func myRote(_ sender: UIRotationGestureRecognizer) {
        //前回ドラッグ終了時の回転を引き継いだアフィン変換を行う。
        let nowRotate = CGAffineTransformRotate(prevEndRotate, sender.rotation)
        //拡大縮小と回転のアフィン変換を合わせたものをラベルに登録する。
        myLabel.transform = CGAffineTransformConcat(prevPinch, nowRotate)
        //今回の回転のアフィン変換をクラス変数に保存する。
        prevRotate = nowRotate
        if(sender.state == UIGestureRecognizer.State.ended) {
            //ドラッグ終了時の回転のアフィン変換をクラス変数に保存する。
            prevEndRotate = nowRotate
        }
    }
    //ドラッグ時の呼び出しメソッド
    @IBAction func myPan(_ sender: UIPanGestureRecognizer) {
        //移動量を取得する。
        let move:CGPoint = sender.translation(in: view)
        //ドラッグした部品の座標に移動量を加算する。
        sender.view!.center.x += move.x
        sender.view!.center.y += move.y
        //移動量を0にする。
        sender.setTranslation(CGPointZero, in:view)
    }
    //リコグナイザーの同時検知を許可するメソッド
    func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
        return true
    }
}

⒊シミュレータを実行

出来た〜〜〜🕺

並行移動にアフィン変換を使う

部品の座標に移動量を加算する方法をとったので、拡大縮小と回転のアフィン変換を使ったやり方と手法が異なる。

アフィン変換には並行移動もあるので、
拡大縮小と回転の手法と合わせてすべてアフィン変換で実装できる。

らしい👀

ちょっとここも毛色が違うので、比較できるように前準備の手順で新規ビューを作ってからやる〜〜〜

てな感じで〜〜〜

⒈コードを書き換え

//
//  ViewController.swift
//
import UIKit
class ViewController: UIViewController, UIGestureRecognizerDelegate {
    @IBOutlet weak var testLabel: UILabel!
    @IBOutlet var rotationRecognizer: UIRotationGestureRecognizer!
    @IBOutlet var pinchRecognizer: UIPinchGestureRecognizer!
    @IBOutlet var panRecognizer: UIPanGestureRecognizer!
    //ドラッグ終了時のアフィン変換
    var prevEndPinch:CGAffineTransform = CGAffineTransform()
    var prevEndRotate:CGAffineTransform = CGAffineTransform()
    var prevEndMove:CGAffineTransform = CGAffineTransform()
    //ドラッグ中の前回アフィン変換
    var prevPinch:CGAffineTransform = CGAffineTransform()
    var prevRotate:CGAffineTransform = CGAffineTransform()
    var prevMove:CGAffineTransform = CGAffineTransform()
    //最初からあるメソッド
    override func viewDidLoad() {
        super.viewDidLoad()
        //デリゲート先に自分を設定する。
        rotationRecognizer.delegate = self
        pinchRecognizer.delegate = self
        panRecognizer.delegate = self
        //ドラッグするには2本の指が必要にする。
        panRecognizer.minimumNumberOfTouches = 2
        //アフィン変換の初期値を設定する。
        prevEndPinch = testLabel.transform
        prevEndRotate = testLabel.transform
        prevEndMove = testLabel.transform
        prevPinch = testLabel.transform
        prevRotate = testLabel.transform
        prevMove = testLabel.transform
    }
    //ドラッグ時の呼び出しメソッド
    @IBAction func panLabel(sender: UIPanGestureRecognizer) {
        //移動量を取得する。
        let move:CGPoint = sender.translationInView(view)
        //前回ドラッグ終了時の並行移動を引き継いだアフィン変換を作る。
        let nowMove = CGAffineTransformTranslate(prevEndMove, move.x, move.y)
        //拡大縮小と並行移動のアフィン変換を合わせる。
        let moveAndPinch = CGAffineTransformConcat(prevPinch,nowMove)
        //さらに回転のアフィン変換と合わせたものをラベルに登録する。
        testLabel.transform = CGAffineTransformConcat(prevRotate,moveAndPinch)
        //今回の並行移動のアフィン変換をクラス変数に保存する。
        prevMove = nowMove
        if(sender.state == UIGestureRecognizerState.Ended) {
            //ドラッグ終了時の並行移動のアフィン変換をクラス変数に保存する。
            prevEndMove = nowMove
        }
    }
    //ピンチ時の呼び出しメソッド
    @IBAction func pinchLabel(sender: UIPinchGestureRecognizer) {
        //前回ドラッグ終了時の拡大縮小を引き継いだアフィン変換を作る。
        let nowPinch = CGAffineTransformScale(prevEndPinch, sender.scale, sender.scale)
        //拡大縮小と並行移動のアフィン変換を合わせる。
        let moveAndPinch = CGAffineTransformConcat(nowPinch,prevMove)
        //さらに回転のアフィン変換と合わせたものをラベルに登録する。
        testLabel.transform = CGAffineTransformConcat(prevRotate,moveAndPinch)
        //今回の拡大縮小のアフィン変換をクラス変数に保存する。
        prevPinch = nowPinch
        if(sender.state == UIGestureRecognizerState.Ended) {
            //ドラッグ終了時の拡大縮小のアフィン変換をクラス変数に保存する。
            prevEndPinch = nowPinch
        }
    }
    //回転時の呼び出しメソッド
    @IBAction func rotateLabel(sender: UIRotationGestureRecognizer) {
        //前回ドラッグ終了時の回転を引き継いだアフィン変換を作る。
        let nowRotate = CGAffineTransformRotate(prevEndRotate, sender.rotation)
        //拡大縮小と並行移動のアフィン変換を合わせる。
        let moveAndPinch = CGAffineTransformConcat(prevPinch, prevMove)
        //さらに回転のアフィン変換と合わせたものをラベルに登録する。
        testLabel.transform = CGAffineTransformConcat(nowRotate,moveAndPinch)
        //今回の回転のアフィン変換をクラス変数に保存する。
        prevRotate = nowRotate
        if(sender.state == UIGestureRecognizerState.Ended) {
            //ドラッグ終了時の回転のアフィン変換をクラス変数に保存する。
            prevEndRotate = nowRotate
        }
    }
    //リコグナイザーの同時検知を許可するメソッド
    func gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWithGestureRecognizer otherGestureRecognizer: UIGestureRecognizer) -> Bool {
        return true
    }
}

を参考に〜〜〜コードを書き換え🕺

class PinchRotatePanViewController: UIViewController, UIGestureRecognizerDelegate {
    
    @IBOutlet weak var myLabel: UILabel!
    @IBOutlet var myPinchGesture: UIPinchGestureRecognizer!
    @IBOutlet var myPanGesture: UIPanGestureRecognizer!
    @IBOutlet var myRotateGesture: UIRotationGestureRecognizer!
    //ドラッグ終了時のアフィン変換
    var prevEndPinch:CGAffineTransform = CGAffineTransform()
    var prevEndRotate:CGAffineTransform = CGAffineTransform()
    var prevEndMove:CGAffineTransform = CGAffineTransform()
    //ドラッグ中の前回アフィン変換
    var prevPinch:CGAffineTransform = CGAffineTransform()
    var prevRotate:CGAffineTransform = CGAffineTransform()
    var prevMove:CGAffineTransform = CGAffineTransform()
    //最初からあるメソッド
    override func viewDidLoad() {
        super.viewDidLoad()
        //デリゲート先に自分を設定する。
        myRotateGesture.delegate = self
        myPinchGesture.delegate = self
        myPanGesture.delegate = self
        //ドラッグするには2本の指が必要にする。
        myPanGesture.minimumNumberOfTouches = 2
        //アフィン変換の初期値を設定する。
        prevEndPinch = myLabel.transform
        prevEndRotate = myLabel.transform
        prevEndMove = myLabel.transform
        prevPinch = myLabel.transform
        prevRotate = myLabel.transform
        prevMove = myLabel.transform
    }
    //ピンチ時の呼び出しメソッド
    @IBAction func myPinch(_ sender: UIPinchGestureRecognizer) {
        //前回ドラッグ終了時の拡大縮小を引き継いだアフィン変換を作る。
        let nowPinch = CGAffineTransformScale(prevEndPinch, sender.scale, sender.scale)
        //拡大縮小と並行移動のアフィン変換を合わせる。
        let moveAndPinch = CGAffineTransformConcat(nowPinch,prevMove)
        //さらに回転のアフィン変換と合わせたものをラベルに登録する。
        myLabel.transform = CGAffineTransformConcat(prevRotate,moveAndPinch)
        //今回の拡大縮小のアフィン変換をクラス変数に保存する。
        prevPinch = nowPinch
        if(sender.state == UIGestureRecognizer.State.ended) {
            //ドラッグ終了時の拡大縮小のアフィン変換をクラス変数に保存する。
            prevEndPinch = nowPinch
        }
    }
    //回転時の呼び出しメソッド
    @IBAction func myRotate(_ sender: UIRotationGestureRecognizer) {
        //前回ドラッグ終了時の回転を引き継いだアフィン変換を作る。
        let nowRotate = CGAffineTransformRotate(prevEndRotate, sender.rotation)
        //拡大縮小と並行移動のアフィン変換を合わせる。
        let moveAndPinch = CGAffineTransformConcat(prevPinch, prevMove)
        //さらに回転のアフィン変換と合わせたものをラベルに登録する。
        myLabel.transform = CGAffineTransformConcat(nowRotate,moveAndPinch)
        //今回の回転のアフィン変換をクラス変数に保存する。
        prevRotate = nowRotate
        if(sender.state == UIGestureRecognizer.State.ended) {
            //ドラッグ終了時の回転のアフィン変換をクラス変数に保存する。
            prevEndRotate = nowRotate
        }
    }
    //ドラッグ時の呼び出しメソッド
    @IBAction func myPan(_ sender: UIPanGestureRecognizer) {
        //移動量を取得する。
        let move:CGPoint = sender.translation(in: view)
        //前回ドラッグ終了時の並行移動を引き継いだアフィン変換を作る。
        let nowMove = CGAffineTransformTranslate(prevEndMove, move.x, move.y)
        //拡大縮小と並行移動のアフィン変換を合わせる。
        let moveAndPinch = CGAffineTransformConcat(prevPinch,nowMove)
        //さらに回転のアフィン変換と合わせたものをラベルに登録する。
        myLabel.transform = CGAffineTransformConcat(prevRotate,moveAndPinch)
        //今回の並行移動のアフィン変換をクラス変数に保存する。
        prevMove = nowMove
        if(sender.state == UIGestureRecognizer.State.ended) {
            //ドラッグ終了時の並行移動のアフィン変換をクラス変数に保存する。
            prevEndMove = nowMove
        }
    }
    //リコグナイザーの同時検知を許可するメソッド
    func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
        return true
    }
}

⒉シミュレータを実行

シミュレータだと2本指制御がうまく反応してくれない💦
ので、実機で確認👀

確かに移動はできたけど、
別に2本指じゃなくても移動できるぞ👀
うーむ。。。。

とりあえず、期待値は満たしているし、余計なコードはない方がいいので、

        //ドラッグするには2本の指が必要にする。
        myPanGesture.minimumNumberOfTouches = 2

を削って、、、

今回のコード(並行移動あり)

class PinchRotatePanViewController: UIViewController, UIGestureRecognizerDelegate {
    
    @IBOutlet weak var myLabel: UILabel!
    @IBOutlet var myPinchGesture: UIPinchGestureRecognizer!
    @IBOutlet var myPanGesture: UIPanGestureRecognizer!
    @IBOutlet var myRotateGesture: UIRotationGestureRecognizer!
    //ドラッグ終了時のアフィン変換
    var prevEndPinch:CGAffineTransform = CGAffineTransform()
    var prevEndRotate:CGAffineTransform = CGAffineTransform()
    var prevEndMove:CGAffineTransform = CGAffineTransform()
    //ドラッグ中の前回アフィン変換
    var prevPinch:CGAffineTransform = CGAffineTransform()
    var prevRotate:CGAffineTransform = CGAffineTransform()
    var prevMove:CGAffineTransform = CGAffineTransform()
    //最初からあるメソッド
    override func viewDidLoad() {
        super.viewDidLoad()
        //デリゲート先に自分を設定する。
        myRotateGesture.delegate = self
        myPinchGesture.delegate = self
        myPanGesture.delegate = self
        //アフィン変換の初期値を設定する。
        prevEndPinch = myLabel.transform
        prevEndRotate = myLabel.transform
        prevEndMove = myLabel.transform
        prevPinch = myLabel.transform
        prevRotate = myLabel.transform
        prevMove = myLabel.transform
    }
    //ピンチ時の呼び出しメソッド
    @IBAction func myPinch(_ sender: UIPinchGestureRecognizer) {
        //前回ドラッグ終了時の拡大縮小を引き継いだアフィン変換を作る。
        let nowPinch = CGAffineTransformScale(prevEndPinch, sender.scale, sender.scale)
        //拡大縮小と並行移動のアフィン変換を合わせる。
        let moveAndPinch = CGAffineTransformConcat(nowPinch,prevMove)
        //さらに回転のアフィン変換と合わせたものをラベルに登録する。
        myLabel.transform = CGAffineTransformConcat(prevRotate,moveAndPinch)
        //今回の拡大縮小のアフィン変換をクラス変数に保存する。
        prevPinch = nowPinch
        if(sender.state == UIGestureRecognizer.State.ended) {
            //ドラッグ終了時の拡大縮小のアフィン変換をクラス変数に保存する。
            prevEndPinch = nowPinch
        }
    }
    //回転時の呼び出しメソッド
    @IBAction func myRotate(_ sender: UIRotationGestureRecognizer) {
        //前回ドラッグ終了時の回転を引き継いだアフィン変換を作る。
        let nowRotate = CGAffineTransformRotate(prevEndRotate, sender.rotation)
        //拡大縮小と並行移動のアフィン変換を合わせる。
        let moveAndPinch = CGAffineTransformConcat(prevPinch, prevMove)
        //さらに回転のアフィン変換と合わせたものをラベルに登録する。
        myLabel.transform = CGAffineTransformConcat(nowRotate,moveAndPinch)
        //今回の回転のアフィン変換をクラス変数に保存する。
        prevRotate = nowRotate
        if(sender.state == UIGestureRecognizer.State.ended) {
            //ドラッグ終了時の回転のアフィン変換をクラス変数に保存する。
            prevEndRotate = nowRotate
        }
    }
    //ドラッグ時の呼び出しメソッド
    @IBAction func myPan(_ sender: UIPanGestureRecognizer) {
        //移動量を取得する。
        let move:CGPoint = sender.translation(in: view)
        //前回ドラッグ終了時の並行移動を引き継いだアフィン変換を作る。
        let nowMove = CGAffineTransformTranslate(prevEndMove, move.x, move.y)
        //拡大縮小と並行移動のアフィン変換を合わせる。
        let moveAndPinch = CGAffineTransformConcat(prevPinch,nowMove)
        //さらに回転のアフィン変換と合わせたものをラベルに登録する。
        myLabel.transform = CGAffineTransformConcat(prevRotate,moveAndPinch)
        //今回の並行移動のアフィン変換をクラス変数に保存する。
        prevMove = nowMove
        if(sender.state == UIGestureRecognizer.State.ended) {
            //ドラッグ終了時の並行移動のアフィン変換をクラス変数に保存する。
            prevEndMove = nowMove
        }
    }
    //リコグナイザーの同時検知を許可するメソッド
    func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
        return true
    }
}

に修正〜〜〜〜

ブラッシュアップ

今回は、特になし

Apple公式

これまでの記事で出したまとめ的な感じになるけど、、、💦

さて、次回は

をレッツゴする🕺

いいなと思ったら応援しよう!