Cut Bezier Path by y coordinate - ios

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)
}
}

Related

Code not executing after end of UIView.animate?

I am in the draw function of an UIView.
I want to draw a triangle, then after an animation, it's a mirror copy.
I can draw the triangle and the animation, or the triangle and it's copy, but when I add the code for drawing the copy in the "finished in" portion of the animation, it isn't drawn.
I don't know how to solve this problem...
Here is my code (with drawing of the mirror copy not showing anything).
public override func draw(_ rect: CGRect) {
guard let context = UIGraphicsGetCurrentContext() else { return }
let viewWidth = self.bounds.width
let viewHeight = self.bounds.height
context.setLineWidth(1.0)
context.setStrokeColor(UIColor.darkGray.cgColor)
context.beginPath()
let path = CGMutablePath()
let r = sqrt(viewHeight * viewHeight + viewWidth * viewWidth) * 0.50
let p1 = CGPoint(x: viewWidth/2, y: 20 + r)
var angle = CGFloat.pi * (90.0 + 30.0) / 180.0
let p2 = CGPoint(x: p1.x + cos(angle) * r, y: p1.y - sin(angle) * r)
angle = CGFloat.pi * 90.0 / 180.0
let p3 = CGPoint(x: p1.x + cos(angle) * r, y: p1.y - sin(angle) * r)
path.move(to: p1)
path.addLine(to: p2)
path.addLine(to: p3)
path.closeSubpath()
context.addPath(path)
context.strokePath()
let trf = CGAffineTransform(scaleX: -1, y: 1)
let sv = UIView(frame:self.bounds)
self.addSubview(sv)
let shapeLayer = CAShapeLayer.init()
shapeLayer.path = path
shapeLayer.strokeColor = UIColor.red.cgColor
shapeLayer.lineWidth = 1.0
sv.layer.addSublayer(shapeLayer)
shapeLayer.display()
UIView.animate(withDuration: 0.5, delay: 0, options: [],
animations: {
sv.transform = trf
}) { finished in
sv.removeFromSuperview()
context.setStrokeColor(UIColor.red.cgColor)
context.scaleBy(x: -1, y: 1)
context.translateBy(x:-viewWidth, y:0)
context.addPath(path)
context.strokePath()
context.scaleBy(x: -1, y: 1)
context.translateBy(x:viewWidth, y:0)
}
}

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

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.

How to draw CATransform3D for this layer?

I'm using CATransform3D and CAShapeLayer to create a layer like below
Here is my code.
let path = CGMutablePath()
let startPoint = CGPoint(x: center.x - width / 2, y: center.y - height / 2)
path.move(to: startPoint)
path.addLine(to: CGPoint(x: startPoint.x + width, y: startPoint.y))
path.addLine(to: CGPoint(x: startPoint.x + width, y: startPoint.y + height))
path.addLine(to: CGPoint(x: startPoint.x, y: startPoint.y + height))
path.closeSubpath()
let backgroundLayer = CAShapeLayer()
backgroundLayer.path = path
backgroundLayer.fillColor = UIColor.clear.cgColor
backgroundLayer.strokeColor = boarderColor.cgColor
var transform = CATransform3DIdentity
transform.m34 = -1 / 500
let angle = 45.toRadians
backgroundLayer.transform = CATransform3DRotate(transform, angle, 1, 0, 0)
The output is like below.
What is the reason for the difference of shape?
The backgroundLayer needs a frame and a position. If these are added, the result is as follows:
Source
Here a slightly modified version of your code that gives the result shown in the screenshot.
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let boarderColor = UIColor.red
let height: CGFloat = 400
let width: CGFloat = 250
let center = CGPoint(x: width / 2.0, y: height / 2)
let path = CGMutablePath()
let startPoint = CGPoint(x: center.x - width / 2, y: center.y - height / 2)
path.move(to: startPoint)
path.addLine(to: CGPoint(x: startPoint.x + width, y: startPoint.y))
path.addLine(to: CGPoint(x: startPoint.x + width, y: startPoint.y + height))
path.addLine(to: CGPoint(x: startPoint.x, y: startPoint.y + height))
path.closeSubpath()
let backgroundLayer = CAShapeLayer()
backgroundLayer.path = path
backgroundLayer.fillColor = UIColor.clear.cgColor
backgroundLayer.strokeColor = boarderColor.cgColor
//these two lines are missing
backgroundLayer.frame = CGRect(x: 0, y: 0, width: width, height: height)
backgroundLayer.position = CGPoint(x: self.view.bounds.width / 2.0, y: self.view.bounds.height / 2)
var transform = CATransform3DIdentity
transform.m34 = -1 / 500
let angle = CGFloat(45 * Double.pi / 180.0)
backgroundLayer.transform = CATransform3DRotate(transform, angle, 1, 0, 0)
self.view.layer.addSublayer(backgroundLayer)
}
}

