How to shift arc center in SpriteKit? - ios

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?

Related

UIBezierPath animation from circle to rounded square

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

iOS - UIBezierPath making start angle always vertical

Hi I'm currently trying to make random angles of a circle but id like the startangle to always be vertical. In the image above its shows 180 degrees of a circle but the start angle is horizontal and it changes depending on the angle i specify. So basically i want the start of the circle to always start at 0 degrees or vertical. Does anyone know the solution for this?
This is the code I'm using.
let center = CGPointMake(size.width/2, size.height/2)
let path = UIBezierPath()
path.addArcWithCenter(center, radius: 200, startAngle: 0, endAngle: 180.degreesToRadians, clockwise: true)
path.addLineToPoint(center)
path.closePath()
let angleShape = SKShapeNode(path: path.CGPath)
angleShape.strokeColor = UIColor(red: 0.203, green: 0.596, blue: 0.858, alpha: 1.0)
angleShape.fillColor = UIColor(red: 0.203, green: 0.596, blue: 0.858, alpha: 1.0)
addChild(angleShape)
change this:
path.addArcWithCenter(center, radius: 200, startAngle: 0, endAngle: 180.degreesToRadians, clockwise: true)
to this:
path.addArcWithCenter(center, radius: 200, startAngle: CGFloat(M_PI_2), endAngle: CGFloat(M_PI + M_PI_2), clockwise: true)
if you want a random startAngle and endAngle try this
func randomRange(min min: CGFloat, max: CGFloat) -> CGFloat {
assert(min < max)
return CGFloat(Float(arc4random()) / 0xFFFFFFFF) * (max - min) + min
}
let startAngle = randomRange(min: CGFloat(0), max: CGFloat(M_PI*2))
let endAngle = randomRange(min: CGFloat(0), max: CGFloat(M_PI*2))
path.addArcWithCenter(center, radius: 200, startAngle: startAngle, endAngle: endAngle, clockwise: true)

Swift: UIBezierPath bezierPathByReversingPath() Issues

I am using bezierPathByReversingPath() trying to make an animating circle. I finally found a method that will work, but I am getting this bar across the circle that is not being "deleted".
The Code:
#IBDesignable class circle: UIView {
override func drawRect(rect: CGRect) {
let center = CGPoint(x:bounds.width/2, y: bounds.height/2)
let circlePath : UIBezierPath = UIBezierPath(arcCenter: center, radius:
CGFloat(250), startAngle: CGFloat(0), endAngle: CGFloat(360), clockwise: true)
let circlePath2 : UIBezierPath = UIBezierPath(arcCenter: center, radius:
CGFloat(200), startAngle: CGFloat(0), endAngle: CGFloat(360), clockwise: true)
circlePath.appendPath(circlePath2.bezierPathByReversingPath())
circlePath.fill() //appendPath(circlePath.bezierPathByReversingPath())
}
}
The Picture
P.S. If you want an example of what I am doing, it is a win/lose animation in the iOS game "Dulp".
It is because UIBezierPath(arcCenter: center, radius: CGFloat(250), startAngle: CGFloat(0), endAngle: CGFloat(360), clockwise: true) takes radians, not degrees, as you specify here.
Thus, one would need to supply the arguments in radians.
I created a simple function that converts from degrees to radians; the result turned out fine.
Here is the function:
func degreesToRadians(degree : Int) -> CGFloat
{
return CGFloat(degree) * CGFloat(M_PI) / 180.0
}
Usage:
let circlePath : UIBezierPath = UIBezierPath(arcCenter: center, radius: 250.0, startAngle: self.degreesToRadians(0), endAngle:
self.degreesToRadians(360), clockwise: true)
let circlePath2 : UIBezierPath = UIBezierPath(arcCenter: center, radius: 200.0, startAngle: self.degreesToRadians(0), endAngle:
self.degreesToRadians(360), clockwise: true)
Or one could create a protocol extension:
extension Double
{
var degreesToRad : CGFloat
{
return CGFloat(self) * CGFloat(M_PI) / 180.0
}
}
Usage:
let circlePath : UIBezierPath = UIBezierPath(arcCenter: center, radius: 250.0, startAngle: 0.0.degreesToRad , endAngle: 360.0.degreesToRad, clockwise: true)
let circlePath2 : UIBezierPath = UIBezierPath(arcCenter: center, radius: 200.0, startAngle: 0.0.degreesToRad, endAngle: 360.0.degreesToRad, clockwise: true)
First of all, the angles are in radians - not degrees. So your circles are going around 30 or so times and not ending up where they started. That's what gives you the bar.
Secondly, you can just stroke one circle instead of creating two and filling them:
#IBDesignable class Circle: UIView {
override func drawRect(rect: CGRect) {
let center = CGPoint(x:bounds.width/2, y: bounds.height/2)
let circlePath : UIBezierPath = UIBezierPath(arcCenter: center, radius:
CGFloat(225), startAngle: CGFloat(0), endAngle: CGFloat(2*M_PI), clockwise: true)
circlePath.lineWidth = 50
circlePath.stroke()
}
}

