I'm using SpriteKit and trying to achieve the following effect using arcs
I created three SKShapeNodes and assigned UIBezierPath's CGPath property to the SKShapeNode's path property..
Here is my code:
func createCircle(){
//container contains the SKShapeNodes
self.container = SKShapeNode()
self.container.position = CGPoint(x: self.frame.size.width/2, y: self.frame.size.height/2)
//creating UIBezierPaths
let arc = UIBezierPath(arcCenter: CGPoint(x: 0, y: 0), radius: 100, startAngle: CGFloat(M_PI), endAngle: CGFloat(M_PI/2) , clockwise: false)
arc.flatness = 100
self.red = SKShapeNode()
self.red.lineWidth = 20
self.red.strokeColor = SKColor.redColor()
self.red.path = arc.CGPath
let arc1 = UIBezierPath(arcCenter: CGPoint(x: 0, y: 0), radius: 100, startAngle: CGFloat(M_PI/2), endAngle: CGFloat(0.0), clockwise: false)
self.blue = SKShapeNode()
self.blue.strokeColor = SKColor.whiteColor()
self.blue.lineWidth = 20
self.blue.path = arc1.CGPath
let arc2 = UIBezierPath(arcCenter: CGPoint(x: 0, y: 0), radius: 100, startAngle: 0.1, endAngle: CGFloat(3*M_PI/2), clockwise: false)
self.yellow = SKShapeNode()
self.yellow.strokeColor = SKColor.yellowColor()
self.yellow.lineWidth = 20
self.yellow.path = arc2.CGPath
//adding arcs to the container
self.container.addChild(red)
self.container.addChild(yellow)
self.container.addChild(blue)
//adding container to the GameScene
self.addChild(container)
}
After experimenting with the startAngle and endAngle I was able to make this:
The code output shape is not identical to the circle I want to create. How can I get the gaps that are in the desired effect image?
Using this image as a reference I was able to make changes to startAngle and endAngleof UIBezierPath arc method
Result Image
Related
How to make UIView like above.
Tried below but its creating a semi circle type view.
let circlePath = UIBezierPath(arcCenter: CGPoint(x: topView.bounds.size.width, y: topView.bounds.size.height / 2), radius: topView.bounds.size.height, startAngle: .pi, endAngle: 0.0, clockwise: false)
let circleShape = CAShapeLayer()
circleShape.path = circlePath.cgPath
topView.layer.mask = circleShape
Draw it yourself. There is nothing complicated.
With sample:
Start at CenterPoint
Go to BottomPoint (line)
Arc from BottomPoint to LeftPoint, and the angle are Pi/2 to Pi (in clockwise)
Go to leftPoint
Go to CenterPoint (line)
The UIBezierPath:
let bezierPath = UIBezierPath()
let centerPoint = CGPoint(x: view.bounds.width, y: 0)
let bottomPoint = CGPoint(x: view.bounds.width, y: view.bounds.height)
let leftPoint = CGPoint(x: 0, y: 0)
bezierPath.move(to: bottomPoint) //not needed
bezierPath.addArc(withCenter: centerPoint,
radius: view.bounds.height,
startAngle: CGFloat.pi/2.0,
endAngle: CGFloat.pi,
clockwise: true)
bezierPath.addLine(to: leftPoint) //not needed
bezierPath.addLine(to: centerPoint)
There are two "not needed", because they are implicit, but you might want to write them if they are "too much hidden" for you.
Why your self? Because, a circle will only have two points and fill between it. In other words, it won't go to "centerPoint" to fill it.
Example with the same angle I used in my handmade path:
There are 2 * .pi radians in a circle, and you're going from pi to 0, which is a semicircle. Your start and end angles have to be 0.5 pi apart.
I'm trying to animate from a circle to a square with rounded corners. As far as I understand, number of points from both paths should be equal for smooth animation. So I wrote a function that builds a circle bases on a number of arcs:
private func circleShape(segmentNumber: Int = 8, radius: CGFloat = 32) -> CGPath {
let center = self.bounds.center
let circlePath = UIBezierPath()
let segmentAngle = CGFloat.pi * CGFloat(2) / CGFloat(segmentNumber)
var currentAngle: CGFloat = 0
for _ in 0 ..< segmentNumber {
let nextAngle = currentAngle + segmentAngle
circlePath.addArc(withCenter: center, radius: radius,
startAngle: currentAngle, endAngle: nextAngle, clockwise: true)
currentAngle = nextAngle
}
circlePath.close()
return circlePath.cgPath
}
I'm checking number of points with getPathElementsPointsAndTypes() and it increases unproportionally to the segmentNumber (segmentNumber: 1, points: 13; sN: 2, p: 14; sN: 3, p: 21; sN: 4, p: 16). So I can't find a segmentNumber that would give the same number of points (19) as the function that draws the square:
private func squareShape(size: CGFloat = 44, radius: CGFloat = 8) -> CGPath {
let rect = CGRect(center: self.bounds.center, size: CGSize(width: size, height: size))
let left = CGFloat.pi
let up = CGFloat(1.5) * CGFloat.pi
let down = CGFloat.pi / CGFloat(2)
let right = CGFloat(0.0)
let squarePath = UIBezierPath()
squarePath.addArc(withCenter: CGPoint(x: rect.minX + radius, y: rect.minY + radius), radius: radius, startAngle: left, endAngle: up, clockwise: true)
squarePath.addArc(withCenter: CGPoint(x: rect.maxX - radius, y: rect.minY + radius), radius: radius, startAngle: up, endAngle: right, clockwise: true)
squarePath.addArc(withCenter: CGPoint(x: rect.maxX - radius, y: rect.maxY - radius), radius: radius, startAngle: right, endAngle: down, clockwise: true)
squarePath.addArc(withCenter: CGPoint(x: rect.minX + radius, y: rect.maxY - radius), radius: radius, startAngle: down, endAngle: left, clockwise: true)
squarePath.close()
return squarePath.cgPath
}
Animation code:
let animation = CABasicAnimation(keyPath: "path")
animation.duration = 2
animation.toValue = self.circleShape()
animation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.linear)
self.testLayer.add(animation, forKey: "pathAnimation")
Result:
What could I change to make the animation same smooth as in example without rounded rects?
You can simply have two paths
func circlePath () -> UIBezierPath {
return UIBezierPath(roundedRect: CGRect(x:0, y:0, width:50, height:50), cornerRadius: 25)
}
func squarePath () -> UIBezierPath {
return UIBezierPath(roundedRect: CGRect(x:0, y:0, width:30, height:30), cornerRadius: 4)
}
One of the app I'm using has bar graph in circular shape. I've tried to create this by using UIBezierPath with arcCenter but I felt like this is completely wrong approach.
The code I'm currently using:
func drawGraph(){
var startDegrees = 120
for _ in 0...10{
let startAngle: CGFloat = radians(of: startDegrees)
let endAngle: CGFloat = radians(of: startDegrees + 5)
let center = CGPoint(x: bounds.midX, y: bounds.midY)
let radius = min(center.x, center.y) - trackBorderWidth / 2 - CGFloat(Int.random(in: 10...50))
let trackPath = UIBezierPath(arcCenter: center, radius: radius, startAngle: startAngle, endAngle: endAngle, clockwise: true)
trackPath.lineWidth = trackBorderWidth
trackPath.lineCapStyle = .square
trackBackgroundColor.set()
trackPath.stroke()
startDegrees += 7
}
}
Do you have better solutions or do you know any similar graph libraries like this?
EDIT:
So I've been trying to change my code little bit, I'm able to create something similar with this code:
func drawGraph(){
var temp:CGFloat = 120.0
for i in 0...64{
let startAngle: CGFloat = radians(of: temp)
let endAngle: CGFloat = radians(of: temp + 5)
let center = CGPoint(x: bounds.midX, y: bounds.midY)
let radius = min(center.x, center.y) - trackBorderWidth / 2 - 10
let randomNumber = CGFloat(Int.random(in: 10...20))
let trackPath = UIBezierPath(arcCenter: center, radius: radius + randomNumber / 2, startAngle: startAngle, endAngle: endAngle, clockwise: true)
trackPath.lineWidth = randomNumber
trackPath.lineCapStyle = .butt
UIColor.randomColor().set()
trackPath.stroke()
temp += 5.5
}
let circlePath = UIBezierPath(arcCenter: CGPoint(x: bounds.midX, y: bounds.midY), radius: 101, startAngle: CGFloat(0), endAngle:CGFloat(Double.pi * 2), clockwise: true)
let shapeLayer = CAShapeLayer()
shapeLayer.path = circlePath.cgPath
//change the fill color
shapeLayer.fillColor = UIColor.clear.cgColor
//you can change the stroke color
shapeLayer.strokeColor = UIColor.red.cgColor
//you can change the line width
shapeLayer.lineWidth = 3.0
self.layer.addSublayer(shapeLayer)
}
I want to change the radius of two corners of a SKShapeNode (rect) but I didn't find a working solution.
I've tried to use a path, and it didn't work.
Swift 4.2, iOS 12.1.1, Xcode 10.1
let shape = SKShapeNode()
shape.path = UIBezierPath(roundedRect: CGRect(x: -128, y: -128, width: 256, height: 256), cornerRadius: 64).cgPath
shape.position = CGPoint(x: frame.midX, y: frame.midY)
shape.fillColor = UIColor.red
shape.strokeColor = UIColor.blue
shape.lineWidth = 10
addChild(shape)
I made a function that will allow you to customize each corner radius to whatever size you'd like. You can have 1,2,3 or 4 corners with a radius. If you always just want two corners then I would suggest making a wrapper function so you don't have so many parameters to fill in each time you call it.
func CustomRoundRectPath(_ rect:CGRect, _ TLR:CGFloat,_ TRR:CGFloat,_ BLR:CGFloat,_ BRR:CGFloat) -> CGPath {
let w = rect.width
let h = rect.height
//TLP:(TLP)
let TLP = CGPoint(x: TLR, y: h - TLR)
let TRP = CGPoint(x: w - TRR, y: h - TRR)
let BLP = CGPoint(x: BLR, y: BLR)
let BRP = CGPoint(x: w - BRR, y: BRR)
//Create path and addComponents
let path = CGMutablePath()
path.addArc(center: TLP, radius: TLR, startAngle: CGFloat.pi, endAngle: CGFloat.pi/2, clockwise: true)
path.addLine(to: CGPoint(x: TRP.x, y: h))
path.addArc(center: TRP, radius: TRR, startAngle: CGFloat.pi/2, endAngle: 0, clockwise: true)
path.addLine(to: CGPoint(x: w, y: BRP.y))
path.addArc(center: BRP, radius: BRR, startAngle: 0, endAngle: -CGFloat.pi/2, clockwise: true)
path.addLine(to: CGPoint(x: BLP.x, y: 0))
path.addArc(center: BLP, radius: BLR, startAngle: -CGFloat.pi/2, endAngle: -CGFloat.pi, clockwise: true)
path.addLine(to: CGPoint(x: 0, y: TLP.y))
return path
}
I have one circle and 4 arcs located around that circle.
Currently I'm drawing arcs like this:
fileprivate func addArc(from startAngle: CGFloat, to endAngle: CGFloat, of color: SKColor, with text: String) {
let arc = UIBezierPath(arcCenter: CGPoint(x: 0, y: 0), radius: 100, startAngle: startAngle, endAngle: endAngle, clockwise: false)
arc.flatness = 100
let node = SKShapeNode()
node.lineWidth = 36
node.strokeColor = color
node.path = arc.cgPath
addChild(node)
}
and when I later try to add a new LabelNode into my arc,
let labelNode = SKLabelNode(text: text)
labelNode.horizontalAlignmentMode = .center
labelNode.verticalAlignmentMode = .center
node.addChild(labelNode)
that NodeLabel being added into the middle of the circle but not into the middle of the arcs.
I've found that the mistake is in this line:
let arc = UIBezierPath(arcCenter: CGPoint(x: 0, y: 0), radius: 100, startAngle: startAngle, endAngle: endAngle, clockwise: false)
where I'm setting arcCenter.
So the question is, how can I shift the center of those arcs so labelNodes will be added into arcs but not into the circle?