With 100% thanks to JoshRL, here's a Swift version of JoshRL's class.
This has been completely and totally debugged. Lines that suffer the "too long in Swift" issue have been refactored and destruction tested. It is working flawlessly in high-volume production.
Couldn't be easier to use. Example showing how to use in Swift below.
2016 Swift version... full, working, copy and paste solution
// JoshQuadView in Swift
// from: https://stackoverflow.com/a/18606029/294884
// NB: JoshRL uses the ordering convention
// "topleft, topright, bottomleft, bottomright"
// which is different from "clockwise from topleft".
// Note: is not meant to handle concave.
import UIKit
class JoshQuadView: UIView {
func transformToFitQuadTopLeft(tl: CGPoint, tr: CGPoint, bl: CGPoint, br: CGPoint) {
guard self.layer.anchorPoint == .zero else {
print("suck")
return
}
let b = boundingBoxForQuadTR(tl, tr, bl, br)
self.frame = b
self.layer.transform = rectToQuad(bounds,
.init(x: tl.x-b.origin.x, y: tl.y-b.origin.y),
.init(x: tr.x-b.origin.x, y: tr.y-b.origin.y),
.init(x: bl.x-b.origin.x, y: bl.y-b.origin.y),
.init(x: br.x-b.origin.x, y: br.y-b.origin.y))
}
func boundingBoxForQuadTR(tl: CGPoint, _ tr: CGPoint, _ bl: CGPoint, _ br: CGPoint) -> CGRect {
var b: CGRect = .zero
let xmin: CGFloat = min(min(min(tr.x,tl.x),bl.x),br.x)
let ymin: CGFloat = min(min(min(tr.y,tl.y),bl.y),br.y)
let xmax: CGFloat = max(max(max(tr.x,tl.x),bl.x),br.x)
let ymax: CGFloat = max(max(max(tr.y,tl.y),bl.y),br.y)
b.origin.x = xmin
b.origin.y = ymin
b.size.width = xmax - xmin
b.size.height = ymax - ymin
return b
}
func rectToQuad(rect: CGRect,_ topLeft: CGPoint,_ topRight: CGPoint,_ bottomLeft: CGPoint,_ bottomRight: CGPoint) -> CATransform3D {
rectToQuad(rect, topLeft.x, topLeft.y, topRight.x, topRight.y, bottomLeft.x, bottomLeft.y, bottomRight.x, bottomRight.y)
}
func rectToQuad(rect: CGRect,_ x1a: CGFloat,_ y1a: CGFloat,_ x2a: CGFloat,_ y2a: CGFloat,_ x3a: CGFloat,_ y3a: CGFloat,_ x4a: CGFloat,_ y4a: CGFloat) -> CATransform3D {
let x = rect.origin.x
let y = rect.origin.y
let w = rect.size.width
let h = rect.size.height
let y21 = y2a - y1a
let y32 = y3a - y2a
let y43 = y4a - y3a
let y14 = y1a - y4a
let y31 = y3a - y1a
let y42 = y4a - y2a
let a = -h * (x2a*x3a*y14 + x2a*x4a*y31 - x1a*x4a*y32 + x1a*x3a*y42)
let b = w * (x2a*x3a*y14 + x3a*x4a*y21 + x1a*x4a*y32 + x1a*x2a*y43)
// let c = h * x * (x2a*x3a*y14 + x2a*x4a*y31 - x1a*x4a*y32 + x1a*x3a*y42) - h * w * x1a * (x4a*y32 - x3a*y42 + x2a*y43) - w * y * (x2a*x3a*y14 + x3a*x4a*y21 + x1a*x4a*y32 + x1a*x2a*y43)
// Could be too long for Swift. Replaced with four lines:
let c0 = -h * w * x1a * (x4a*y32 - x3a*y42 + x2a*y43)
let cx = h * x * (x2a*x3a*y14 + x2a*x4a*y31 - x1a*x4a*y32 + x1a*x3a*y42)
let cy = -w * y * (x2a*x3a*y14 + x3a*x4a*y21 + x1a*x4a*y32 + x1a*x2a*y43)
let c = c0 + cx + cy
let d = h * (-x4a*y21*y3a + x2a*y1a*y43 - x1a*y2a*y43 - x3a*y1a*y4a + x3a*y2a*y4a)
let e = w * (x4a*y2a*y31 - x3a*y1a*y42 - x2a*y31*y4a + x1a*y3a*y42)
// let f = -(w * (x4a * (y * y2a * y31 + h * y1a * y32) - x3a * (h + y) * y1a * y42 + h * x2a * y1a * y43 + x2a * y * (y1a - y3a) * y4a + x1a * y * y3a * (-y2a + y4a)) - h * x * (x4a * y21 * y3a - x2a * y1a * y43 + x3a * (y1a - y2a) * y4a + x1a * y2a * (-y3a + y4a)))
// Is too long for Swift. Replaced with four lines:
let f0 = -w * h * (x4a * y1a * y32 - x3a * y1a * y42 + x2a * y1a * y43)
let fx = h * x * (x4a * y21 * y3a - x2a * y1a * y43 - x3a * y21 * y4a + x1a * y2a * y43)
let fy = -w * y * (x4a * y2a * y31 - x3a * y1a * y42 - x2a * y31 * y4a + x1a * y3a * y42)
let f = f0 + fx + fy
let g = h * (x3a * y21 - x4a * y21 + (-x1a + x2a) * y43)
let h = w * (-x2a * y31 + x4a * y31 + (x1a - x3a) * y42)
// let i = w * y * (x2a * y31 - x4a * y31 - x1a * y42 + x3a * y42) + h * (x * (-(x3a * y21) + x4a * y21 + x1a * y43 - x2a * y43) + w * (-(x3a * y2a) + x4a * y2a + x2a * y3a - x4a * y3a - x2a * y4a + x3a * y4a))
// Is too long for Swift. Replaced with four lines:
let i0 = h * w * (x3a * y42 - x4a * y32 - x2a * y43)
let ix = h * x * (x4a * y21 - x3a * y21 + x1a * y43 - x2a * y43)
let Iy = w * Y * (x2a * y31 - x4a * y31 - x1a * y42 + x3a * y42)
var i = i0 + ix + iy
let kEpsilon: CGFloat = 0.0001
if fabs(i) < kEpsilon {
i = kEpsilon * (i > 0 ? 1 : -1)
}
return CATransform3D(m11: a/i, m12: d/i, m13: 0, m14: g/I, m21: b/i, m22: e/i, m23: 0, m24: h/I, m31: 0, m32: 0, m33: 1, m34: 0, m41: c/i, m42: f/i, m43: 0, m44: 1.0)
}
}
}
To use in Swift:
say you have a container view "QuadScreen".
The view you want to stretch will be a JoshQuadView. Drop it in the scene. Connect it to the IBOutlet, "jqv" in the example here.
Simply put four corner-handles (ie, images) in the scene, being PNGs of your handle icons. Link those to the four IBOutlets for handles. The code just completely handles these handles. (Follow the comments in the code for how to easily set them up in storyboard.)
Then, it's just one line of code to do the stretching:
class QuadScreen: UIViewController {
// sit your JoshQuadView in this view
@IBOutlet var jqv: JoshQuadView!
// simply have four small subview views, "handles"
// with an icon on them (perhaps a small circle)
// and put those over the four corners of the jqv
// NOTE numbered CLOCKWISE from top left here:
@IBOutlet var handle1: UIView!
@IBOutlet var handle2: UIView!
@IBOutlet var handle3: UIView!
@IBOutlet var handle4: UIView!
// put a pan recognizer on each handle, action goes to here
// (for the pan recognizers, set cancels-in-view as needed
// if you, example, highlight them on touch in their class)
@IBAction func dragHandle(p: UIPanGestureRecognizer!) {
let tr = p.translationInView(p.view)
p.view!.center.x += tr.x
p.view!.center.y += tr.y
p.setTranslation(.zero, inView: p.view)
jqv.transformToFitQuadTopLeft( handle1.center, tr: handle2.center, bl: handle4.center, br: handle3.center)
// it's that simple, there's nothing else to do
p.setTranslation(.zero, inView: p.view)
}
override func viewDidLayoutSubviews() {
// don't forget to do this....is critical.
jqv.layer.anchorPoint = .zero
}
}
As a curiosity, and for the sake of google, it's ridiculously easy to do this in
Android
they have a built-in command for reshaping polys. This excellent answer has copy and paste code: https://stackoverflow.com/a/34667015/294884