Skip to content

YamatoOtaka/CGAffineTransFormSample

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

3 Commits
 
 
 
 
 
 

Repository files navigation

CGAffineTransFormSample

https://qiita.com/yum_fishing/items/2e8c3de5e110dbb9e41e より

CoreGraphicsのCGAffineTranaformを使った回転拡大/縮小移動せん断のアニメーションサンプルのメモです。

回転

    //ラジアン計算
    let angle = 180 * CGFloat.pi / 180
    //時計回りに180°
    let transform = CGAffineTransform(rotationAngle: angle)
    UIView.animate(withDuration: 1.0, animations: {
        self.animationView.transform = transform
    }) { _ in
        //元に戻す
        self.identity()
    }

拡大•縮小

拡大

    // `1`がDefaultValue
    let transform = CGAffineTransform(scaleX: 2, y: 2)
    UIView.animate(withDuration: 1.0, animations: {
        self.animationView.transform = transform
    }) { _ in
        self.identity()
    }

縮小

    let transform = CGAffineTransform(scaleX: 0.5, y: 0.5)
    UIView.animate(withDuration: 1.0, animations: {
        self.animationView.transform = transform
    }) { _ in
        self.identity()
    }

移動

    let transform = CGAffineTransform(translationX: 100, y: 100)
    UIView.animate(withDuration: 1.0, animations: {
        self.animationView.transform = transform
    }) { _ in
        self.identity()
    }

せん断

    let transform = CGAffineTransform(a: 1, b: 0, c: 1, d: 1, tx: 0, ty: 0)
    UIView.animate(withDuration: 1.0, animations: {
        self.animationView.transform = transform
    }) { _ in
        self.identity()
    }

メモ

CGAffineTranaformは回転拡大・縮小移動についてはそれぞれのコンストラクタがありますが、 せん断についてはアフィン変換行列から指定する必要があります。最後に指定方法をまとめます。

アニメーションの原点を左上にしてみる

Point

layerのアニメーションの原点はanchorPointで変更することができます。ただ、anchorPointを修正するとアニメーションの表示位置が意図通りにならないことがあるので、元のFrameにLayer.Frameを合わせることで正しい位置に表示するようにします。

回転

    let angle = 180 * CGFloat.pi / 180
    let transform = CGAffineTransform(rotationAngle: angle)
    // anchorPointを変更しつつ、表示位置を変更させないための小技
    let tmpFrame = animationView.frame
    animationView.layer.anchorPoint = CGPoint(x: 0, y: 0)
    animationView.layer.frame = tmpFrame
    UIView.animate(withDuration: 1.0, animations: {
        self.animationView.transform = transform
    }) { _ in
        self.identity()
    }

拡大

    let transform = CGAffineTransform(scaleX: 2, y: 2)
    let tmpFrame = animationView.frame
    animationView.layer.anchorPoint = CGPoint(x: 0, y: 0)
    animationView.layer.frame = tmpFrame
    UIView.animate(withDuration: 1.0, animations: {
        self.animationView.transform = transform
    }) { _ in
        self.identity()
    }

縮小

    let transform = CGAffineTransform(scaleX: 0.5, y: 0.5)
    let tmpFrame = animationView.frame
    animationView.layer.anchorPoint = CGPoint(x: 0, y: 0)
    animationView.layer.frame = tmpFrame
    UIView.animate(withDuration: 1.0, animations: {
        self.animationView.transform = transform
    }) { _ in
        self.identity()
    }

移動

    let transform = CGAffineTransform(translationX: 100, y: 100)
    let tmpFrame = animationView.frame
    animationView.layer.anchorPoint = CGPoint(x: 0, y: 0)
    animationView.layer.frame = tmpFrame
    UIView.animate(withDuration: 1.0, animations: {
        self.animationView.transform = transform
    }) { _ in
        self.identity()
    }

せん断

    let transform = CGAffineTransform(a: 1, b: 0, c: 1, d: 1, tx: 0, ty: 0)
    let tmpFrame = animationView.frame
    animationView.layer.anchorPoint = CGPoint(x: 0, y: 0)
    animationView.layer.frame = tmpFrame
    UIView.animate(withDuration: 1.0, animations: {
        self.animationView.transform = transform
    }) { _ in
        self.identity()
    }

アニメーションを組み合わせてみる

Point

CGAffineTransformをアニメーションとして組み合わせる場合はtransformをセットするタイミングで連結することで上手くアニメーションが作動します。アニメーションをセットする前にCGAffineTransformを連結すると最初のtransformのみ実行されるので注意が必要です。

