Center a bezier path to a UIview? - swift

For the life of me, I cannot center this bezier path (the circle displayed below). The orange view is properly constrained and horizontally aligned, however when I add 3 layers to the orange view using the method below, I cannot seem to center it to the orange view. I declared the orange view as spinnerHolder.
private func createCircleShapeLayer(strokeColor: UIColor, fillColor: UIColor) -> CAShapeLayer {
let layer = CAShapeLayer()
//The farther from 0 x is for this, the more separated the movements of the 3 paths.
let circularPath = UIBezierPath(
arcCenter: CGPoint(x: 0, y: 0),
radius: 30,
startAngle: 0,
endAngle: 2 * CGFloat.pi,
clockwise: true
)
layer.path = circularPath.cgPath
layer.strokeColor = strokeColor.cgColor
layer.lineWidth = 3
layer.fillColor = fillColor.cgColor
layer.lineCap = kCALineCapRound
layer.position = spinnerHolder.center
return layer
}

1- Change arcCenter to
let circularPath = UIBezierPath(arcCenter: CGPoint(x:spinnerHolder.frame.width/2, y:spinnerHolder.frame.height/2),
radius: 30,
startAngle: 0,
endAngle: 2 * CGFloat.pi,
clockwise: true)
2- Comment this
layer.position = spinnerHolder.center
3- Call the method inside viewDidLayoutSubviews
var once = false
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
if once {
spinnerHolder.addSublayer(createCircleShapeLayer(,,,,,))
once = false
}
}
calling inside viewDidLayoutSubviews isn't mandatory , add it anytime/anywhere but not before the VC loads
4- Check this Centering CAShapeLayer within UIView Swift

set arcCenter point as
let centerPoint = CGPoint(x:self.viewRect.bounds.midX, y: self.viewRect.bounds.midY)

Related

WRONG LINE LENGHT shapeLayer.strokeEnd = 0.5 draws more than half of the circle [duplicate]

This question already has an answer here:
Stroke of CAShapeLayer stops at wrong point?
(1 answer)
Closed 1 year ago.
I want the line to end at the top with shapeLayer.strokeEnd = 1.0 and get a circle. the line must end here
but she goes on, it just can't be seen
full circle
when i specify a value of 0.5 i want to get half of the circle but i get much more
half circle
My code:
View
public func createCircleLine(progress: CGFloat, color: UIColor, width: CGFloat) {
let radius = (min(bounds.width, bounds.height) - circleLineWidth) / 2
let center = min(bounds.width, bounds.height) / 2
let bezierPath = UIBezierPath(arcCenter: CGPoint(x: center, y: center),
radius: radius,
startAngle: -CGFloat.pi / 2,
endAngle: 2 * CGFloat.pi,
clockwise: true)
let shapeLayer = CAShapeLayer()
shapeLayer.path = bezierPath.cgPath
shapeLayer.fillColor = nil
shapeLayer.strokeColor = circleProgressLineColor.cgColor
shapeLayer.lineWidth = circleLineWidth
shapeLayer.lineCap = .round
shapeLayer.strokeEnd = progress
layer.addSublayer(shapeLayer)
}
ViewController
class ViewController: UIViewController {
#IBOutlet weak var progressView: CircleProgressView!
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(false)
progressView.createCircleLine(progress: 1.0, color: .green, width: 10)
} }
I don’t understand why I can’t get the correct line length, the coordinates are correct
can i get the correct line length without CABasicAnimation ()?
You need to change the endAngle of your circle to 3 * CGFloat.pi / 2, so it is a complete cycle with no overlapping. The current circle you have has π/2 (90 degrees) overlap
let bezierPath = UIBezierPath(arcCenter: CGPoint(x: center, y: center),
radius: radius,
startAngle: -CGFloat.pi / 2,
endAngle: 3 * CGFloat.pi / 2,
clockwise: true)

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.

How to call UIBezier outline in Swift Playground?

