UIViewで画像グリグリ(Swift5)

## 概要
初めまして
最近はXCodeでの開発を主でやっています。
開発を始めて長くはありませんが、あれやこれや試行錯誤しながら調べたことをどんどん忘れていくのが勿体無いので記録を残そうかと思います。
よくこんなことしたい！に対応するサンプルコードを探すんですがまるっとサンプルコードって意外とないんですよね。なんでそういうサンプルを残そうと思います。
まずは地味なバックエンド処理ではなく、ユーザの操作に対応して見た目でわかるようなもので行きます。


***画像をグリグリ動かしたい！***

タップで画像がひっくり返る。パン（画像をなぞる）で画像が回転する。

## サンプルコード
細かい説明は長いのでサンプルコードを載せます。
storyboardに適当な大きさのUIView（画像よりは大きく）を乗せてCustomClassにサンプルクラスを設定すれば終わりです。


```Transform.swift
//
//  TransformerView.swift
//
//  Created by hats on 2019/10/10.
//  Copyright © 2019 hats. All rights reserved.
//

import UIKit

class TransformerView :UIView, UIGestureRecognizerDelegate{
    
    var mylayer:CALayer = CALayer()         // 画像表示layer
    var image: UIImage!
    
    var rotation:Int = 0{
        didSet{ afterDidSetTransform() }
    }
    var isFlip:Bool = false{
        didSet{ afterDidSetTransform() }
    }
    
    
    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        self.layer.addSublayer(mylayer)
        
        // 配置したframeの中央に画像が表示されるように
        image = UIImage(named: "_i_icon_11477_icon_114770_64")!
        mylayer.contents = image.cgImage
        mylayer.frame = CGRect(x: self.frame.width/2 - image.size.width/2, y: self.frame.height/2 - image.size.height/2, width: image.size.width, height: image.size.height)
        
        // パン操作
        let panGetsture = UIPanGestureRecognizer(target: self, action: #selector(panAction))
        self.addGestureRecognizer(panGetsture)
        panGetsture.delegate = self

        // タップ操作
        let tapGetsture = UITapGestureRecognizer(target: self, action: #selector(tapAction))
        self.addGestureRecognizer(tapGetsture)
        tapGetsture.delegate = self
        
    }
    
    
    func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer,shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
        return true
    }

    var prevPanPt:CGPoint!
    @objc func panAction(sender: UIPanGestureRecognizer) {
        let nowPanPt = sender.location(in: self.superview)

        let center = self.frame.origin + CGPoint(x: image.size.width/2, y: image.size.height/2)
        
        if( sender.state == .began){
            prevPanPt = nowPanPt
        }
        if( sender.state == .changed){
            
            let vec1 = prevPanPt - center
            let vec2 = nowPanPt - center
            let rad = vec1 || vec2
            let deg = rad / CGFloat.pi * 180.0
            self.rotation += Int(deg)
            
            prevPanPt = nowPanPt
        }
        if( sender.state == .ended){
        }
        
    }
    
    @objc func tapAction(sender: UITapGestureRecognizer) {
        self.isFlip = !self.isFlip
    }
    
    
    /// 変形操作
    private func afterDidSetTransform(){
        
        let rotateLayer = CGFloat(rotation) / 180 * CGFloat.pi
        let tranRotate = CATransform3DMakeRotation(rotateLayer, 0, 0, 1.0)
        
        let flipLayer = self.isFlip ? CGFloat.pi : 0
        let tranFlip = CATransform3DMakeRotation(flipLayer, 0, 1.0, 0)
        mylayer.transform = CATransform3DConcat(tranFlip, tranRotate)
    }
    
}

func + (left: CGPoint, right: CGPoint) -> CGPoint{
    return CGPoint(x: left.x + right.x, y:left.y + right.y)
}
func - (left: CGPoint, right: CGPoint) -> CGPoint{
    return CGPoint(x: left.x - right.x, y:left.y - right.y)
}
/// 内積を取得
func * (left: CGPoint, right: CGPoint) -> CGFloat{
    return (right.x * left.x + right.y * left.y)
}
/// 2ベクトル間の角度θを取得
func || (left: CGPoint, right: CGPoint) -> CGFloat{
    let a_ = sqrt( pow(left.x, 2) + pow(left.y, 2) )
    let b_ = sqrt( pow(right.x, 2) + pow(right.y, 2) )
    let cos_sita = left * right / (a_ * b_)
    let sita = acos(cos_sita)
    return sita.isNaN ? 0 : sita
}
```


## ちょっとだけ解説
難しいコードは書いてません。
見てわかる程度のものですが肝だけ簡単に解説します。

今回はCALayerに画像を乗せて、それをUIViewに乗せています。
UIViewでは操作に応じてCALayerにtransformをかけています。
panActionでやっているのは、なんとなく画像中心位置に対して回転操作を行っているのを角度に変換計算しているだけです。＋ーがないので全部右回りになっちゃいますが今回はこれで。

afterDidSetTransformが肝です。Y軸周りでの回転、つまり見た目が反転とZ軸周り、つまり見た目のままの回転を反映しています。
![ozwgk-sk6t8.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/491285/dfef0b7a-67ef-4eca-8ba2-765323dd0b15.gif)