How to draw slices in semicircle shape layers in objective-c - ios

I have drawn the semicircle shape by using following code
- (void)drawRect:(CGRect)rect
{
UIBezierPath *bezierPath = [UIBezierPath bezierPath];
[bezierPath addArcWithCenter:CGPointMake(self.frame.size.width, self.frame.size.height/2) radius:150 startAngle:0 endAngle:2 * M_PI clockwise:YES];
CAShapeLayer *progressLayer = [[CAShapeLayer alloc] init];
[progressLayer setPath:bezierPath.CGPath];
[progressLayer setStrokeColor:[UIColor colorWithWhite:1. alpha:.2].CGColor];
[progressLayer setFillColor:[UIColor colorWithRed:67.0/255.0 green:144.0/255.0 blue:246.0/255.0 alpha:1.0].CGColor];
[progressLayer setLineWidth:.7 * self.bounds.size.width];
[[self layer] addSublayer:progressLayer];
// [self drawWheel];
}
I want to draw a slices like pie in a semicircle using objective-c that should match exactly like image as shown below
Can anyone suggest me any solution please

Add this code in your drawRect(_:)
let radius = min(bounds.size.width, bounds.size.height) / 2;
let center = CGPoint.init(x: bounds.size.width / 2, y: bounds.size.height / 2)
let sliceCount = 6;
let slicePath = UIBezierPath.init()
slicePath.lineWidth = 1;
slicePath.move(to: center)
var angle: CGFloat = 0 - (CGFloat.pi / 2);
var interval: CGFloat = (CGFloat.pi) / CGFloat(sliceCount)
for i in 0 ... sliceCount {
let x = center.x + (radius * cos(angle))
let y = center.y + (radius * sin(angle))
slicePath.addLine(to: CGPoint.init(x: x, y: y))
slicePath.move(to: center)
angle -= interval;
}
UIColor.white.setStroke()
slicePath.stroke()
Here is the result.
Updated
Another code that's very similar to your goal
override func draw(_ rect: CGRect) {
let radius = min(bounds.size.width, bounds.size.height) / 2;
let center = CGPoint.init(x: bounds.size.width, y: bounds.size.height / 2)
let sliceCount = 6;
let semiCirclePath = UIBezierPath.init()
semiCirclePath.move(to: center)
semiCirclePath.addArc(withCenter: center, radius: radius, startAngle: CGFloat.pi / 2, endAngle: CGFloat.pi + (CGFloat.pi / 2) , clockwise: true)
semiCirclePath.close()
UIColor.blue.setFill()
semiCirclePath.fill()
let slicePath = UIBezierPath.init()
slicePath.lineWidth = 1;
slicePath.move(to: center)
var angle: CGFloat = 0 - (CGFloat.pi / 2);
var interval: CGFloat = (CGFloat.pi) / CGFloat(sliceCount)
for i in 0 ... sliceCount {
let x = center.x + (radius * cos(angle))
let y = center.y + (radius * sin(angle))
slicePath.addLine(to: CGPoint.init(x: x, y: y))
slicePath.move(to: center)
angle -= interval;
}
UIColor.white.setStroke()
slicePath.stroke()
semiCirclePath.removeAllPoints()
semiCirclePath.move(to: center)
semiCirclePath.addArc(withCenter: center, radius: 30, startAngle: CGFloat.pi / 2, endAngle: CGFloat.pi + (CGFloat.pi / 2) , clockwise: true)
UIColor.lightGray.setFill()
semiCirclePath.fill()
}
Here what it looks like,
Updated second time
For the one that's completely looked like
override func draw(_ rect: CGRect) {
let radius = min(bounds.size.width, bounds.size.height) / 2;
let center = CGPoint.init(x: bounds.size.width, y: bounds.size.height / 2)
let selectedSliceIndex = 2;
let sliceCount = 6;
let slicePath = UIBezierPath.init()
let selectedSlicePath = UIBezierPath.init()
slicePath.lineWidth = 1;
slicePath.move(to: center)
var angle: CGFloat = 0 - (CGFloat.pi / 2);
let interval: CGFloat = (CGFloat.pi) / CGFloat(sliceCount)
for i in 0 ... sliceCount {
let x = center.x + (radius * cos(angle))
let y = center.y + (radius * sin(angle))
slicePath.addLine(to: CGPoint.init(x: x, y: y))
slicePath.move(to: center)
if i == selectedSliceIndex {
selectedSlicePath.move(to: center)
selectedSlicePath.addArc(withCenter: center, radius: radius, startAngle: angle - interval, endAngle: angle, clockwise: true)
selectedSlicePath.close()
}
angle -= interval;
}
// outer blue circle
let semiCirclePath = UIBezierPath.init()
semiCirclePath.move(to: center)
semiCirclePath.addArc(withCenter: center, radius: radius, startAngle: CGFloat.pi / 2, endAngle: CGFloat.pi + (CGFloat.pi / 2) , clockwise: true)
semiCirclePath.close()
UIColor.blue.setFill()
semiCirclePath.fill()
// lines
UIColor.white.setStroke()
slicePath.stroke()
// selected slice
UIColor.lightGray.setFill()
selectedSlicePath.fill()
UIColor.clear.setFill()
// inner gray circle
semiCirclePath.removeAllPoints()
semiCirclePath.move(to: center)
semiCirclePath.addArc(withCenter: center, radius: 30, startAngle: CGFloat.pi / 2, endAngle: CGFloat.pi + (CGFloat.pi / 2) , clockwise: true)
UIColor.lightGray.setFill()
semiCirclePath.fill()
}
You might need to adjust some values to look beautiful.
I Moved the view's frame inside ViewController FYI.