EDIT: Sorry, I wasn't clear originally. I want to get the "outline" path of a line or shape. I'm specifically trying to understand how to use:
context.replacePathWithStrokedPath()
and / or:
CGPathRef CGPathCreateCopyByStrokingPath(CGPathRef path, const CGAffineTransform *transform, CGFloat lineWidth, CGLineCap lineCap, CGLineJoin lineJoin, CGFloat miterLimit);
https://developer.apple.com/documentation/coregraphics/1411128-cgpathcreatecopybystrokingpath?language=objc
I'm not looking for workarounds, thanks.
=====
I'm really trying to wrap my head around drawing a line with an outline around it. i'm using UIBezier, but running into brick walls. So far, I've got this:
import UIKit
import PlaygroundSupport
let screenWidth = 375.0 // points
let screenHeight = 467.0 // points
let centerX = screenWidth / 2.0
let centerY = screenHeight / 2.0
let screenCenterCoordinate = CGPoint(x: centerX, y: centerY)
class LineDrawingView: UIView {
override func draw(_ rect: CGRect) {
let path = UIBezierPath()
path.lineWidth = 5
path.lineCapStyle = .round
//Move to Drawing Point
path.move(to: CGPoint(x:20, y:120))
path.addLine(to: CGPoint(x:200, y:120))
path.stroke()
let dot = UIBezierPath()
dot.lineWidth = 1
dot.lineCapStyle = .round
dot.move(to: CGPoint(x:200, y:120))
dot.addArc(withCenter: CGPoint(x:200, y:120), radius: 5, startAngle: CGFloat(0.0), endAngle: CGFloat(8.0), clockwise: true)
UIColor.orange.setStroke()
UIColor.orange.setFill()
path.stroke()
dot.fill()
let myStrokedPath = UIBezierPath.copy(path)
myStrokedPath().stroke()
}
}
let tView = LineDrawingView(frame: CGRect(x: 0,y: 0, width: screenWidth, height: screenHeight))
tView.backgroundColor = UIColor.white
PlaygroundPage.current.liveView = tView
So, where am I going wrong in this? I cannot seem to figure out where to use CGPathCreateCopyByStrokingPath...or how...
EDIT 2:
Ok, now I've got this. Closer, but how do I fill the path again?
let c = UIGraphicsGetCurrentContext()!
c.setLineWidth(15.0)
let clipPath = UIBezierPath(arcCenter: CGPoint(x:centerX,y:centerY), radius: 90.0, startAngle: -0.5 * .pi, endAngle: 1.0 * .pi, clockwise: true).cgPath
c.addPath(clipPath)
c.saveGState()
c.replacePathWithStrokedPath()
c.setLineWidth(0.2)
c.setStrokeColor(UIColor.black.cgColor)
c.strokePath()
The class was modified slightly to produce this graphic:
The path was not copied in the modified code. Instead the existing path was used to draw, then modified and reused. The dot did not have a stroke so that was added. Since only closed paths can be filled, I drew a thinner path on top of a thicker path by changing the line width.
This is the modified code:
class LineDrawingView: UIView {
override func draw(_ rect: CGRect) {
let path = UIBezierPath()
path.lineWidth = 7
path.lineCapStyle = .round
//Move to Drawing Point
path.move(to: CGPoint(x:20, y:120))
path.addLine(to: CGPoint(x:200, y:120))
path.stroke()
let dot = UIBezierPath()
dot.lineWidth = 1
dot.lineCapStyle = .round
dot.move(to: CGPoint(x:200, y:120))
dot.addArc(withCenter: CGPoint(x:200, y:120), radius: 5, startAngle: CGFloat(0.0), endAngle: CGFloat(8.0), clockwise: true)
dot.stroke()
UIColor.orange.setStroke()
UIColor.orange.setFill()
path.lineWidth = 5
path.stroke()
dot.fill()
}
}
So, I've found the (an) answer. I used CAShapeLayer:
let c = UIGraphicsGetCurrentContext()!
c.setLineCap(.round)
c.setLineWidth(15.0)
c.addArc(center: CGPoint(x:centerX,y:centerY), radius: 90.0, startAngle: -0.5 * .pi, endAngle: (-0.5 * .pi) + (3 / 2 * .pi ), clockwise: false)
c.replacePathWithStrokedPath()
let shape = CAShapeLayer()
shape.path = c.path
shape.fillColor = UIColor.yellow.cgColor
shape.strokeColor = UIColor.darkGray.cgColor
shape.lineWidth = 1
myView.layer.addSublayer(shape)
It works well enough, but not on overlapping layers. I need to learn how to connect contours or something.

How to remove arc border in shadowed UIView with rounded / arc path?

