Sunday, July 22, 2018

Swift UIBezierPath for inscribed and circumscribed circles Xcode 7 2 1

Swift UIBezierPath for inscribed and circumscribed circles Xcode 7 2 1


UIBezierPath has an inbuilt initialiser that returns an oval in a rect and of course we can use this to draw circles by passing a square. However, assume you have a rectangular image and you want to mask it with a circle. For this youll need the inscribed circle not an oval. The second scenario I want to imagine is a circumscribed circle outside the rect for where we want, for example, text to flow around a square image in a UITextView. In order to address this I started to think about how we draw around an image square. A square text flow is simple to achieve with a rect larger than that of the image. Circular and oval wraps pose a bigger problem:
But with a little assistance from the Internet in finding the right equations all these things can be achieved:

extension CGFloat {
func radians() -> CGFloat {
let b = CGFloat(M_PI) * (self/180)
return b
}
}

extension UIBezierPath {

convenience init(circumscribedCircleRect rect:CGRect) {
let halfWidth = rect.width / 2
let halfHeight = rect.height / 2
let radius = sqrt(halfWidth * halfWidth + halfHeight * halfHeight)
let center = CGPointMake(rect.midX, rect.midY)
self.init(arcCenter: center, radius: radius, startAngle: CGFloat(0).radians(), endAngle: CGFloat(360).radians(), clockwise: true)
}


convenience init(inscribedCircleRect rect:CGRect) {
let halfWidth = rect.width / 2
let halfHeight = rect.height / 2
let radius = halfWidth >= halfHeight ? halfHeight : halfWidth
let center = CGPointMake(rect.midX, rect.midY)
self.init(arcCenter: center, radius: radius, startAngle: CGFloat(0).radians(), endAngle: CGFloat(360).radians(), clockwise: true)
}


}
Now of course we might also want an oval outside a rect for flowing text as well. This is also possible to calculate:
convenience init(ovalOutsideRect rect:CGRect) {
let width = rect.width
let height = rect.height
let origin = CGPointMake(rect.minX, rect.minY)
let newSize = CGSizeMake(CGFloat(M_SQRT2) * width, CGFloat(M_SQRT2) * height)
let newRect = CGRect(x: origin.x + (width - newSize.width) / 2, y: origin.y + (height - newSize.height) / 2, width: newSize.width, height: newSize.height)
self.init(ovalInRect:newRect)
}
But Ive found that in real use that UITextView doesnt work well when an oval is close to the edge of the page. Words end up randomly broken and characters are left trapped on the wrong side of the curve. Here this is demonstrated with a circular image (or rather a square image that was masked and then a wrap placed around it).
To solve this Ive chosen to design a path for when the image is on the left side of the page and would mirror this on the right side. This represents the minimum wrap required to a smooth curve around the image:
The code for which reads:
convenience init(ovalOutsideRectTextFlowRight rect:CGRect) {
let width = rect.width
let height = rect.height
let origin = CGPointMake(rect.minX, rect.minY)
let newSize = CGSizeMake(CGFloat(M_SQRT2) * width, CGFloat(M_SQRT2) * height)
let newRect = CGRect(x: origin.x + (width - newSize.width) / 2, y: origin.y + (height - newSize.height) / 2, width: newSize.width, height: newSize.height)
self.init(ovalInRect:newRect)
let bezSquare = UIBezierPath(rect: CGRect(x: newRect.minX, y: newRect.minY, width: newRect.width / 2, height: newRect.height))
self.appendPath(bezSquare)
}

Applying this logic with the context of a text wrap this is then achieved:

Conclusion

This post has started to look at the usefulness of inscribed and circumscribed circles. The use case inside a UITextView has been presented but Ive not stretched into discussing the code for this here. See discussion of UITextView exclusionPaths in this post for more detail on this area and also the accompanying demo which includes an oval exclusion area that can be dragged and will show you the exact problem with the fragmentation of words.

Follow @sketchytech


visit link download