1
1

More than 3 years have passed since last update.

[Swift5] UIViewを『自由変形』する

Last updated at Posted at 2021-02-19

UIViewを自由変形する。
拡大縮小や回転ではなく『自由変形』である。

image.png

JoshQuadViewを使う

stackoverflowで見つけた JoshQuadView を拝借するが、Swift5では若干の変更が必要になる。
Swift5で使えるよう修正したものをgistにアップしておいた。
https://gist.github.com/Nouris-Inc/9dcc78ce35116cc2920617ec0576c818

stackoverflow
https://stackoverflow.com/questions/9470493/transforming-a-rectangle-image-into-a-quadrilateral-using-a-catransform3d

中でやっている事はCATransform3Dによる普通のレイヤー変形なので、GLKit (iOS12でdeprecated) など使っていないため安心です。

使い方は、 transformToFitQuadTopLeft() に4コーナーのCGPointを渡すだけです。
ただし、layerのアンカーポイントを CGPoint.zero にしておかなければ動かないので忘れずに設定すること。

let view = JoshQuadView(frame: rect)
view.layer.anchorPoint = CGPoint.zero

view.transformToFitQuadTopLeft(tl: p0, tr: p1, bl: p2, br: p3)

なお、4コーナーの tl, tr, bl, br は、TopLeft TopRight BottomLeft BottomRightである。

変形が反転してしまう不具合を防ぐ

4つ角のうち、どこかのコーナーが180度以上に開いてしまうとレイヤーが反転(?)してしまう。

image.png

これを防ぐため、180度以上は開くことができないよう、変形前にチェックする。

CGPointでベクトル計算ができるように、CGPointVector を利用している。
(が、引き算しか使わなかったので、自前で書いてもいいかもしれない)
https://github.com/koher/CGPointVector

func checkAngle(_ view:UIView, origin:CGPoint) -> Bool {
    var p1:CGPoint = CGPoint.zero
    var p2:CGPoint = CGPoint.zero

    if (view == handleTL) {
        p1 = handleTR.center - origin
        p2 = handleBL.center - origin
    } else if (view == handleTR) {
        p1 = handleBR.center - origin
        p2 = handleTL.center - origin
    } else if (view == handleBL) {
        p1 = handleTL.center - origin
        p2 = handleBR.center - origin
    } else if (view == handleBR) {
        p1 = handleBL.center - origin
        p2 = handleTR.center - origin
    }

    let cross = p1.cross(p2)
    return (cross > 0)
}

handleTL〜handleBR を受け取って、チェック対象のコーナーを判別し、隣接コーナーをp1, p2としている。
originは角度を確認するコーナーの座標である。

CGPointVectorには、point.angle()という角度を取得するメソッドがあるのだが、これは180度を最大値に折り返してしまうため、永久に180度より大きくならない。
なお、分かりやすいように180度と書いているが、実際に返る値はラジアン(180=π)です。

では、どうするのかというと外積を使います。

CGPointVector には内積(dot)はあっても、外積(cross)が無いのでextensionを自作します。
外積の詳しい説明は省きますが、180度以内であれば正の数を、180度を超えると負の数を返します。

extension CGPoint {
    public func cross(_ other: CGPoint) -> CGFloat {
        return self.x * other.y - self.y * other.x
    }
}

これで、変形する前にコーナーが反転しないよう、チェックすることができました。

image.png

レッツ自由変形!

Androidでも同じことができるらしいよ!

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1