UIViewを自由変形する。
拡大縮小や回転ではなく『自由変形』である。
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度以上に開いてしまうとレイヤーが反転(?)してしまう。
これを防ぐため、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
}
}
これで、変形する前にコーナーが反転しないよう、チェックすることができました。
レッツ自由変形!
Androidでも同じことができるらしいよ!