回転&拡大

    let tmpFrame = animationView.frame
    let angle = 180 * CGFloat.pi / 180
    let transform = CGAffineTransform(scaleX: 2, y: 2)
    let transform2 = CGAffineTransform(rotationAngle: angle)
    animationView.layer.anchorPoint = CGPoint(x: 0, y: 0)
    animationView.layer.frame = tmpFrame
    UIView.animate(withDuration: 1.0, animations: {
        // 組み合わせる時はtransformをセットするタイミングで連結しないとうまくいかない
        self.animationView.transform = transform.concatenating(transform2)
    }) { _ in
        self.identity()
    }

アフィン変換について

swiftからは回転移動拡大・縮小などに関するアフィン変換についてはそれぞれラッパーとなるコンストラクタがありますが、せん断やカスタムでLayerを描画したい場合は下記の行列を指定して実装する必要があります。三列目は常に(0, 0, 1)などで実質データ構造としては最初の二列を指定します。

$$\begin{bmatrix} a & b & 0 \\\ c & d & 0\\\ tx & ty & 1 \end{bmatrix}$$

a, b, c, d, tx, ty それぞれの役割はこんな感じになり、tx・tyはそのままの意味で、それぞれ平行移動する距離をあらわします。

$$\begin{bmatrix} a & b & 0 \\\ c & d & 0\\\ tx & ty & 1 \end{bmatrix} 👉 \begin{bmatrix} x.scale & y' & 0 \\\ x' & y.scale & 0\\\ tx & ty & 1 \end{bmatrix}$$

ちなみに何も指定していない場合のDefault値は以下になります。

$$\begin{bmatrix} a & b & 0 \\\ c & d & 0\\\ tx & ty & 1 \end{bmatrix} 👉 \begin{bmatrix} 1 & 0 & 0 \\\ 0 & 1 & 0\\\ 0 & 0 & 1 \end{bmatrix}$$

https://developer.apple.com/documentation/coregraphics/cgaffinetransform

行列を指定して実装してみる

拡大

    // x, y とも2倍
    let transform = CGAffineTransform(a: 2, b: 0,
                                      c: 0, d: 2,
                                      tx: 0, ty: 0)
    UIView.animate(withDuration: 1.0, animations: {
        self.animationView.transform = transform
    }) { _ in
        self.identity()
    }

縮小

    // x, y とも1/2倍
    let transform = CGAffineTransform(a: 0.5, b: 0,
                                      c: 0, d: 0.5,
                                      tx: 0, ty: 0)
    UIView.animate(withDuration: 1.0, animations: {
        self.animationView.transform = transform
    }) { _ in
        self.identity()
    }

移動

     // x方向に100pt移動
     let transform = CGAffineTransform(a: 1, b: 0,
                                       c: 0, d: 1,
                                       tx: 100, ty: 0)
     UIView.animate(withDuration: 1.0, animations: {
         self.animationView.transform = transform
     }) { _ in
         self.identity()
     }

せん断

せん断前のセクションで実装した通り下記のようになります。なお指定するCGFloatの移動距離はx.scale * cになります。今回は1を指定しているのでviewのframe.width分xの正方向に軸が傾きます。

    // x.scale(width) * 1 軸が傾く
    let transform = CGAffineTransform(a: 1, b: 0,
                                      c: 1, d: 1,
                                      tx: 100, ty: 0)
    UIView.animate(withDuration: 1.0, animations: {
        self.animationView.transform = transform
    }) { _ in
        self.identity()
    }

回転

回転に関するアフィン変換は少し複雑で、それぞれx'y'を導き出す数式を知っていると上手く変換できます。回転の場合下記のようなアフィン変換で考えるとわかりやすい。

$$\begin{bmatrix} x' \\\ y' \end{bmatrix} 👉 \begin{bmatrix} cosθ & -sinθ \\\ sinθ & cosθ \end{bmatrix} アフィン変換で表すと \begin{bmatrix} cosθ & sinθ & 0 \\\ -sinθ & cosθ & 0\\\ tx & ty & 1 \end{bmatrix}$$

数学的な見解が知りたい場合はここで説明してるサイトがわかりやすかったです。

     // 90°時計回りに回転
     let angle = 90 * CGFloat.pi / 180
     let sinAngle = sin(angle)
     let cosAngle = cos(angle)
     let transform = CGAffineTransform(a: cosAngle, b: sinAngle,
                                          c: -sinAngle, d: cosAngle,
                                          tx: 0, ty: 0)
     UIView.animate(withDuration: 1.0, animations: {
         self.animationView.transform = transform
     }) { _ in
            self.identity()
     }

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages