Swift: How to set round UIBezierPath position - ios

I just created a round line with UIBezierPath using this code: let circularPath = UIBezierPath(arcCenter: timerLabel.layer.position, radius: frame.size.width / 2 - 80, startAngle: -0.5 * CGFloat.pi, endAngle: 1.5 * CGFloat.pi, clockwise: true)
And I want my circle to have the position of my TimerLabel. But as you can see in the image, my circle just doesn't want to be placed in the middle of my timer label.
I have already tried to set the arcCenter parameter to timerlabel.center, CGPoit (x: timerlabel.x, y: timerlabel.y) and so on. But the circle is still not centered. Can someone help me? Warm greetings

Related

How to create a circular loader from an image in swift

I've done a bit of research but unfortunately only found some libraries that allowed coloured circular loaders and not using an image.
So here's my issue, I've got a circular countdown which in the design I've got to implement uses a complex glow that I find really hard to reproduce as shown. The progress of this countdown is shown by this glow progressing over 3 seconds around a circle.
My initial thought was to try to modify UIView+Glow so that the glow wouldn't vary but even then I would come at a stop when it came to making my UIView radially hidden.
So I'm now thinking of simply exporting the outer glow that makes the progress bar as an image and radially hiding that (which would be faster and simpler to do in the end, rather than trying to make the exact same glow manually).
Does anyone have any idea where I should start looking or what I should be doing to hide part of a circular image using angles/rads ?
EDIT: here's what the glow looks like overlayed with the circular label (the black circle) that is used to show the countdown value.
First I would like to thank #Carpsen90 for helping me out, although his answer was not what I was looking for it helped me understand how to use CABasicAnimation and in combination with inspiration from this answer I found a solution to my problem using an image and a CAShapeLayer
for those wondering what I did here's the code I used
// countdownGlow is the glow (white and red part of my image)
func animateMask() {
let arcPath = CGMutablePath()
arcPath.move(to: CGPoint(x: self.countdownGlow.frame.width / 2, y: 0))
arcPath.addArc(center: CGPoint(x: self.countdownGlow.frame.width / 2, y: self.countdownGlow.frame.width / 2), radius: self.countdownGlow.frame.width / 2, startAngle: CGFloat(-1 * Double.pi / 2), endAngle: CGFloat(-3 * Double.pi / 2), clockwise: false)
arcPath.addArc(center: CGPoint(x: self.countdownGlow.frame.width / 2, y: self.countdownGlow.frame.width / 2), radius: self.countdownGlow.frame.width / 2, startAngle: CGFloat(-3 * Double.pi / 2), endAngle: CGFloat(-5 * Double.pi / 2), clockwise: false)
let ringLayer = CAShapeLayer(layer: self.countdownGlow.layer)
ringLayer.path = arcPath
ringLayer.strokeEnd = 0.0
ringLayer.fillColor = UIColor.clear.cgColor
ringLayer.strokeColor = UIColor.black.cgColor
ringLayer.lineWidth = self.countdownGlow.frame.width / 2
self.countdownGlow.layer.mask = ringLayer
self.countdownGlow.layer.mask?.frame = self.countdownGlow.layer.bounds
let swipe = CABasicAnimation(keyPath: "strokeEnd")
swipe.duration = 3
swipe.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear)
swipe.fillMode = kCAFillModeForwards
swipe.isRemovedOnCompletion = true
swipe.toValue = 1.0
ringLayer.add(swipe, forKey: "strokeEnd")
}
I couldn't seem to simply tell my arc to be a full circle so I went with half a circle once and then another half circle which worked out
If you have any improvements to offer I would gladly try it out to see, because I am sure my solution is not optimised at all

Drawing a line with half a circle using UIBezierPath

I'm using this piece of code to draw a straight line using UIBezierPath class as follows:
let myPath = UIBezierPath()
myPath.move(to: CGPoint(x:10, y:5))
myPath.addLine(to: CGPoint(x:100, y:5))
myPath.close()
UIColor.blue.set()
myPath.stroke()
myPath.fill()
however, I don't know how to change this basic drawing to include half a circle in the path to be as follows:
From: addArc documentation
myPath.addArc(withCenter: CGPoint(x: 55, y: 5),
radius: 10,
startAngle: 0,
endAngle: CGFloat.pi,
clockwise: false)
This should give you about the circle you want. You will also need to break your line into 2 segments.