Related

How to bend text like line segment into circle with core graphics?

The question was asked for BezierPath but now I not necessary to be with bezier it can be with core graphics also.
I am trying to draw curved text (shape it could be started as straight line to circle) and I saw CoreText allow us to draw text on UIBezierPath path. So I tried to draw a straight line and curved it into circle by given bend factor value. It should straight line if value is zero and if its positive bend line to down side else bend the line to up side [text will draw along this path.]
Did a way to bend straight line with given 'bend factor' into circle with UIBezierPath like this ?
i'have tried with way actually I am not convinced... Draw curve with 2 control points, if bend factor is 0 draw straight line else curve line while curve != half circle then updating path with half circle to complete path into a circle if bend factor is positive and doing reverse if bend factor is negative value it looks like straight line converts into circle but this not a good method to do this..For better understanding please take a look here
For example:
let r = textFXmodel.radius // This is bend factor
if r != 0.0 { // If bend factor is equals to zero draw straight line
let positiveR = abs(r)
let fullSize = self.bounds.size
var curvedLinePath = UIBezierPath()
let insetRect = self.bounds.insetBy(dx: 10, dy: 10)
let maximumValue = Float(insetRect.size.width / 2)
let minimumValue = -Float(insetRect.size.width / 2)
if r >= 0.0 { // If bend factor 'r' is positive value
if r <= CGFloat(maximumValue / 2) { // If bend factor 'r' less then half of circle ⌢
// Draw curved line and bend it from control points
let controlPoint1 = CGPoint(x: fullSize.width / 2 - (r + r * 0.2), y: fullSize.height / 2 - (r * 1.5))
let controlPoint2 = CGPoint(x: fullSize.width / 2 + (r + r * 0.2), y: fullSize.height / 2 - (r * 1.5))
let curveStartPoint = CGPoint(x: r, y: fullSize.height / 2)
let curveEndPoint = CGPoint(x: fullSize.width - r, y: fullSize.height / 2)
curvedLinePath.move(to: curveStartPoint)
curvedLinePath.addCurve(to: curveEndPoint, controlPoint1: controlPoint1, controlPoint2: controlPoint2)
} else { // bend factor 'r' is greater or equal to half circle so remove curved line and draw half circle path
let scaledRadius: CGFloat = r - 60
let centerPoint = CGPoint(x: fullSize.width / 2, y: fullSize.height / 2)
let startAngel = CGFloat.pi - (scaledRadius * 0.023)
let endAngle = (CGFloat.pi * 2) + (scaledRadius * 0.023)
curvedLinePath.removeAllPoints()
curvedLinePath = UIBezierPath(arcCenter: centerPoint, radius: (fullSize.width / 4), startAngle: startAngel, endAngle: endAngle, clockwise: true)
}
} else {
if r >= CGFloat(minimumValue / 2) {
let controlPoint1 = CGPoint(x: fullSize.width / 2 - (positiveR + positiveR * 0.2), y: fullSize.height / 2 + (positiveR * 1.5))
let controlPoint2 = CGPoint(x: fullSize.width / 2 + (positiveR + positiveR * 0.2), y: fullSize.height / 2 + (positiveR * 1.5))
let curveStartPoint = CGPoint(x: positiveR, y: fullSize.height / 2)
let curveEndPoint = CGPoint(x: fullSize.width - positiveR, y: fullSize.height / 2)
curvedLinePath.move(to: curveStartPoint)
curvedLinePath.addCurve(to: curveEndPoint, controlPoint1: controlPoint1, controlPoint2: controlPoint2)
} else {
let scaledRadius: CGFloat = positiveR - 60
let centerPoint = CGPoint(x: fullSize.width / 2, y: fullSize.height / 2)
let startAngel = CGFloat.pi + (scaledRadius * 0.023)
let endAngle = (CGFloat.pi * 2) - (scaledRadius * 0.023)
curvedLinePath.removeAllPoints()
curvedLinePath = UIBezierPath(arcCenter: centerPoint, radius: (fullSize.width / 4), startAngle: startAngel, endAngle: endAngle, clockwise: false)
}
}
// and here goes drawing code...
My code works like this it looks like line break at half of way (:
If you have a way to draw text like this without Bezierpath or CoreText please share it with me. I've googled a lot and I read all answers about this topic and no one helped me even this answer, and this
UPDATE:-
In this version line segment will bend in same center but this also not real circle it looks like drop shape
private func updatePath(withRadius radius: CGFloat) -> UIBezierPath {
var curveStartPoint: CGPoint = .zero
var curveEndPoint: CGPoint = .zero
var controlPoint1: CGPoint = .zero
var controlPoint2: CGPoint = .zero
if radius > 0.0 {
controlPoint1 = CGPoint(x: size.width / 2 - (radius + radius * 0.2), y: size.height / 2 - radius)
controlPoint2 = CGPoint(x: size.width / 2 + (radius + radius * 0.2), y: size.height / 2 - radius)
curveStartPoint = CGPoint(x: radius, y: size.height / 2 + (radius * 0.5))
curveEndPoint = CGPoint(x: size.width - radius, y: size.height / 2 + (radius * 0.5))
} else {
let positateR: CGFloat = abs(radius)
controlPoint1 = CGPoint(x: size.width / 2 - (positateR + positateR * 0.2), y: size.height / 2 + positateR)
controlPoint2 = CGPoint(x: size.width / 2 + (positateR + positateR * 0.2), y: size.height / 2 + positateR)
curveStartPoint = CGPoint(x: positateR, y: size.height / 2 + (radius * 0.5))
curveEndPoint = CGPoint(x: size.width - positateR, y: size.height / 2 + (radius * 0.5))
}
let drawingPath = UIBezierPath()
drawingPath.move(to: curveStartPoint)
drawingPath.addCurve(to: curveEndPoint, controlPoint1: controlPoint1, controlPoint2: controlPoint2)
return drawingPath
}

Border only in corners with UIBezierPath() wrong space in lineDashPattern

Im trying to create a view with border only in corners (qrCode style) using lineDashPattern, but the space between the lines doesn't seem to match with the Pattern defined by me. This is my playground:
override func loadView() {
let view = UIView()
view.backgroundColor = .white
let myView = UIView(frame: CGRect(x: 50, y: 50, width: 200, height: 200))
myView.backgroundColor = .black
let borderLayer = CAShapeLayer()
let d = (myView.bounds.width / 2) as NSNumber
borderLayer.strokeColor = UIColor.blue.cgColor
borderLayer.lineDashPattern = [d]
borderLayer.frame = myView.bounds
borderLayer.fillColor = nil
borderLayer.lineWidth = 10
borderLayer.strokeStart = 0.1
//borderLayer.lineDashPhase = -80
borderLayer.path = UIBezierPath(roundedRect: myView.bounds, byRoundingCorners: .allCorners, cornerRadii: CGSize(width: 30, height: 30)).cgPath
borderLayer.lineCap = CAShapeLayerLineCap.round
myView.layer.addSublayer(borderLayer)
view.addSubview(myView)
self.view = view
}
I'm trying to adjust the start of the lines with the lineDashPhase and strokeStart attributes, but doesn't seems to work. Can someone help me?
Interesting idea - using long dashes on a rounded rect... But I don't believe you'll get your desired results.
You probably want to draw the 4 corners like this:
Your path would start with:
move to: A
line to: B
arc with center: C.x B.y and clockwise from 9 o'clock to 12 o'clock
line to: D
then update your points, and draw the top-right / bottom-right / bottom-left corners.
Here's an example class:
class BracketView: UIView {
var radius: CGFloat = 30
var lineLength: CGFloat = 30
var lineWidth: CGFloat = 10
var lineColor: UIColor = .blue
var shapeLayer: CAShapeLayer!
override class var layerClass: AnyClass {
return CAShapeLayer.self
}
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
func commonInit() -> Void {
shapeLayer = self.layer as? CAShapeLayer
shapeLayer.fillColor = UIColor.clear.cgColor
shapeLayer.lineCap = .round
}
override func layoutSubviews() {
super.layoutSubviews()
// set these properties here, so we can change them if desired
shapeLayer.strokeColor = lineColor.cgColor
shapeLayer.lineWidth = lineWidth
let pth = UIBezierPath()
var ptA: CGPoint = .zero
var ptB: CGPoint = .zero
var ptC: CGPoint = .zero
var ptD: CGPoint = .zero
var arcCenter: CGPoint = .zero
var startAngle: CGFloat = 0
// top-left corner
startAngle = .pi // 9 o'clock
ptA.x = bounds.minX
ptA.y = bounds.minY + radius + lineLength
ptB.x = bounds.minX
ptB.y = bounds.minY + radius
ptC.x = bounds.minX + radius
ptC.y = bounds.minY
ptD.x = bounds.minX + radius + lineLength
ptD.y = bounds.minY
arcCenter.x = ptC.x
arcCenter.y = ptB.y
pth.move(to: ptA)
pth.addLine(to: ptB)
pth.addArc(withCenter: arcCenter, radius: radius, startAngle: startAngle, endAngle: startAngle + .pi * 0.5, clockwise: true)
pth.addLine(to: ptD)
// top-right corner
startAngle += (.pi * 0.5) // 12 o'clock
ptA.x = bounds.maxX - (radius + lineLength)
ptA.y = bounds.minY
ptB.x = bounds.maxX - radius
ptB.y = bounds.minY
ptC.x = bounds.maxX
ptC.y = bounds.minY + radius
ptD.x = bounds.maxX
ptD.y = bounds.minY + radius + lineLength
arcCenter.x = ptB.x
arcCenter.y = ptC.y
pth.move(to: ptA)
pth.addLine(to: ptB)
pth.addArc(withCenter: arcCenter, radius: radius, startAngle: startAngle, endAngle: startAngle + .pi * 0.5, clockwise: true)
pth.addLine(to: ptD)
// bottom-right corner
startAngle += (.pi * 0.5) // 3 o'clock
ptA.x = bounds.maxX
ptA.y = bounds.maxY - (radius + lineLength)
ptB.x = bounds.maxX
ptB.y = bounds.maxY - radius
ptC.x = bounds.maxX - radius
ptC.y = bounds.maxY
ptD.x = bounds.maxX - (radius + lineLength)
ptD.y = bounds.maxY
arcCenter.x = ptC.x
arcCenter.y = ptB.y
pth.move(to: ptA)
pth.addLine(to: ptB)
pth.addArc(withCenter: arcCenter, radius: radius, startAngle: startAngle, endAngle: startAngle + .pi * 0.5, clockwise: true)
pth.addLine(to: ptD)
// bottom-left corner
startAngle += (.pi * 0.5) // 6 o'clock
ptA.x = bounds.minX + radius + lineLength
ptA.y = bounds.maxY
ptB.x = bounds.minX + radius
ptB.y = bounds.maxY
ptC.x = bounds.minX
ptC.y = bounds.maxY - radius
ptD.x = bounds.minX
ptD.y = bounds.maxY - (radius + lineLength)
arcCenter.x = ptB.x
arcCenter.y = ptC.y
pth.move(to: ptA)
pth.addLine(to: ptB)
pth.addArc(withCenter: arcCenter, radius: radius, startAngle: startAngle, endAngle: startAngle + .pi * 0.5, clockwise: true)
pth.addLine(to: ptD)
shapeLayer.path = pth.cgPath
}
}
Result with a 200 x 200 frame, green background:

UIImage at last point of UIBezierPath

I want to add a UIImage at last point of UIBezierPath like the image bellow:
Circle Path
Code:
let consume = 0.25
let x = self.frame.size.width / 2.0
let y = self.frame.size.height / 2.0
let radius = (frame.size.width - 10) / 2.0
let startAngle: CGFloat = CGFloat(M_PI * (-1/2))
let endAngle = CGFloat(M_PI * 2 - M_PI/2 * consume)
let circlePath = UIBezierPath(arcCenter: CGPoint(x: x, y: y), radius: radius, startAngle: startAngle, endAngle: endAngle, clockwise: false)
circleLayer = CAShapeLayer()
circleLayer.path = circlePath.cgPath
circleLayer.fillColor = UIColor.clear.cgColor
circleLayer.strokeColor = UIColor.red.cgColor
circleLayer.lineWidth = 8.0;
let image = UIImage(named: "myCoolImage")
let imageView = UIImageView(image: image)
// coordX and coordY must be at last point of UIBezierPath
imageView.frame = CGRect(x: coordX, y: coordY, width: 20.0, height: 20.0)
self.addSubview(imageView)
I've tried the following:
let coordX: CGFloat = cos(endAngle) * radius
let coordY: CGFloat = sin(endAngle) * radius
Result:
Positive coordX and coordY
The main problem is how to calculate the coordX and coordY.
For the sake of having an actual, fully fleshed out answer, here's basically what you need.
let consume = 0.25
let x = self.frame.size.width / 2.0
let y = self.frame.size.height / 2.0
let radius = (frame.size.width - 10) / 2.0
let startAngle: CGFloat = CGFloat(M_PI * (-1/2))
let endAngle = CGFloat(M_PI * 2 - M_PI/2 * consume)
let circlePath = UIBezierPath(arcCenter: CGPoint(x: x, y: y), radius: radius, startAngle: startAngle, endAngle: endAngle, clockwise: false)
circleLayer = CAShapeLayer()
circleLayer.path = circlePath.cgPath
circleLayer.fillColor = UIColor.clear.cgColor
circleLayer.strokeColor = UIColor.red.cgColor
circleLayer.lineWidth = 8.0;
let imageSize = 20.0
// Center + cos/sin(angle) * radius, then subtract half
// the image size so it will be centered on the line
let coordX: CGFloat = x + (cos(endAngle) * radius) - (imageSize / 2.0)
let coordY: CGFloat = y - (sin(endAngle) * radius) - (imageSize / 2.0)
let image = UIImage(named: "myCoolImage")
let imageView = UIImageView(image: image)
// coordX and coordY must be at last point of UIBezierPath
imageView.frame = CGRect(x: coordX, y: coordY, width: imageSize, height: imageSize)
self.addSubview(imageView)

Cut Bezier Path by y coordinate

I have got specific UIBezierPath, example
override func drawInContext(ctx: CGContext) {
if let slider = slider {
// Clip
let rect = bounds.insetBy(dx: bounds.width / 10, dy: bounds.height / 2.2)
let path = UIBezierPath(roundedRect: rect, cornerRadius: 5)
let circleRadius : CGFloat = 10
let xCoordInset = bounds.width / 10
let circlePath = UIBezierPath(ovalInRect: CGRectMake(xCoordInset , rect.midY - circleRadius, circleRadius * 2, circleRadius * 2))
let circlePath1 = UIBezierPath(ovalInRect: CGRectMake(bounds.width - xCoordInset - circleRadius, rect.midY - circleRadius, circleRadius * 2, circleRadius * 2))
let circlePath2 = UIBezierPath(ovalInRect: CGRectMake(rect.midX - circleRadius, rect.midY - circleRadius, circleRadius * 2, circleRadius * 2))
path.appendPath(circlePath)
path.appendPath(circlePath1)
path.appendPath(circlePath2)
CGContextAddPath(ctx, path.CGPath)
CGContextSetFillColorWithColor(ctx, slider.trackTintColor.CGColor)
CGContextFillPath(ctx)
}
}
I want to fill a half of this bezierPath with Gray Color, and another half with Red Color. So I suppose that I need to have 2 same layers, but 1 of them should be cut by y coordinate, can you advice some available methods for this action?
Clip to a rectangle above the cut line before filling with gray. Then clip to a rectangle below the cut line and fill with red.
override func drawInContext(ctx: CGContext) {
if let slider = slider {
// Clip
let rect = bounds.insetBy(dx: bounds.width / 10, dy: bounds.height / 2.2)
let path = UIBezierPath(roundedRect: rect, cornerRadius: 5)
let circleRadius : CGFloat = 10
let xCoordInset = bounds.width / 10
let circlePath = UIBezierPath(ovalInRect: CGRectMake(xCoordInset , rect.midY - circleRadius, circleRadius * 2, circleRadius * 2))
let circlePath1 = UIBezierPath(ovalInRect: CGRectMake(bounds.width - xCoordInset - circleRadius, rect.midY - circleRadius, circleRadius * 2, circleRadius * 2))
let circlePath2 = UIBezierPath(ovalInRect: CGRectMake(rect.midX - circleRadius, rect.midY - circleRadius, circleRadius * 2, circleRadius * 2))
path.appendPath(circlePath)
path.appendPath(circlePath1)
path.appendPath(circlePath2)
let yCut = bounds.midY // or whatever
CGContextSaveGState(ctx); do {
CGContextClipToRect(ctx, CGRectMake(bounds.minX, bounds.minY, bounds.width, yCut - bounds.minY))
CGContextAddPath(ctx, path.CGPath)
CGContextSetFillColorWithColor(ctx, slider.trackTintColor.CGColor)
CGContextFillPath(ctx)
}; CGContextRestoreGState(ctx)
CGContextSaveGState(ctx); do {
CGContextClipToRect(ctx, CGRectMake(bounds.minX, yCut, bounds.width, bounds.maxY - yCut))
CGContextAddPath(ctx, path.CGPath)
CGContextSetFillColorWithColor(ctx, slider.progressTintColor.CGColor)
CGContextFillPath(ctx)
}; CGContextRestoreGState(ctx)
}
}

how to calculate middle points on circular arc draw using UIBezierPath path ios or swift

i'm drawing arc using UIBezierPath. how can i calculate middle point of this arc.
let center = CGPoint(x: bounds.width / 2, y: bounds.height / 2)
var startValue: CGFloat = 0
var startAngle: CGFloat = 0
var endValue: CGFloat = 0
var endAngle: CGFloat = 0
startAngle = (startValue * 2 * CGFloat(M_PI)) - CGFloat(M_PI_2)
endValue = startValue + 0.20
endAngle = (endValue * 2 * CGFloat(M_PI)) -CGFloat(M_PI_2)
let path = UIBezierPath()
path.moveToPoint(center)
path.addArcWithCenter(center, radius: radius.outer, startAngle: startAngle, endAngle: endAngle, clockwise: true)
path.fill()
path.stroke()
The midPoint angle will be the average of your startAngle and your endAngle. This calculates the point based upon the center point and the radius.outer:
let midPointAngle = (startAngle + endAngle) / 2.0
let midPoint = CGPoint(x: center.x + radius.outer * cos(midPointAngle), y: center.y - radius.outer * sin(midPointAngle))

Resources