Creating a Circle With Arcs, Using UIBezierPath (Images Included)

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

Draw a semi-circle button iOS

I am trying to draw a semi-circle button. I'm having trouble both drawing the semi-circle and attaching it to the button I made in xcode. The button in xcode has constraints pinning it to the bottom of the screen and centering it. I reference the button in my view controller and then try to override it to a semi-circle as follows. I am getting a blank button. I also hard coded in the coordinates from the storyboard of where the button is. Is there a better way to do that?
let circlePath = UIBezierPath.init(arcCenter: CGPoint(x: 113.0, y: 434.0),
radius: 10.0, startAngle: 0.0, endAngle: CGFloat(M_PI), clockwise: true)
let circleShape = CAShapeLayer()
circleShape.path = circlePath.CGPath
sunButton.layer.mask = circleShape
The problem is that your shape layer is positioned way outside the buttons bounds. If you just add the shape layer as a subview of your button's layer you'll see the problem:
button.layer.addSublayer(circleShape)
You have to adjust the arc center point, so that the arc lies within the button's bounds:
let circlePath = UIBezierPath.init(arcCenter: CGPointMake(button.bounds.size.width / 2, 0), radius: button.bounds.size.height, startAngle: 0.0, endAngle: CGFloat(M_PI), clockwise: true)
let circleShape = CAShapeLayer()
circleShape.path = circlePath.CGPath
button.layer.mask = circleShape
And you'll get this:
Swift 5 update:
let circlePath = UIBezierPath.init(arcCenter: CGPoint(x: button.bounds.size.width / 2, y: 0), radius: button.bounds.size.height, startAngle: 0.0, endAngle: .pi, clockwise: true)
let circleShape = CAShapeLayer()
circleShape.path = circlePath.cgPath
button.layer.mask = circleShape
Swift 5:
// semiCircleDown
let circlePath = UIBezierPath(arcCenter: CGPoint(x: shapeView.bounds.size.width/2, y: 0), radius: shapeView.bounds.size.height, startAngle: 0, endAngle: .pi, clockwise: true).cgPath
//semiCircleUp
let circlePath = UIBezierPath(arcCenter: CGPoint(x: shapeView.bounds.size.width/2, y: 0), radius: shapeView.bounds.size.height, startAngle: 2 * .pi, endAngle: .pi, clockwise: true).cgPath
//quarterCircleRightDown
let circlePath = UIBezierPath(arcCenter: CGPoint(x: 0, y: 0), radius: shapeView.bounds.size.width, startAngle: 2 * .pi, endAngle: .pi * 3/2, clockwise: true).cgPath
//quarterCircleLeftDown
let circlePath = UIBezierPath(arcCenter: CGPoint(x: shapeView.bounds.size.width, y: 0), radius: shapeView.bounds.size.width, startAngle: .pi * 3/2, endAngle: .pi, clockwise: true).cgPath
quarterCircleRightUp
let circlePath = UIBezierPath(arcCenter: CGPoint(x: 0, y: shapeView.bounds.size.height), radius: shapeView.bounds.size.width, startAngle: 0, endAngle: .pi/2, clockwise: false).cgPath
//quarterCircleLeftUp
let circlePath = UIBezierPath(arcCenter: CGPoint(x: shapeView.bounds.size.width, y: shapeView.bounds.size.height), radius: shapeView.bounds.size.width, startAngle: .pi/2, endAngle: .pi, clockwise: false).cgPath
let shape = CAShapeLayer()
shape.path = circlePath.cgPath
shapeView.layer.mask = shape

Resources