SKSpriteNode will not center

I have this inside my GameScene which is called in the didMove()
for i in 1...5 {
// path to create the circle
let path = UIBezierPath(arcCenter: CGPoint(x: center.x, y: center.y), radius: CGFloat(((43 * i) + 140)), startAngle: CGFloat(GLKMathDegreesToRadians(-50)), endAngle: CGFloat(M_PI * 2), clockwise: false)
// the inside edge of the circle used for creating its physics body
let innerPath = UIBezierPath(arcCenter: CGPoint(x: center.x, y: center.y), radius: CGFloat(((43 * i) + 130)), startAngle: CGFloat(GLKMathDegreesToRadians(-50)), endAngle: CGFloat(M_PI * 2), clockwise: false)
// create a shape from the path and customize it
let shape = SKShapeNode(path: path.cgPath)
shape.lineWidth = 20
shape.strokeColor = UIColor(red:0.98, green:0.99, blue:0.99, alpha:1.00)
// create a texture and apply it to the sprite
let trackViewTexture = self.view!.texture(from: shape)
let trackViewSprite = SKSpriteNode(texture: trackViewTexture)
trackViewSprite.physicsBody = SKPhysicsBody(edgeChainFrom: innerPath.cgPath)
self.addChild(trackViewSprite)
}
It uses UIBezierPaths to make a few circles. It converts the path into a SKShapeNode then a SKTexture and then applies it to the final SKSpriteNode.
When I do this, the SKSpriteNode is not where it should be, it is a few to the right:
But when I add the SKShapeNode I created, it is set perfectly fine to where it should be:
Even doing this does not center it!
trackViewSprite.position = CGPoint(x: 0, y: 0)
No matter what I try it just will not center.
Why is this happening? Some sort of bug when converting to a texture?
P.S - This has something to do with this also Keep relative positions of SKSpriteNode from SKShapeNode from CGPath
But there is also no response :(
Edit, When I run this:
let testSprite = SKSpriteNode(color: UIColor.yellow, size: trackViewSprite.size)
self.addChild(testSprite)
It shows it has the same frame also:
After a long discussion, we determined that the problem is due to the frame size not being the expected size of the shape.
To combat this, the OP created an outer path of his original path, and calculated the frame that would surround this. Now this approach may not work for everybody.
If anybody else comes across this issue, they will need to do these things:
1) Check the frame of the SKShapeNode to make sure that it is correct
2) Determine what method is best to calculate the correct desired frame
3) Use this new frame when getting textureFromNode to extract only the desired texture size

How to start & end a path at top of the circle?

Given the following code:
let startAngle: CGFloat = CGFloat( (3 * M_PI) / 2 )
let endAngle:CGFloat = CGFloat( (3 * M_PI) / 2 )
let path = UIBezierPath(arcCenter: theCenter, radius: radius, startAngle: startAngle, endAngle: endAngle, clockwise: true).CGPath
And knowing that this is how iOS draws its circles as such:
What do I have to specify to start my circle at top (12:00) & end it at 90 degrees? If I wanted to start it & end it at 0, I would set start & end to 0 & 2π respectively.
You'll want to go from -π/2 to 3π/2 (in a clockwise arc).
You can't go from 3π/2 to 3π/2, as you tried in the question, as iOS will see that as the same angle - and therefore won't generate any arc. You'll therefore want to use the negative representation of 3π/2 (-π/2).
Although note that the unit circle is continuous. Therefore 3π/2 to 7π/2 is just as valid a range.
Also note that the unit circle's positive angle direction is actually anti-clockwise. iOS does it in a clockwise manner as the coordinate system's y-axis is flipped.

Drawing Round corners For Custom Shape by Core Graphics