Draw male and female gender symbols in iOS

Does anyone know how to programmatically draw the male and female gender signs in iOS like the ones below?
An example for drawing the female symbol. First, make a Layer using UIBezierPath to draw it:
import UIKit
class FemaleLayer: CAShapeLayer {
override var frame: CGRect {
didSet{
self.draw()
}
}
private func draw() {
self.lineWidth = 20.0
self.fillColor = UIColor.clear.cgColor
self.strokeColor = UIColor.black.cgColor
let path = UIBezierPath()
let sideLength = fmin(self.frame.width, self.frame.height)
let circlesRadius = (sideLength / 2.0 - self.lineWidth) * 0.6
let circleCenterY = self.bounds.midY * 0.8
path.addArc(withCenter: CGPoint(x:self.bounds.midX, y:circleCenterY), radius: circlesRadius, startAngle: 0.0, endAngle: 2 * CGFloat.pi, clockwise: true)
let circleBottomY = circleCenterY + circlesRadius
path.move(to: CGPoint(x: self.bounds.midX, y: circleBottomY))
path.addLine(to: CGPoint(x: self.bounds.midX, y: circleBottomY + circlesRadius))
path.move(to: CGPoint(x: self.bounds.midX * 0.6, y: circleBottomY + circlesRadius * 0.5))
path.addLine(to: CGPoint(x: self.bounds.midX * 1.4, y: circleBottomY + circlesRadius * 0.5))
self.path = path.cgPath
}
}
Then use it by adding the layer to a UIView:
let femaleLayer = FemaleLayer()
femaleLayer.frame = self.view.bounds
self.view.layer.addSublayer(femaleLayer)
The final result:
Updated, for male:
class MaleLayer: CAShapeLayer {
override var frame: CGRect {
didSet{
self.draw()
}
}
private func draw() {
self.lineWidth = 20.0
self.fillColor = UIColor.clear.cgColor
self.strokeColor = UIColor.black.cgColor
let path = UIBezierPath()
let sideLength = fmin(self.frame.width, self.frame.height)
let circlesRadius = (sideLength / 2.0 - self.lineWidth) * 0.6
let circleCenterX = self.bounds.midX * 0.9
let circleCenterY = self.bounds.midY * 1.2
path.addArc(withCenter: CGPoint(x:circleCenterX, y:circleCenterY), radius: circlesRadius, startAngle: 0.0, endAngle: 2 * CGFloat.pi, clockwise: true)
let circleRightTopX = circleCenterX + circlesRadius * 0.686
let circleRightTopY = circleCenterY - circlesRadius * 0.686
let lineLength = circlesRadius * 0.7
path.move(to: CGPoint(x: circleRightTopX, y: circleRightTopY))
path.addLine(to: CGPoint(x: circleRightTopX + lineLength, y: circleRightTopY - lineLength))
path.move(to: CGPoint(x: circleRightTopX, y: circleRightTopY - lineLength))
path.addLine(to: CGPoint(x: circleRightTopX + lineLength + self.lineWidth / 2.0, y: circleRightTopY - lineLength))
path.move(to: CGPoint(x: circleRightTopX + lineLength, y: circleRightTopY - lineLength))
path.addLine(to: CGPoint(x: circleRightTopX + lineLength , y: circleRightTopY))
self.path = path.cgPath
}
}

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)

Resources