Swift draw and RESIZE circle with CAShapeLayer() and UIBezierPath - swift

I want to draw a circle with animation, where the animation starts from 12 o'clock, and goes 360 degrees. Everything works fine, but I can not resize the circle.
If I use UIBezierPath - I can define the starting point ("startAngle: -CGFloat.pi / 2").
let shapeLayer = CAShapeLayer()
let center = progressView.center
let circularPath = UIBezierPath(arcCenter: center, radius: 100, startAngle: -CGFloat.pi / 2, endAngle: 2 * CGFloat.pi, clockwise: true)
shapeLayer.path = circularPath.cgPath
If I use UIBezierPath(ovalIn:...) I can resize.. But I want to use both, or how can I keep the start angle, and the size too?
shapeLayer.path = UIBezierPath(ovalIn: CGRect(x: 8, y: 78, width: 70, height: 70)).cgPath

For the first version of your path code, the radius parameter determines the size of the circle.
In the second version, it draws an oval (which may or may not be a circle) into a rectangular box.
Both let you control the size, just with different parameters.
If you want to vary the size of the arc drawn with init(arcCenter:radius:startAngle:endAngle:clockwise:) then vary the radius parameter.
A circle drawn with
let circularPath = UIBezierPath(arcCenter: center,
radius: 100,
startAngle: -CGFloat.pi / 2,
endAngle: 2 * CGFloat.pi,
clockwise: true)
Will be twice as big as a circle drawn with
let circularPath = UIBezierPath(arcCenter: center,
radius: 50,
startAngle: -CGFloat.pi / 2,
endAngle: 2 * CGFloat.pi,
clockwise: true)
(And btw, if the start angle is -π/2, shouldn't the end angle be 3π/2, so the arc is 360° (or 2π) rather than 450°?)

Related

Inner shadow for partial arc with Swift

I want to create an inner shadow for an arc but I get half of a circle instead of the shadow (see image below).
My code is:
let rect = self.bounds
let segmentArcPath = UIBezierPath()
segmentArcPath.addArc(withCenter: rect.center,
radius: (rect.height - insetShadow) / 2,
startAngle: -.pi/2,
endAngle: value * 2 * .pi - .pi/2,
clockwise: true)
shadowPath = segmentArcPath.cgPath
I have tried to add two paths (to not close the shadow), but without success:
let rect = self.bounds
let segmentArcPath = UIBezierPath()
segmentArcPath.addArc(withCenter: rect.center,
radius: (rect.height - insetShadow) / 2,
startAngle: internalStrokeStart,
endAngle: internalStrokeEnd,
clockwise: true)
let segmentArcPath2 = UIBezierPath()
segmentArcPath2.addArc(withCenter: rect.center,
radius: (rect.height - insetShadow) / 2 - 5,
startAngle: internalStrokeStart,
endAngle: internalStrokeEnd,
clockwise: false)
segmentArcPath.append(segmentArcPath2)
shadowPath = segmentArcPath.cgPath
How can I create a shadow as shown in the image above? Do you have any hints?
As recommended by #vacawama:
Only create one UIBezierPath. After drawng the outer arc, draw a 5
unit line towards the center of the arc, then call addArc to add the
inner arc to the same path
let rect = self.bounds
let outerRadius = (rect.height - insetShadow) / 2
let segmentArcPath = UIBezierPath()
segmentArcPath.addArc(withCenter: rect.center,
radius: outerRadius,
startAngle: internalStrokeStart,
endAngle: internalStrokeEnd,
clockwise: true)
let innerCircleRadius = outerRadius - 2
let startPointXInnerCircle = cos(internalStrokeEnd) * innerCircleRadius + rect.center.x
let startPointYInnerCircle = sin(internalStrokeEnd) * innerCircleRadius + rect.center.y
segmentArcPath.addLine(to: CGPoint(x: startPointXInnerCircle, y: startPointYInnerCircle))
segmentArcPath.addArc(withCenter: rect.center,
radius: innerCircleRadius,
startAngle: internalStrokeEnd,
endAngle: internalStrokeStart,
clockwise: false)
shadowPath = segmentArcPath.cgPath

How to draw a UIBezierPath with an ARC to the right similar to the image shown?

How do I draw this UIBezierPath to make it look identical to the green strip on the left of the image below? The rounded arc to the right.
path = UIBezierPath()
path.move(to: CGPoint(x: 0, y: 0))
path.addLine(to: CGPoint(x: 20, y: 0))
path.addLine(to: CGPoint(x: 20, y: 80))
//Add Half Circle Arc To Right
path.addArc(withCenter: CGPoint(x: 0, y: 160), radius: bounds.width, startAngle: 0, endAngle: 90, clockwise: true)
path.addLine(to: CGPoint(x: 20, y: 215))
path.addLine(to: CGPoint(x: 20, y: bounds.maxY))
path.addLine(to: CGPoint(x: 0, y: bounds.maxY))
path.close()
What you need is a routine that given the height of the desired arc and how far this “bubble” should stick out, and will determine the desired arc angle, radius, and center offset.
To determine the center of the circle giving three points on that circle is to identify the chord between two points and then identify the line that bisects that line segment. That results in a line that goes through the center of the circle. Then repeat that for a different two points on the circle. The intersection of those two lines will be the center of the circle.
So, I used a little basic algebra to calculate the slope (m), the y-intercept (b), and the x-intercept (xIntercept) of the line that bisects the line segment between the start of the arc and its half point. We can take that line, and see where it intercepts the x-axis and determine the center of the circle.
From that, a little trigonometry gives us the angle and the radius of the arc that intersects these three points (the top of the arc, the middle of the arc, and the bottom of the arc).
You get something like:
/// Calculate parameters necessary for arc.
/// - Parameter height: The height of top half of the arc.
/// - Parameter distance: How far out the arc should project.
func angleRadiusAndOffset(height: CGFloat, distance: CGFloat) -> (CGFloat, CGFloat, CGFloat) {
let m = distance / height
let b = height / 2 - distance * distance / (2 * height)
let xIntercept = -b / m
let angle = atan2(height, -xIntercept)
let radius = height / sin(angle)
return (angle, radius, xIntercept)
}
And you can then use that to create your path:
var point: CGPoint = CGPoint(x: bounds.minX, y: bounds.minY)
let path = UIBezierPath()
path.move(to: point)
point.x += edgeWidth
path.addLine(to: point)
point.y += bubbleStartY
path.addLine(to: point)
let (angle, radius, offset) = angleRadiusAndOffset(height: bubbleHeight / 2, distance: bubbleWidth)
let center = CGPoint(x: point.x + offset, y:point.y + bubbleHeight / 2)
path.addArc(withCenter: center, radius: radius, startAngle: -angle, endAngle: angle, clockwise: true)
point.y = bounds.maxY
path.addLine(to: point)
point.x = bounds.minX
path.addLine(to: point)
path.close()
And that yields:
That’s using these values:
var edgeWidth: CGFloat = 10
var bubbleWidth: CGFloat = 30
var bubbleHeight: CGFloat = 100
var bubbleStartY: CGFloat = 80
But you can obviously adjust these values as needed.

Set position of outerBelowCircle CAShapeLayer in the center of View

I make a circle by using CAShapeLayer, and I want to set the center of it, in the center of the view. But the circle will be created exactly below the center line (see the screenshot), could anyone help me on this?
let circle = CAShapeLayer()
view.layer.addSublayer(circle)
circle.position = view.center
let circularPath = UIBezierPath(arcCenter: .zero, radius: 100, startAngle: 0, endAngle: 2 * CGFloat.pi, clockwise: true)
circle.path = circularPath.cgPath
I also change the way that for setting it in the center, but It doesn't work
circle.position = CGPoint(x: view.layer.bounds.midX, y: view.layer.bounds.midY)
Consider the following method according to your screenshot this method will resolve your issue.
func showCircle() {
let view = UIView()
let circle = CAShapeLayer()
view.layer.addSublayer(circle)
let positionX = view.center.x
let positionY = view.center.y - 100 // your circle radius
circle.position = CGPoint(x: positionX, y: positionY)
let circularPath = UIBezierPath(arcCenter: .zero, radius: 100, startAngle: 0, endAngle: 2 * CGFloat.pi, clockwise: true)
circle.path = circularPath.cgPath
}
I haven't tested the code. Please verify it by yourself.

Drawing a partial circle

I'm writing a program that will take a number between 0 and 1, and then spits out a circle (or arc I guess) that is completed by that much.
So for example, if 0.5 was inputted, the program would output a semicircle
if 0.1, the program would output a tiny little arc that would ultimately be 10% of the whole circle.
I can get this to work by making the angle starting point 0, and the angle ending point 2*M_PI*decimalInput
However, I need to have the starting point at the top of the circle, so the starting point is 3*M_PI_2 and the ending point would be 7*M_PI_2
I'm just having trouble drawing a circle partially complete with these new starting/ending points. And I'll admit, my math is not the best so any advice/input is appreciated
Here is what I have so far
var decimalInput = 0.75 //this number can be any number between 0 and 1
let start = CGFloat(3*M_PI_2)
let end = CGFloat(7*M_PI_2*decimalInput)
let circlePath = UIBezierPath(arcCenter: circleCenter, radius: circleRadius, startAngle: start, endAngle: end, clockwise: true)
circlePath.stroke()
I just cannot seem to get it right despite what I try. I reckon the end angle is culprit, unless I'm going about this the wrong way
The arc length is 2 * M_PI * decimalInput. You need to add the arc length to the starting angle, like this:
let circleCenter = CGPointMake(100, 100)
let circleRadius = CGFloat(80)
var decimalInput = 0.75
let start = CGFloat(3 * M_PI_2)
let end = start + CGFloat(2 * M_PI * decimalInput)
let circlePath = UIBezierPath(arcCenter: circleCenter, radius: circleRadius, startAngle: start, endAngle: end, clockwise: true)
XCPCaptureValue("path", circlePath)
Result:
Note that the path will be flipped vertically when used to draw in a UIView.
You can use this extension to draw a partial circle
extension UIBezierPath {
func addCircle(center: CGPoint, radius: CGFloat, startAngle: Double, circlePercentage: Double) {
let start = deg2rad(startAngle)
let end = start + CGFloat(2 * Double.pi * circlePercentage)
addArc(withCenter: center,
radius: radius,
startAngle: start,
endAngle: end,
clockwise: true)
}
private func deg2rad(_ number: Double) -> CGFloat {
return CGFloat(number * Double.pi / 180)
}
}
Example usage (you can copy paste it in a playground to see the result)
let view = UIView(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
view.backgroundColor = UIColor.green
let layer = CAShapeLayer()
layer.strokeColor = UIColor.red.cgColor
layer.fillColor = UIColor.clear.cgColor
layer.lineWidth = 8
let path = UIBezierPath()
path.addCircle(center: CGPoint(x: 50, y: 50), radius: 50, startAngle: 270, circlePercentage: 0.87)
layer.path = path.cgPath
view.layer.addSublayer(layer)
view.setNeedsLayout()

No collision-detection by using SKShapeNode with UIBezierpath

I have the following UIBezierPath which is like a circle, but with a gap in it:
var centerPosition = CGPointMake(self.frame.width/2, self.frame.height/2)
var aPath:UIBezierPath = UIBezierPath(arcCenter: centerPosition, radius: 75, startAngle: 0, endAngle: degreesToRadians(size), clockwise: true)
gapeCircle = SKShapeNode(path: myPath, centered: true)
Now I'd like to use the SKPhysicsBody so that when an object collides with the outline of this object, that there is a notification. But only in that situation. If the object "collides" at the gap-position, there shouldn't be a notification.
But I couldn't find out how to solve that problem. I've already tried multiple different SKPhysicsBody-initializers. The only one that worked was the circleOfRadius but that doesn't work like I want it to work, because I need to check the collision only if an object collides with the "non-gap" part of my circle.
I suspect it's because you're not using a closed UIBezierPath. The following code will create a curve that loops back on itself giving the 'C' a width of just 1 pt
var centerPosition = CGPointMake(self.frame.width/2, self.frame.height/2)
var aPath:UIBezierPath = UIBezierPath(arcCenter: centerPosition, radius: 75, startAngle: 0, endAngle: degreesToRadians(size), clockwise: true)
var aPath:UIBezierPath = UIBezierPath(arcCenter: centerPosition, radius: 74, startAngle: degreesToRadians(size), endAngle: 0, clockwise: false)
gapeCircle = SKShapeNode(path: myPath, centered: true)