2018年5月28日月曜日

複数のジェスチャーでViewをアフィン変換

やりたいこと

ピンチやローテートのジェスチャーを複数組み合わせてView(ImageView)の形を変更したい。
ジェスチャーは複数を一度に使えるように。

やり方

UIGestureRecognizerのdelegateを設定し、delegateメソッドで複数のGestureRecognizerの同時使用を許可。
複数のアフィン変換を組み合わせる処理をしてからViewに反映させる。
ジェスチャー(例ではpinchとrotate)使用中のtransformの状態と、終了時のtransformの状態をそれぞれ保存し、ジェスチャー開始時(再開時)は前回の続きから始めるようにする。

つまづいたとこ

Swift3あたりから書き方が変わったせいで、前回のtransformの状態から再開する方法、複数のアフィン変換を組み合わせるコードに迷った。

コード(Swift4)

class ViewController: UIViewController,UIGestureRecognizerDelegate {
    
    var myView = UIImageView()
    
    //ジェスチャーで変化中のtransformを保存
    var prevRotate = CGAffineTransform()
    var prevScale = CGAffineTransform()
    //ジェスチャー終了時のtransformを保存
    var prevEndRotate = CGAffineTransform()
    var prevEndScale = CGAffineTransform()
    

    override func viewDidLoad() {
        super.viewDidLoad()
        
        myView = UIImageView(frame: CGRect(x: 0, y: 0, width: 200, height: 200))
        myView.image = UIImage(named: "image")
        myView.transform = CGAffineTransform.identity //念のため変形を初期化
        myView.isUserInteractionEnabled = true //ユーザーインタラクションを許可
        myView.center = CGPoint(x: self.view.frame.midX, y: self.view.frame.midY)
        self.view.addSubview(myView)
        
        let rotate = UIRotationGestureRecognizer(target: self, action: #selector(ViewController.rotate))
        rotate.delegate = self
        myView.addGestureRecognizer(rotate)
        
        let pinch = UIPinchGestureRecognizer(target: self, action: #selector(ViewController.pinch))
        pinch.delegate = self
        myView.addGestureRecognizer(pinch)
        
        //アフィン変換の初期値設定しとかないとダメ
        prevRotate = myView.transform
        prevScale = myView.transform
        prevEndRotate = myView.transform
        prevEndScale = myView.transform
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }

    @objc func rotate(sender:UIRotationGestureRecognizer) {
        //前回のrotateの続きからrotateする
        let nowRotate = prevEndRotate.rotated(by: sender.rotation)
        //複数のアフィン変換を組み合わせる
        myView.transform = nowRotate.concatenating(prevScale)
        prevRotate = nowRotate

        //rotate終了時のtransform状態を保存
        if sender.state == .ended {
            prevEndRotate = nowRotate
        }
    }

    @objc func pinch(sender:UIPinchGestureRecognizer) {
        //前回のpinchの続きからscaleを変える
        let nowScale = prevEndScale.scaledBy(x: sender.scale, y: sender.scale)
        //複数のアフィン変換を組み合わせる
        myView.transform = nowScale.concatenating(prevRotate)
        prevScale = nowScale

        //pinch終了時のtransform状態を保存
        if sender.state == UIGestureRecognizerState.ended {
            prevEndScale = nowScale
        }
    }
    
    //複数のrecognizerの同時使用許可
    func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
        return true
    }
}

参考サイト

0 件のコメント:

コメントを投稿