Open2

SwiftUI: CGAffineTransformを複数適用する場合の記法

kabeyakabeya

CGAffineTransformは、回転や拡大縮小、移動など、座標変換に使える行列です。

CGAffineTransform(translationX: 5, y: 10)はX方向に5、Y方向に10移動させる行列です。
CGAffineTransform(scaleX: 3, y: 2)はX方向に3倍、Y方向に2倍拡大する行列です。

で、この2つを組み合わせるのに

let transform1 = CGAffineTransform(translationX: 5, y: 10)
     .scaledBy(x: 3, y: 2)

のような書き方ができます。

また、

let transform2 = CGAffineTransform(translationX: 5, y: 10)
     .concatenating(CGAffineTransform(scaleX: 3, y: 2))

のような書き方もできます。

前者は、拡大のあと、移動が行われる行列\begin{bmatrix} 3 & 0 & 5 \\ 0 & 2 & 10 \\ 0 & 0 & 1 \end{bmatrix}が返ります。

後者は、移動のあと、拡大が行われる行列\begin{bmatrix} 3 & 0 & 15 \\ 0 & 2 & 20 \\ 0 & 0 & 1 \end{bmatrix}が返ります。

let rect = CGRect(x: 0, y: 0, width: 10, height: 10)のとき、

  • 前者rect.applying(transform1)は、CGRect(x: 5, y: 10, width: 30, height: 20)を返します。
  • 後者rect.applying(transform2)は、CGRect(x: 15, y: 20, width: 30, height: 20)を返します。

前者の書き方は、一番最後に書かれている変換から前に向かって順に適用されるようです。
後者は、前から後ろに向かって、つまり前の行列に対してconcatenatingした行列が順に適用されていきます。
両者とも3つ以上の場合も同様です。

直感的には前者も後者と同じ動きをするのかと思ってましたので、結果が期待通りでなくてびっくりしました。

kabeyakabeya

記法としては、CGAffineTransform(translationX: 5, y: 10).scaledBy(x: 3, y: 2)のほうが短くて良いのですが、変換の適用順が記載順の逆だとなると、可読性を考えたときに採用しにくいですね。

未来の自分が、その記述がどんな変換になるかをすぐに理解できる気がしません。