i am drawing Custom shape using Core Graphics and i want to make Rounded Corners for this shape
this is my code of Drawing my custom Shape
CGPoint p1=[self getPointFromAngleQuarter:start_angle2 andRaduis:card.small_Raduis andCenter:center];
CGContextMoveToPoint(context, p1.x, p1.y);
CGPoint p2=[self getPointFromAngleQuarter:start_angle2 andCenter:center andRaduis:self.large_Raduis];
CGContextAddLineToPoint(context, p2.x, p2.y);
CGContextAddArc(context,center.x, center.y, selectedLargeRaduis, start, end,0);
CGPoint p5=[self getPointFromAngle:end_Angle andCenter:center andRaduis:self.small_Raduis];
CGContextAddLineToPoint(context, p5.x, p5.y);
CGContextAddArc(context,center.x, center.y,selectedSmallRaduis, end, start,1);
CGContextDrawPath(context, kCGPathFill);
and here is the final Result of my custom Shape
Custom Shape:
If this shape is a solid color, the easy solution is to use a very wide line width, plus a round line cap and round line join. I presume, though, that you want this rounded shape to lay entirely inside the shape you included in your picture. Then the trick is to offset the arcs you draw by an amount equal to corner radius of the path (and stroke the line with twice the width of the corner radius).
For example, considering this diagram (which is not the desired shape, but shows us how to get there):
The black shape in the background is your original shape. The white path is the path I'm going to draw to achieve the rounded corners. The light gray is that path stroked with a large line width, a rounded line join, and a rounded line cap. The dark gray is that path filled in with another color.
So hopefully this illustrates the idea. Create a new path, offset by the corner radius, and drawn with a line width twice the corner radius. If you simply draw the new path with a solid back stroke (replacing the light gray in the above image) and solid black fill (replacing the dark gray in the above image), you get your desired shape:
Here is routine to get the path (the white line in my first image) in Objective-C:
- (UIBezierPath *)arcWithRoundedCornerAt:(CGPoint)center
startAngle:(CGFloat)startAngle
endAngle:(CGFloat)endAngle
innerRadius:(CGFloat)innerRadius
outerRadius:(CGFloat)outerRadius
cornerRadius:(CGFloat)cornerRadius {
CGFloat innerTheta = asin(cornerRadius / 2.0 / (innerRadius + cornerRadius)) * 2.0;
CGFloat outerTheta = asin(cornerRadius / 2.0 / (outerRadius - cornerRadius)) * 2.0;
UIBezierPath *path = [UIBezierPath bezierPath];
[path addArcWithCenter:center
radius:innerRadius + cornerRadius
startAngle:endAngle - innerTheta
endAngle:startAngle + innerTheta
clockwise:false];
[path addArcWithCenter:center
radius:outerRadius - cornerRadius
startAngle:startAngle + outerTheta
endAngle:endAngle - outerTheta
clockwise:true];
[path closePath];
return path;
}
Or in Swift 3:
private func arcWithRoundedCorners(at center: CGPoint, startAngle: CGFloat, endAngle: CGFloat, innerRadius: CGFloat, outerRadius: CGFloat, cornerRadius: CGFloat) -> UIBezierPath {
let innerTheta = asin(cornerRadius / 2 / (innerRadius + cornerRadius)) * 2
let outerTheta = asin(cornerRadius / 2 / (outerRadius - cornerRadius)) * 2
let path = UIBezierPath()
path.addArc(withCenter: center, radius: innerRadius + cornerRadius, startAngle: endAngle - innerTheta, endAngle: startAngle + innerTheta, clockwise: false)
path.addArc(withCenter: center, radius: outerRadius - cornerRadius, startAngle: startAngle + outerTheta, endAngle: endAngle - outerTheta, clockwise: true)
path.close()
return path
}
(You can do the above with Core Graphics calls if you want, but I generally use UIBezierPath.)
If, though, you needed the fill to be a different color than the stroke, then the process is more complicated, because you can't just use this technique. Instead, you actually have to define a path that is an outline of the above shape, but consists of drawing not only the two big arcs, but four little arcs for each of the corners. It's tedious, but simple, trigonometry to construct that path, but I wouldn't go through that effort unless you had to.

Resources