I have ticket View like this
.
My method is I have 2 view, 1 is the ticket itself, and other is for shadow. I have to do this because if I mask the view, it got clipped and the shadow will not appear in the ticket view.
here is the code for create the ticket view:
let shapeLayer = CAShapeLayer()
shapeLayer.frame = someView.bounds
shapeLayer.path = UIBezierPath(roundedRect: someView.bounds,
byRoundingCorners: [UIRectCorner.bottomLeft,UIRectCorner.bottomRight] ,
cornerRadii: CGSize(width: 5.0, height: 5.0)).cgPath
let rect = CGRect(x:0, y:0, width:200, height:100)
let cornerRadius:CGFloat = 5
let subPathSideSize:CGFloat = 25
let path = UIBezierPath(roundedRect: rect, cornerRadius: cornerRadius)
let leftSubPath = UIBezierPath(arcCenter: CGPoint(x: rect.width / 2, y: 0),
radius: subPathSideSize / 2, startAngle: .pi, endAngle: .pi * 0, clockwise: false)
leftSubPath.close()
let rightSubPath = UIBezierPath(arcCenter: CGPoint(x: rect.width / 2, y: rect.height),
radius: subPathSideSize / 2, startAngle: .pi, endAngle: .pi * 0, clockwise: true)
rightSubPath.close()
path.append(leftSubPath)
path.append(rightSubPath.reversing())
let mask = CAShapeLayer()
mask.frame = shapeLayer.bounds
mask.path = path.cgPath
someView.layer.mask = mask
Notes: SomeView is the TicketView.
And here is the code for adding shadow:
let shadowMask = CAShapeLayer()
shadowMask.frame = shadowView.bounds
shadowMask.path = path.cgPath
shadowMask.shadowOpacity = 0.2
shadowMask.shadowRadius = 4
shadowMask.masksToBounds = false
shadowMask.shadowOffset = CGSize(width: 0, height: 2)
shadowView.backgroundColor = UIColor.clear
shadowView.layer.addSublayer(shadowMask)
The shadow makes arc/rounded corner have border like this one (marked with circle red).
Here is my Playground gist
Do you know how to remove the border in the rounded corner and arc path?
Thank you.
u need to add clipToBounds in this block that you have written.
let mask = CAShapeLayer()
mask.frame = shapeLayer.bounds
mask.path = path.cgPath
someView.clipsToBounds = true
someView.layer.mask = mask
So I think there is different calculation for corner in mask and path. So I used fillColor of the shadowLayer to match the color of the CouponView. And now, the borders are gone.
shadowLayer.fillColor = someView.backgroundColor.cgColor

How to crop the UIView as semi circle?

I want to crop UIView in semi circle shape
Thanks in advance.
A convenient way is just subclass a UIView, add a layer on it and make the view color transparent if it's not by default.
import UIKit
class SemiCirleView: UIView {
var semiCirleLayer: CAShapeLayer!
override func layoutSubviews() {
super.layoutSubviews()
if semiCirleLayer == nil {
let arcCenter = CGPoint(x: bounds.size.width / 2, y: bounds.size.height / 2)
let circleRadius = bounds.size.width / 2
let circlePath = UIBezierPath(arcCenter: arcCenter, radius: circleRadius, startAngle: CGFloat.pi, endAngle: CGFloat.pi * 2, clockwise: true)
semiCirleLayer = CAShapeLayer()
semiCirleLayer.path = circlePath.cgPath
semiCirleLayer.fillColor = UIColor.red.cgColor
layer.addSublayer(semiCirleLayer)
// Make the view color transparent
backgroundColor = UIColor.clear
}
}
}
This question was already answered here: Draw a semi-circle button iOS
This is a extract of that question but using a UIView:
Swift 3
let myView = [this should be your view]
let circlePath = UIBezierPath(arcCenter: CGPoint(x: myView.bounds.size.width / 2, y: 0), radius: myView.bounds.size.height, startAngle: 0.0, endAngle: CGFloat(M_PI), clockwise: true)
let circleShape = CAShapeLayer()
circleShape.path = circlePath.cgPath
myView.layer.mask = circleShape
Swift 4
let myView = [this should be your view]
let circlePath = UIBezierPath(arcCenter: CGPoint(x: myView.bounds.size.width / 2, y: 0), radius: myView.bounds.size.height, startAngle: 0.0, endAngle: .pi, clockwise: true)
let circleShape = CAShapeLayer()
circleShape.path = circlePath.cgPath
myView.layer.mask = circleShape
I hope this helps you
it works in swift 4 and swift 5 for this issue.
let yourView = (is the view you want to crop semi circle from top)
let circlePath = UIBezierPath(arcCenter: CGPoint(x: yourView.bounds.size.width / 2, y: yourView.bounds.size.height), radius: yourView.bounds.size.height, startAngle: 0.0, endAngle: .pi, clockwise: false)
let circleShape = CAShapeLayer()
circleShape.path = circlePath.cgPath
yourView.layer.mask = circleShape