How to intersect two rects in iOS? - ios

I would like to remove the "blue" circle and just have a "hollow" center (so there's only the red layer and you can see the background inside). Filling the inside with the clear color doesn't work.
class AddButtonView: UIView {
override func draw(_ rect: CGRect) {
super.draw(rect)
// Outer circle
UIColor.red.setFill()
let outerPath = UIBezierPath(ovalIn: rect)
outerPath.fill()
// Center circle
UIColor.blue.setFill()
let centerRect = rect.insetBy(dx: rect.width * 0.55 / 2, dy: rect.height * 0.55 / 2)
let centerPath = UIBezierPath(ovalIn: centerRect)
centerPath.fill()
}
}
How can I do it? intersects didn't do anything.

override func draw(_ rect: CGRect) {
super.draw(rect)
// Outer circle
UIColor.red.setFill()
let outerPath = UIBezierPath(ovalIn: rect)
outerPath.fill()
guard let context = UIGraphicsGetCurrentContext() else { return }
context.saveGState()
context.setBlendMode(.destinationOut)
// Center circle
UIColor.blue.setFill()
let centerRect = rect.insetBy(dx: rect.width * 0.55 / 2, dy: rect.height * 0.55 / 2)
let centerPath = UIBezierPath(ovalIn: centerRect)
centerPath.fill()
context.restoreGState()
}
Do not forget to give the view a backgroundColor of .clear and set its isOpaque property to false.
Inspired by Draw transparent UIBezierPath line inside drawRect.
Result:

Related

How to set correct path for triangle along with stroke in CAShapLayer?

I've to create triangle with some border that fit's into another view. But the problem is, Once I set stroke then the Triangle is bigger than the container. So, I decided to calculate the inner triangle size
After applied border we'll have two triangle like Homothetic Transformation. I just want to calculate the size of the small triangle. So, the border will be fit into the container.
I tried to set it up, but I can't get it done for triangle. Because of the inner triangle center is not in the container center.
Please note that this is first try with equilateral Triangle.
Originally I want solve with same solution with any shape of Polygons.
I attached full demo xcode project at here.
Please help me to solve this problem. Thank you...
ShapeView.swift
import UIKit
class ShapeView: UIView {
var path: CGPath? {
didSet{
setNeedsDisplay(bounds)
}
}
var cornerRadius: CGFloat = .zero
var borderWidth: CGFloat{
set{
shapeLayer.lineWidth = newValue
makeTriangle()
}
get{
return shapeLayer.lineWidth
}
}
var borderColor: UIColor{
set{
shapeLayer.strokeColor = newValue.cgColor
}
get{
return UIColor(cgColor: shapeLayer.strokeColor ?? UIColor.clear.cgColor)
}
}
var background: UIColor{
set{
shapeLayer.fillColor = newValue.cgColor
setNeedsDisplay(bounds)
}
get{
return UIColor(cgColor: shapeLayer.fillColor ?? UIColor.clear.cgColor)
}
}
private lazy var shapeLayer: CAShapeLayer = {
let shapeLayer = CAShapeLayer()
shapeLayer.lineWidth = .zero
layer.addSublayer(shapeLayer)
return shapeLayer
}()
func makeTriangle(){
// Homothetic Transformation
// https://math.stackexchange.com/questions/3952129/calculate-bounds-of-the-inner-rectangle-of-the-polygon-based-on-its-constant-bo
// https://stackoverflow.com/questions/65315093/cashapelayer-stroke-issue
let lineWidth: CGFloat = borderWidth/2
let size = (frame.width)-lineWidth
let rect = CGRect(x: lineWidth, y: lineWidth , width:size, height: size)
var cornerRadius: CGFloat = self.cornerRadius-lineWidth
cornerRadius = max(cornerRadius, 0)
let path = CGMutablePath()
path.move(to: CGPoint(x: rect.width/2.0, y: lineWidth))
path.addLine(to: CGPoint(x: lineWidth, y: rect.height - lineWidth))
path.addLine(to: CGPoint(x: rect.width - lineWidth, y: rect.height - lineWidth))
path.closeSubpath()
self.path = path
}
override func draw(_ rect: CGRect) {
super.draw(rect)
guard let path = path else{
return
}
shapeLayer.path = path
}
}
Current Out:
Note that the expressions for the coordinates of the bounds of the inner triangle in the Math.SE post that you linked is not very useful here, because that post assumes an equilateral triangle. However, your triangle is not equilateral. Its base has the same length as its height. The view that it is in is a square after all.
After doing some trigonometry, I've found that the top of the inner triangle's bounding rect is insetted by lineWidth divided by sin(atan(0.5)). The bottom is insetted by lineWidth, obviously. And since the inner bounding rect also has to be a square, the left and right insets are both half of the sum of the top and bottom insets.
Here is a GeoGebra screenshot illustrating where sin(atan(0.5)) comes from (I'm rather bad with GeoGebra, so please forgive the mess):
The right half of the outer triangle has a base that is half of its height, so beta = atan(0.5). Then consider the triangle E'EA2, and notice that sin(beta) = lineWidth / EE', and EE' is the top inset that we want.
// makeTriangle
let lineWidth: CGFloat = borderWidth / 2
let top = lineWidth / sin(atan(0.5))
let bottom = lineWidth
let horizontal = (top + bottom) / 2
let rect = bounds.inset(by:
UIEdgeInsets(
top: top,
left: horizontal,
bottom: bottom,
right: horizontal
)
)
let path = CGMutablePath()
path.move(to: CGPoint(x: rect.midX, y: rect.minY))
path.addLine(to: CGPoint(x: rect.minX, y: rect.maxY))
path.addLine(to: CGPoint(x: rect.maxX, y: rect.maxY))
path.closeSubpath()()
self.path = path
Now the outer triangle should fit neatly inside the square. But note that by default, CALayer property changes are animated, and the outer triangle could exceed the bounds of the square if you are sliding the slider too quickly, and the animations can't keep up. You can disable the animations by wrapping the code that sets CALayer properties into a CATransaction, and setDisableActions(true). For example in the setter of borderWidth:
set{
CATransaction.begin()
CATransaction.setDisableActions(true)
shapeLayer.lineWidth = newValue
makeTriangle()
CATransaction.commit()
}

How can I set image for UIBezierPath

I want to set image inside this path (i.e) I want imageview instead of red colour
enter image description here
Here is my code
import UIKit
#IBDesignable
class diagonalviewprofile: UIView {
#IBInspectable var color : UIColor? = UIColor.red {
didSet {
// self.layer.backgroundColor = self.color?.cgColor
}
}
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = UIColor.clear
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
backgroundColor = UIColor.clear
}
// Only override draw() if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
override func draw(_ rect: CGRect) {
// Drawing code
//get the size of the view
let size = self.bounds.size
//get 4 points for the shape layer
let p1 = self.bounds.origin
let p2 = CGPoint(x: p1.x + size.width, y: p1.y)
let p3 = CGPoint(x: p2.x, y: size.height - 150)
let p4 = CGPoint(x: p1.x, y: size.height - 30)
//create the path
let path = UIBezierPath()
path.move(to: p1)
path.addLine(to: p2)
path.addLine(to: p3)
path.addLine(to: p4)
path.close()
(color ?? UIColor.red).set()
path.fill()
}
}
I want to set image inside this path (i.e) I want imageview instead of red colour
You can set image like:
// get an image of the graphics context
let image: UIImage = UIGraphicsGetImageFromCurrentImageContext()!

