I working with UIBezierPath and shape detection. For painting im using "UIPanGestureRecognizer".
Example of code:
My shape definder
var gesture = UIPanGestureRecognizer()
.
.
.
view.addGestureRecognizer(gesture.onChange { \[weak self\] gesture in
let point = gesture.location(in: self.view)
let shapeL = CAShapeLayer()
shapeL.strokeColor = UIColor.black.cgColor
shapeL.lineWidth = 2
shapeL.fillColor = UIColor.clear.cgColor
switch gesture.state {
case .began:
//some code
currentBezierPath = UIBezierPath()
break
case .changed:
//some code
shapeLayer.path = self.currentBezierPath.cgPath
break
case .ended:
//define what user was painted(circle, rectangle, etc)
shapeDefinder(path: currentBezierPath)
break
default:
break
})
shapeDefinder
func shapeDefinder(path: UIBezierPath) {
if(path.hasFourRightAngles()){
// square
}
}
extension hasFourRightAngles
extension UIBezierPath {
func hasFourRightAngles() -> Bool {
guard self.currentPoint != .zero else {
// empty path cannot have angles
return false
}
let bounds = self.bounds
let points = [
bounds.origin,
CGPoint(x: bounds.minX, y: bounds.minY),
CGPoint(x: bounds.maxX, y: bounds.maxY),
CGPoint(x: bounds.minX, y: bounds.maxY)
]
let angleTolerance = 5.0 // in degrees
var rightAngleCount = 0
for i in 0...3 {
let p1 = points[i]
let p2 = points[(i+1)%4]
let p3 = points[(i+2)%4]
let angle = p2.angle(between: p1, and: p3)
if abs(angle - 90) <= angleTolerance {
rightAngleCount += 1
}
}
return rightAngleCount >= 4
}
}
and
extension CGPoint {
func angle(between p1: CGPoint, and p2: CGPoint) -\> CGFloat {
let dx1 = self.x - p1.x
let dy1 = self.y - p1.y
let dx2 = p2.x - self.x
let dy2 = p2.y - self.y
let dotProduct = dx1*dx2 + dy1*dy2
let crossProduct = dx1*dy2 - dx2*dy1
return atan2(crossProduct, dotProduct) \* 180 / .pi
}
}
but my method hasFourRightAngles() doesnt work, it always has true.
Cant understand how i can detect square(the user must draw exactly a square, if the user draws a circle, then the check should not pass.)
Maybe someone know about some library which works with UIBezierPath for detect shapes?
The bounds of a path are always a rectangle, no matter the shape, so you should expect this function to always return true. From the docs:
If you want to consider the components of the path itself, you'd need to iterate over its components using it's CGPath. See
applyWithBlockfor how to get the elements. That said, this probably won't work very well, since you likely don't care precisely how the shape was drawn. If you go down this road, you'll probably want to do some work to simplify the curve first, and perhaps put the stokes in a useful order.If the drawing pattern itself is the important thing (i.e. the user's gesture is what matters), then I would probably keep track of whether this could be a rectangle at each point of the drawing. Either it needs to be roughly colinear to the previous line, or roughly normal. And then the final point must be close to the original point.
The better approach is possibly to consider the final image of the shape, regardless of how it was drawn, and then classify it. For various algorithms to do that, see How to identify different objects in an image?