Correctly setting corner radius to Rects drawn with Gore Graphics

I want to implement a bar constisting of colored rectangles to visualise a distribution, similar to the storage graphic on the iPhone.
My current approach is this, which is working correctly:
override open func draw(_ rect: CGRect) {
let context = UIGraphicsGetCurrentContext()
drawRects(context!)
}
func drawRects(_ context:CGContext) {
if values.count == 0 { return }
var currentX: Float = 0
let scale = (Float(self.frame.width)) / Float(totalSum)
for i in 0..<values.count {
let color = colors[i].cgColor
let value = values[i]
let rect = CGRect(x: CGFloat(currentX), y: 0, width: CGFloat(Float(value) * scale), height: frame.size.height)
context.addRect(rect)
context.setFillColor(color)
context.fill(rect)
currentX += ((Float(value) * scale) + (value == 0 ? 0 : barSpacing))
}
}
The result is this:
Now I want to round the edges of the individual rectangles with UIBezierPath using this code at the end of the for-loop in the drawRects method:
let path = UIBezierPath(roundedRect: rect, cornerRadius: rect.height / 2)
UIColor.white.setStroke()
path.stroke()
This leads to this:
Somehow this doesn't just round the edges, but cuts off a bit of the height and width, which leads to the small leftovers of the rectangle in the corners. As you can see in the two images, the height of the rectangles changes when applying the path.
Setting the UIBezierPath as a path to the current Core Graphics context instead of setting it as a stroke leads to the same problem.
What is it that I'm doing wrong?
Disclaimer: Did this without testing.
I do believe the way you are filling in your context is incorrect. So, you are filling via context.fill(rect), which does exactly as you are stating. Fill the rect, everything except the path. Well, the path doesn't interfere with the corner, thus your result.
To counter this, we utilize the method context.fillPath() which provides the default rule, CGPathFillRule = .winding. It should only fill the interior of the `rect.
https://developer.apple.com/documentation/coregraphics/cgpathfillrule
CGPathFillRule: When filling a path, regions that a fill rule defines as interior to the path are painted. When clipping with a path, regions interior to the path remain visible after clipping.
override open func draw(_ rect: CGRect) {
let context = UIGraphicsGetCurrentContext()
drawRects(context!)
}
func drawRects(_ context:CGContext) {
if values.count == 0 { return }
var currentX: Float = 0
let scale = (Float(self.frame.width)) / Float(totalSum)
for i in 0..<values.count {
let color = colors[i].cgColor
let value = values[i]
let rect = CGRect(x: CGFloat(currentX), y: 0, width: CGFloat(Float(value) * scale), height: frame.size.height)
let path = UIBezierPath(roundedRect: rect, cornerRadius: rect.height / 2)
context.addPath(path.cgPath)
context.addRect(rect)
context.setFillColor(color)
context.fillPath()
context.closePath()
currentX += ((Float(value) * scale) + (value == 0 ? 0 : barSpacing))
}
}
The solution that finally worked for me is to fill the UIBezierPath, rather than the rectangle:
let rect = CGRect(x: CGFloat(currentX), y: 0, width: CGFloat(Float(value) * scale), height: frame.size.height)
let path = UIBezierPath(roundedRect: rect, cornerRadius: rect.height / 2)
context.setFillColor(color)
path.fill()

Transform UIView shape

I want to transform my UIView's shape into this (the white UIView - look at the right edge) using Swift:
Any ideas how to do it? I guess I should use the transform functionality, but I don't know how exactly. Thanks in advance!
Update:
Here is the update, I misunderstood your question in my first answer
You need to subclass UIView and override the default draw method with your own behaviour. To Adjust the angle you can change the offsetFactor
class ShapeView : UIView {
public var bgc = UIColor.clear
override init(frame: CGRect) {
super.init(frame: frame)
//backup old background color
self.bgc = backgroundColor!
backgroundColor = UIColor.clear
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.bgc = backgroundColor!
backgroundColor = UIColor.clear
}
override func draw(_ rect: CGRect) {
let size = self.bounds.size
let offsetFactor = size.width * 0.98
//define the points
let tl = self.bounds.origin //top left
let tr = CGPoint(x:tl.x + size.width, y:tl.y) //top right
let br = CGPoint(x:tl.x + offsetFactor, y:tr.y + size.height) // bottom right
let bl = CGPoint(x:tl.x, y:tr.y + size.height) //bottom left
let path = UIBezierPath()
path.move(to: tl)
path.addLine(to: tr)
path.addLine(to: br)
path.addLine(to: bl)
path.close()
//set old background color
bgc.set()
path.fill()
}
}
Image
I hope I did understand your question right.
You need to subclass UIView and override the default draw method with your own behaviour. To Adjust the spire you can change the offsetFactor
This should do the job
class ShapeView : UIView {
public var bgc = UIColor.clear
override init(frame: CGRect) {
super.init(frame: frame)
//backup old background color
self.bgc = backgroundColor!
backgroundColor = UIColor.clear
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.bgc = backgroundColor!
backgroundColor = UIColor.clear
}
override func draw(_ rect: CGRect) {
let size = self.bounds.size
let offsetFactor = size.height * 0.7
//define the points
let tl = self.bounds.origin //top left
let tr = CGPoint(x:tl.x + size.width, y:tl.y) //top right
let br = CGPoint(x:tr.x, y:tr.y + offsetFactor) // bottom right
let bm = CGPoint(x:size.width/2, y:size.height) ////bottom middle
let bl = CGPoint(x:tl.x, y:offsetFactor) //bottom left
let path = UIBezierPath()
path.move(to: tl)
path.addLine(to: tr)
path.addLine(to: br)
path.addLine(to: bm)
path.addLine(to: bl)
path.close()
//set old background color
bgc.set()
path.fill()
}
}
Storyboard and Simulator
Dudes, I found an easier solution that works perfectly as an extension to UIView:
extension UIView {
func makeItEdgy() {
let bounds = self.bounds
// Create path to mask the view
let path = CGMutablePath()
path.move(to: bounds.origin)
path.addLine(to: CGPoint(x: bounds.maxX, y: bounds.minY))
path.addLine(to: CGPoint(x: bounds.maxX - (bounds.height / 5), y: bounds.maxY))
path.addLine(to: CGPoint(x: bounds.minX, y: bounds.maxY))
path.closeSubpath()
let maskLayer = (self.layer.mask as? CAShapeLayer) ?? CAShapeLayer()
maskLayer.path = path
self.layer.mask = maskLayer
}
}

Don't want bezier path get clipped by view frame

I thought that Bezier paths don't get clipped by the containing view, unless explicitly specified. I've verified in storyboard and can confirm that "Clip subviews" is unchecked. Also I've added clipsToBounds = falsein drawRect(),
Is there a way to not clip the bezier path at edges of containing view?
I don't want to have a clipped circle like shown in the image:
Here's my code:
override func drawRect(rect: CGRect) {
let lineWidth: CGFloat = 6
let color: UIColor = UIColor.yellowColor()
clipsToBounds = false
let haloRectFrame = CGRectMake(0, 0, bounds.width, bounds.height)
let haloPath = UIBezierPath(ovalInRect: haloRectFrame)
haloPath.lineWidth = lineWidth
color.set()
haloPath.stroke()
Ok, I've implemented two solutions. First solution utilises Bezier path with stroke. Here the stroke gets' clipped, so to fix it I've reduced the frame size. The second solution utilises CAShapeLayer, a subclass of CALayer. Here I use Bezier path, but since CAShapeLayer doesn't get clipped I don't need to reduce the frame size.
Solution 1 (Bezier path with stroke):
override func drawRect(rect: CGRect) {
let haloRectFrame = CGRectMake(
lineWidth / 2,
lineWidth / 2,
rect.width - lineWidth,
rect.height - lineWidth)
let haloPath = UIBezierPath(ovalInRect: haloRectFrame)
haloPath.lineWidth = lineWidth
color.set()
haloPath.stroke()
}
Solution 2 (CAShapeLayer):
override func drawRect(rect: CGRect) {
let haloPath = UIBezierPath(ovalInRect: rect).CGPath
let haloLayer = CAShapeLayer(layer: layer)
haloLayer.path = haloPath
haloLayer.fillColor = UIColor.clearColor().CGColor
haloLayer.strokeColor = color.CGColor
haloLayer.lineWidth = lineWidth
layer.addSublayer(haloLayer)
}

Resources