I'm trying to draw an UIView with some 'curvy edges'.
Here's what it's supposed to look like:
here's what I got:
Notice how the top right (TR) corner is not symmetrical to the bottom right (BR) corner ? The BR corner is very similar to what I want to achieve but I can't get the TR corner to align correctly (played around with bunch of different start and end angles).
here's the code:
struct Constants {
static let cornerRadius: CGFloat = 15.0 // used for left-top and left-bottom curvature
static let rightTipWidth: CGFloat = 40.0 // the max. width for the right tip thingy
static let rightCornerRadius: CGFloat = 10.0 // the radius for the right tip
static let rightEdgeRadius: CGFloat = 10.0 // the radius for the top right and bottom right curvature
}
override func draw(_ rect: CGRect) {
super.draw(rect)
// Initialize the path.
let path = UIBezierPath()
// starting point
let startingPoint = CGPoint(x: Constants.cornerRadius, y: 0.0)
path.move(to: startingPoint)
// create a center point for the arc for the top left corner
let leftTopCircleCenterPoint = CGPoint(x: Constants.cornerRadius, y: Constants.cornerRadius)
path.addArc(withCenter: leftTopCircleCenterPoint, radius: Constants.cornerRadius, startAngle: 270.degreesToRadians, endAngle: 180.degreesToRadians, clockwise: false)
// move the path to the bottom left corner
path.addLine(to: CGPoint(x: 0.0, y: frame.size.height - Constants.cornerRadius))
// add the arc to bottom left
let leftBottomCircleCenterPoint = CGPoint(x: Constants.cornerRadius, y: frame.size.height - Constants.cornerRadius)
path.addArc(withCenter: leftBottomCircleCenterPoint, radius: Constants.cornerRadius, startAngle: 180.degreesToRadians, endAngle: 90.degreesToRadians, clockwise: false)
// move along the bottom to the right edge - rightTipWidth
let maxXRightEdge = frame.size.width - Constants.rightTipWidth
path.addLine(to: CGPoint(x: maxXRightEdge, y: frame.size.height))
// add a curve at the bottom before tipping up at 45 degrees
let bottomRightEdgeControlPoint = CGPoint(x: maxXRightEdge, y: frame.size.height - Constants.rightEdgeRadius)
path.addArc(withCenter: bottomRightEdgeControlPoint, radius: Constants.rightEdgeRadius, startAngle: 90.degreesToRadians, endAngle: 45.degreesToRadians, clockwise: false)
// figure out the center for the right side curvature
let rightMidPointY = frame.size.height / 2.0
let halfRadius = (Constants.rightCornerRadius / 2.0)
// move up till the mid point corner radius
path.addLine(to: CGPoint(x: frame.size.width - Constants.rightCornerRadius, y: (rightMidPointY + halfRadius)))
// the destination for the curve (end point of the curve)
let rightEndPoint = CGPoint(x: frame.size.width - Constants.rightCornerRadius, y: (rightMidPointY - halfRadius))
// figure out the right side tip's control point (See: https://developer.apple.com/documentation/uikit/uibezierpath/1624351-addquadcurve)
let rightControlPoint = CGPoint(x: frame.size.width - halfRadius, y: rightMidPointY)
// add the curve for the right side tip
path.addQuadCurve(to: rightEndPoint, controlPoint: rightControlPoint)
// move up at 45 degrees
path.addLine(to: CGPoint(x: maxXRightEdge + Constants.rightEdgeRadius, y: Constants.rightEdgeRadius))
let topRightEdgeControlPoint = CGPoint(x: maxXRightEdge, y: Constants.rightEdgeRadius)
path.addArc(withCenter: topRightEdgeControlPoint, radius: Constants.rightEdgeRadius, startAngle: 315.degreesToRadians, endAngle: 270.degreesToRadians, clockwise: false) // straight
path.close()
// Specify the fill color and apply it to the path.
UIColor.orange.setFill()
path.fill()
// Specify a border (stroke) color.
UIColor.orange.setStroke()
path.stroke()
}
extension BinaryInteger {
var degreesToRadians: CGFloat { return CGFloat(Int(self)) * .pi / 180 }
}
Just a quick summary of my thought process:
Create a bezierPath and move it to the startingPoint
Add the LT (left-top) curve and move the line downards
Move the line along the left edge and add the LB (left-bottom) curve
and the move line along the bottom to the right edge
Move the line till frame.size.width - Constants.rightTipWidth
Add an arc with a center point at x = currentPoint and y = height- rightEdgeRadius
Move the line up until y = (height / 2.0) +
(Constants.rightCornerRadius / 2.0)
Add the QuadCurve with an end point of y = (height / 2.0) -
(Constants.rightCornerRadius / 2.0)
Move the line up till x = maxXRightEdge + Constants.rightEdgeRadius
Add the top right (TR) curve ---> resulting in a non-symmetrical
curvature
Here is another rendition:
#IBDesignable
open class PointerView: UIView {
/// The left-top and left-bottom curvature
#IBInspectable var cornerRadius: CGFloat = 15 { didSet { updatePath() } }
/// The radius for the right tip
#IBInspectable var rightCornerRadius: CGFloat = 10 { didSet { updatePath() } }
/// The radius for the top right and bottom right curvature
#IBInspectable var rightEdgeRadius: CGFloat = 10 { didSet { updatePath() } }
/// The fill color
#IBInspectable var fillColor: UIColor = .blue { didSet { shapeLayer.fillColor = fillColor.cgColor } }
/// The stroke color
#IBInspectable var strokeColor: UIColor = .clear { didSet { shapeLayer.strokeColor = strokeColor.cgColor } }
/// The angle of the tip
#IBInspectable var angle: CGFloat = 90 { didSet { updatePath() } }
/// The line width
#IBInspectable var lineWidth: CGFloat = 0 { didSet { updatePath() } }
/// The shape layer for the pointer
private lazy var shapeLayer: CAShapeLayer = {
let _shapeLayer = CAShapeLayer()
_shapeLayer.fillColor = fillColor.cgColor
_shapeLayer.strokeColor = strokeColor.cgColor
_shapeLayer.lineWidth = lineWidth
return _shapeLayer
}()
public override init(frame: CGRect) {
super.init(frame: frame)
configure()
}
public required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
configure()
}
private func configure() {
layer.addSublayer(shapeLayer)
}
open override func layoutSubviews() {
super.layoutSubviews()
updatePath()
}
private func updatePath() {
let path = UIBezierPath()
let offset = lineWidth / 2
let boundingRect = bounds.insetBy(dx: offset, dy: offset)
let arrowTop = CGPoint(x: boundingRect.maxX - boundingRect.height / 2 / tan(angle * .pi / 180 / 2), y: boundingRect.minY)
let arrowRight = CGPoint(x: boundingRect.maxX, y: boundingRect.midY)
let arrowBottom = CGPoint(x: boundingRect.maxX - boundingRect.height / 2 / tan(angle * .pi / 180 / 2), y: boundingRect.maxY)
let start = CGPoint(x: boundingRect.minX + cornerRadius, y: boundingRect.minY)
// top left
path.move(to: start)
path.addQuadCurve(to: CGPoint(x: boundingRect.minX, y: boundingRect.minY + cornerRadius), controlPoint: CGPoint(x: boundingRect.minX, y: boundingRect.minY))
// left
path.addLine(to: CGPoint(x: boundingRect.minX, y: boundingRect.maxY - cornerRadius))
// lower left
path.addQuadCurve(to: CGPoint(x: boundingRect.minX + cornerRadius, y: boundingRect.maxY), controlPoint: CGPoint(x: boundingRect.minX, y: boundingRect.maxY))
// bottom
path.addLine(to: calculate(from: path.currentPoint, to: arrowBottom, less: rightEdgeRadius))
// bottom right (before tip)
path.addQuadCurve(to: calculate(from: arrowRight, to: arrowBottom, less: rightEdgeRadius), controlPoint: arrowBottom)
// bottom edge of tip
path.addLine(to: calculate(from: path.currentPoint, to: arrowRight, less: rightCornerRadius))
// tip
path.addQuadCurve(to: calculate(from: arrowTop, to: arrowRight, less: rightCornerRadius), controlPoint: arrowRight)
// top edge of tip
path.addLine(to: calculate(from: path.currentPoint, to: arrowTop, less: rightEdgeRadius))
// top right (after tip)
path.addQuadCurve(to: calculate(from: start, to: arrowTop, less: rightEdgeRadius), controlPoint: arrowTop)
path.close()
shapeLayer.lineWidth = lineWidth
shapeLayer.path = path.cgPath
}
/// Calculate some point between `startPoint` and `endPoint`, but `distance` from `endPoint
///
/// - Parameters:
/// - startPoint: The starting point.
/// - endPoint: The ending point.
/// - distance: Distance from the ending point
/// - Returns: Returns the point that is `distance` from the `endPoint` as you travel from `startPoint` to `endPoint`.
private func calculate(from startPoint: CGPoint, to endPoint: CGPoint, less distance: CGFloat) -> CGPoint {
let angle = atan2(endPoint.y - startPoint.y, endPoint.x - startPoint.x)
let totalDistance = hypot(endPoint.y - startPoint.y, endPoint.x - startPoint.x) - distance
return CGPoint(x: startPoint.x + totalDistance * cos(angle),
y: startPoint.y + totalDistance * sin(angle))
}
}
And because that is #IBDesignable, I can put it in a separate framework target and then optionally use it (and customize it) right in Interface Builder:
The only change I made in parameters was to not use the width of the tip, but rather the angle of the tip. That way, if the size changes as constraints (or whatever) change, it preserves the desired shape.
I also changed this to use a CAShapeLayer rather that a custom draw(_:) method to enjoy any efficiencies that Apple has built in to shape layers.
I don't know your implementation but I think it will be easy if you implemented it like that , that way you cam achieve symmetric shape perfectly
to draw a triangle , just tweak the positions of triangle points
class TriangleView : UIView {
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override func draw(_ rect: CGRect) {
guard let context = UIGraphicsGetCurrentContext() else { return }
context.beginPath()
context.move(to: CGPoint(x: rect.minX, y: rect.maxY))
context.addLine(to: CGPoint(x: rect.maxX, y: rect.maxY))
context.addLine(to: CGPoint(x: (rect.maxX / 2.0), y: rect.minY))
context.closePath()
context.setFillColor(red: 1.0, green: 0.5, blue: 0.0, alpha: 0.60)
context.fillPath()
}
}
Here, you forgot halfRadius
// move up at 45 degrees
path.addLine(to: CGPoint(x: maxXRightEdge + Constants.rightEdgeRadius, y: Constants.rightEdgeRadius - halfRadius))
Full code:
override func draw(_ rect: CGRect) {
super.draw(rect)
// Initialize the path.
let path = UIBezierPath()
// starting point
let startingPoint = CGPoint(x: Constants.cornerRadius, y: 0.0)
path.move(to: startingPoint)
// create a center point for the arc for the top left corner
let leftTopCircleCenterPoint = CGPoint(x: Constants.cornerRadius, y: Constants.cornerRadius)
path.addArc(withCenter: leftTopCircleCenterPoint, radius: Constants.cornerRadius, startAngle: 270.degreesToRadians, endAngle: 180.degreesToRadians, clockwise: false)
// move the path to the bottom left corner
path.addLine(to: CGPoint(x: 0.0, y: frame.size.height - Constants.cornerRadius))
// add the arc to bottom left
let leftBottomCircleCenterPoint = CGPoint(x: Constants.cornerRadius, y: frame.size.height - Constants.cornerRadius)
path.addArc(withCenter: leftBottomCircleCenterPoint, radius: Constants.cornerRadius, startAngle: 180.degreesToRadians, endAngle: 90.degreesToRadians, clockwise: false)
// move along the bottom to the right edge - rightTipWidth
let maxXRightEdge = frame.size.width - Constants.rightTipWidth
path.addLine(to: CGPoint(x: maxXRightEdge, y: frame.size.height))
// add a curve at the bottom before tipping up at 45 degrees
let bottomRightEdgeControlPoint = CGPoint(x: maxXRightEdge, y: frame.size.height - Constants.rightEdgeRadius)
path.addArc(withCenter: bottomRightEdgeControlPoint, radius: Constants.rightEdgeRadius, startAngle: 90.degreesToRadians, endAngle: 45.degreesToRadians, clockwise: false)
// figure out the center for the right side curvature
let rightMidPointY = frame.size.height / 2.0
let halfRadius = (Constants.rightCornerRadius / 2.0)
// move up till the mid point corner radius
path.addLine(to: CGPoint(x: frame.size.width - Constants.rightCornerRadius, y: (rightMidPointY + halfRadius)))
// the destination for the curve (end point of the curve)
let rightEndPoint = CGPoint(x: frame.size.width - Constants.rightCornerRadius, y: (rightMidPointY - halfRadius))
// figure out the right side tip's control point (See: https://developer.apple.com/documentation/uikit/uibezierpath/1624351-addquadcurve)
let rightControlPoint = CGPoint(x: frame.size.width - halfRadius, y: rightMidPointY)
// add the curve for the right side tip
path.addQuadCurve(to: rightEndPoint, controlPoint: rightControlPoint)
// move up at 45 degrees
path.addLine(to: CGPoint(x: maxXRightEdge + Constants.rightEdgeRadius, y: Constants.rightEdgeRadius - halfRadius))
let topRightEdgeControlPoint = CGPoint(x: maxXRightEdge, y: Constants.rightEdgeRadius)
path.addArc(withCenter: topRightEdgeControlPoint, radius: Constants.rightEdgeRadius, startAngle: 315.degreesToRadians, endAngle: 270.degreesToRadians, clockwise: false) // straight
path.close()
// Specify the fill color and apply it to the path.
UIColor.orange.setFill()
path.fill()
// Specify a border (stroke) color.
UIColor.orange.setStroke()
path.stroke()
}
Related
So this is the navigation my designer made for our project. Height of the TabBar is 70.
What I have tried so far.
My attempt was based on tutorial from Philipp Weiss.
https://betterprogramming.pub/draw-a-custom-ios-tabbar-shape-27d298a7f4fa
Its based on idea of creating custom IBDesignable UITabBar class and overriding draw method.
#IBDesignable
class CustomizedTabBar: UITabBar {
private var shapeLayer: CALayer?
override func draw(_ rect: CGRect) {
self.addShape()
}
private func addShape() {
let shapeLayer = CAShapeLayer()
shapeLayer.path = createPath()
shapeLayer.strokeColor = UIColor.blueMenu2.cgColor
shapeLayer.fillColor = UIColor.blueMenu2.cgColor
shapeLayer.lineWidth = 1.0
if let oldShapeLayer = self.shapeLayer {
self.layer.replaceSublayer(oldShapeLayer, with: shapeLayer)
} else {
self.layer.insertSublayer(shapeLayer, at: 0)
}
self.shapeLayer = shapeLayer
}
func createPath() -> CGPath {
let height: CGFloat = 37.0
let path = UIBezierPath()
let centerWidth = self.frame.width / 2
path.move(to: CGPoint(x: 0, y: 0)) // start top left
path.addLine(to: CGPoint(x: (centerWidth - height * 2), y: 0)) // the beginning of the trough
// first curve down
path.addCurve(to: CGPoint(x: centerWidth, y: height),
controlPoint1: CGPoint(x: (centerWidth - 30), y: 0), controlPoint2: CGPoint(x: centerWidth - 35, y: height))
// second curve up
path.addCurve(to: CGPoint(x: (centerWidth + height * 2), y: 0),
controlPoint1: CGPoint(x: centerWidth + 35, y: height), controlPoint2: CGPoint(x: (centerWidth + 30), y: 0))
// complete the rect
path.addLine(to: CGPoint(x: self.frame.width, y: 0))
path.addLine(to: CGPoint(x: self.frame.width, y: self.frame.height))
path.addLine(to: CGPoint(x: 0, y: self.frame.height))
path.close()
return path.cgPath
}
I was trying to edit bezier path to reach my goal but with no success.
I am not sure if this approach can work for this specific TabBar design.
Setting height of navigation to 70 was without problem.
#IBInspectable var height: CGFloat = 70
override open func sizeThatFits(_ size: CGSize) -> CGSize {
guard let window = UIApplication.shared.keyWindow else {
return super.sizeThatFits(size)
}
var sizeThatFits = super.sizeThatFits(size)
if #available(iOS 11.0, *) {
sizeThatFits.height = height + window.safeAreaInsets.bottom
} else {
sizeThatFits.height = height
}
return sizeThatFits
}
How can I create this curved TabBar?
Do u know how to make similar shape just by using bezier curves?
To create a UIBezierPath for your desired shape...
move to 1
add 90° clockwise arc with center c1
add line to 2
add 90° clockwise arc with center c2
add 180° counter-clockwise arc with center c3
add 90° clockwise arc with center c4
add line to 3
add 90° clockwise arc with center c5
add line to 4
add 90° clockwise arc with center c6
add line to 5
add 90° clockwise arc with center c7
close path
Here is some sample code - it's a UIView subclass, with all the path elements in layoutSubviews():
class TabBarShapeView: UIView {
var shapeLayer: CAShapeLayer!
override class var layerClass: AnyClass {
return CAShapeLayer.self
}
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
private func commonInit() {
shapeLayer = self.layer as? CAShapeLayer
shapeLayer.fillColor = UIColor.clear.cgColor
shapeLayer.strokeColor = UIColor.gray.cgColor
shapeLayer.lineWidth = 1
}
override func layoutSubviews() {
super.layoutSubviews()
let middleRad: CGFloat = bounds.height - 10.0
let cornerRad: CGFloat = 12.0
let pth = UIBezierPath()
let topLeftC: CGPoint = CGPoint(x: bounds.minX + cornerRad, y: bounds.minY + cornerRad)
let topRightC: CGPoint = CGPoint(x: bounds.maxX - cornerRad, y: bounds.minY + cornerRad)
let botRightC: CGPoint = CGPoint(x: bounds.maxX - cornerRad, y: bounds.maxY - cornerRad)
let botLeftC: CGPoint = CGPoint(x: bounds.minX + cornerRad, y: bounds.maxY - cornerRad)
var pt: CGPoint!
// 1
pt = CGPoint(x: bounds.minX, y: bounds.minY + cornerRad)
pth.move(to: pt)
// c1
pth.addArc(withCenter: topLeftC, radius: cornerRad, startAngle: .pi * 1.0, endAngle: .pi * 1.5, clockwise: true)
// 2
pt = CGPoint(x: bounds.midX - middleRad, y: bounds.minY)
pth.addLine(to: pt)
// c2
pt.y += middleRad * 0.5
pth.addArc(withCenter: pt, radius: middleRad * 0.5, startAngle: -.pi * 0.5, endAngle: 0.0, clockwise: true)
// c3
pt.x += middleRad * 1.0
pth.addArc(withCenter: pt, radius: middleRad * 0.5, startAngle: .pi * 1.0, endAngle: 0.0, clockwise: false)
// c4
pt.x += middleRad * 1.0
pth.addArc(withCenter: pt, radius: middleRad * 0.5, startAngle: .pi * 1.0, endAngle: .pi * 1.5, clockwise: true)
// 3
pt = CGPoint(x: bounds.maxX - cornerRad, y: bounds.minY)
pth.addLine(to: pt)
// c5
pth.addArc(withCenter: topRightC, radius: cornerRad, startAngle: -.pi * 0.5, endAngle: 0.0, clockwise: true)
// 4
pt = CGPoint(x: bounds.maxX, y: bounds.maxY - cornerRad)
pth.addLine(to: pt)
// c6
pth.addArc(withCenter: botRightC, radius: cornerRad, startAngle: 0.0, endAngle: .pi * 0.5, clockwise: true)
// 5
pt = CGPoint(x: bounds.minX + cornerRad, y: bounds.maxY)
pth.addLine(to: pt)
// c7
pth.addArc(withCenter: botLeftC, radius: cornerRad, startAngle: .pi * 0.5, endAngle: .pi * 1.0, clockwise: true)
pth.close()
shapeLayer.path = pth.cgPath
}
}
Your subclass likely isn't working because UITabBar doesn't draw the tab bar itself in drawRect(). But makes it from multiple internal sub views.
I'd recommend using a UITabBarController, but hiding the UITabBar itself.
self.tabBarController.tabBar.hidden = true
Then putting your own custom tab bar look alike view at the button of the screen.
Adding additionalSafeAreaInsets to make the content move up out of the way of your new view, like they would the real tab bar.
Then just change the tab index yourself on button presses.
self.tabBarController.selectedIndex = 1
I tried to add a curve but not working.
I have attached the image left side is given by the designer, the right side I have done in swift.
here the if I add corner radius not working for the curve if add curve corner radius not working.
I have added left side and right side separate views, how it's possible to achieve the remove the super view background-color and curve.
extension UIView {
func roundCorners(
corners: UIRectCorner,
radius: CGFloat
) {
let path = UIBezierPath(
roundedRect: bounds,
byRoundingCorners: corners,
cornerRadii: CGSize(
width: radius,
height: radius
)
)
let mask = CAShapeLayer()
mask.path = path.cgPath
layer.mask = mask
}
}
// here I am using on UITableviewCell in layoutSubview
leftCurve.roundCorners(corners: [.topRight,.bottomRight], radius: 20)
rightCurver.roundCorners(corners: [.topLeft,.bottomLeft], radius: 20)
Here is a demo for add curve to both side using UIBezierPath
class ShapeView: UIView {
var path: UIBezierPath = UIBezierPath()
// Set you circle position for verticle.
var circleYPosition: CGFloat = 50 {
didSet {
self.setNeedsDisplay()
}
}
override func awakeFromNib() {
super.awakeFromNib()
self.backgroundColor = UIColor.clear
// Here apply shadow
}
override func draw(_ rect: CGRect) {
super.draw(rect)
// 4 corners radious
UIBezierPath(roundedRect: rect, byRoundingCorners: .allCorners, cornerRadii: CGSize(width: 10, height: 10)).addClip()
// Left - right circle
path.move(to: CGPoint(x: 0, y: self.frame.size.height))
//left side
path.addArc(withCenter: CGPoint(x: 0, y: self.circleYPosition - 15),
radius: 10,
startAngle: CGFloat((90 * Double.pi) / 180),
endAngle: CGFloat((270 * Double.pi) / 180),
clockwise: false)
path.addLine(to: CGPoint(x: 0, y: 0))
path.addLine(to: CGPoint(x: self.frame.size.width, y: 0))
path.addLine(to: CGPoint(x: self.frame.size.width, y: self.frame.size.height))
path.move(to: CGPoint(x: self.frame.size.width, y: self.frame.size.height))
//right side
path.addArc(withCenter: CGPoint(x: self.frame.size.width, y: self.circleYPosition - 15),
radius: 10,
startAngle: CGFloat((270 * Double.pi) / 180),
endAngle: CGFloat((90 * Double.pi) / 180),
clockwise: false)
path.close()
UIColor.white.setFill()
path.fill()
// Center Dash path
let dashPath = UIBezierPath()
dashPath.move(to: CGPoint(x: self.bounds.minX + 12, y: self.circleYPosition - 15))
dashPath.addLine(to: CGPoint(x: self.bounds.maxX - 12, y: self.circleYPosition - 15))
dashPath.setLineDash([5,5], count: 2, phase: 0.0)
dashPath.lineWidth = 1.0
dashPath.lineCapStyle = .butt
UIColor.lightGray.set()
dashPath.stroke()
}
}
I am trying to achieve following shape using coregraphics.
I am able to create a rounded rect
func createRoundedRect() {
let path = UIBezierPath(roundedRect: self.bounds, cornerRadius: 15.0)
// Specify the point that the path should start get drawn.
path.move(to: CGPoint(x: 0.0, y: 0.0))
// Create a line between the starting point and the bottom-left side of the view.
path.addLine(to: CGPoint(x: 0.0, y: self.frame.size.height))
// Create the bottom line (bottom-left to bottom-right).
path.addLine(to: CGPoint(x: self.frame.size.width, y: self.frame.size.height))
// Create the vertical line from the bottom-right to the top-right side.
path.addLine(to: CGPoint(x: self.frame.size.width, y: 0.0))
// Close the path. This will create the last line automatically.
path.close()
}
But I am not sure how to make a view of above shape. Any help or idea is appreciated.
You render this with just two arcs, one for the top and one for the bottom. Just use a fat lineWidth and set the strokeColor to be the same as the fillColor to achieve the desired corner radius.
For example:
#IBDesignable
class TvView: UIView {
override class var layerClass: AnyClass { CAShapeLayer.self }
var shapeLayer: CAShapeLayer { return layer as! CAShapeLayer}
#IBInspectable var curveHeight: CGFloat = 10 { didSet { setNeedsLayout() } }
#IBInspectable var cornerRadius: CGFloat = 10 { didSet { setNeedsLayout() } }
override func layoutSubviews() {
super.layoutSubviews()
shapeLayer.fillColor = UIColor.red.cgColor
shapeLayer.strokeColor = UIColor.red.cgColor
shapeLayer.path = path()?.cgPath
shapeLayer.lineWidth = cornerRadius * 2
shapeLayer.lineJoin = .round
}
func path() -> UIBezierPath? {
let rect = bounds.insetBy(dx: cornerRadius, dy: cornerRadius)
guard
rect.height > 2 * curveHeight,
rect.width > 0,
curveHeight > 0
else {
return nil
}
let angle: CGFloat = 2 * (atan2(curveHeight, rect.width / 2))
let radius = rect.width / 2 / sin(angle)
let path = UIBezierPath(arcCenter: CGPoint(x: rect.midX, y: rect.minY + radius), radius: radius, startAngle: .pi * 3 / 2 - angle, endAngle: .pi * 3 / 2 + angle, clockwise: true)
path.addArc(withCenter: CGPoint(x: rect.midX, y: rect.maxY - radius), radius: radius, startAngle: .pi / 2 - angle, endAngle: .pi / 2 + angle, clockwise: true)
path.close()
return path
}
}
Using the same color for stroke and fill, that yields:
Or, so you can see what’s going on, here it is with the stroke rendered in a different color:
I'm trying to create following progress view.
I have write following code to make this view,
class SeggyProgressView: UIView {
//--------------------------------------------------
// MARK:- Variables
//--------------------------------------------------
var arrPointsRange = [0, 500, 1000, 1500, 2000, 2500, 3000]
var currenPoint = 1586
//--------------------------------------------------
// MARK:- Init Methods
//--------------------------------------------------
init() {
super.init(frame: .zero)
self.setupView()
}
//--------------------------------------------------
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.setupView()
}
//--------------------------------------------------
override init(frame: CGRect) {
super.init(frame: frame)
self.setupView()
}
//--------------------------------------------------
// MARK:- Custom Methods
//--------------------------------------------------
func setupView() {
let path = UIBezierPath()
// Add first point to x=10 and y=1/4 of view
let x: CGFloat = 50
var lineYPoint = self.bounds.height / 4
// Add starting point
path.move(to: CGPoint(x: x, y: lineYPoint))
// Calculate line width
let lineWidth = (self.bounds.width - (x * 2) - CGFloat(10 * (self.arrPointsRange.count - 2))) / CGFloat(self.arrPointsRange.count - 2)
// Keep it to store x value of
var lineXPoint = lineWidth
// Enumerated on point range to draw above lines and haf circle
for (index, _) in arrPointsRange.enumerated() {
if index < (self.arrPointsRange.count - 2) {
lineXPoint += lineWidth
path.addLine(to: CGPoint(x: lineXPoint, y: lineYPoint))
let arcCenterX = lineXPoint + 10
path.addArc(withCenter: CGPoint(x: arcCenterX, y: lineYPoint), radius: 8, startAngle: CGFloat(180.0).toRadians(), endAngle: CGFloat(0.0).toRadians(), clockwise: true)
}
}
lineXPoint += lineWidth
// Draw line line for above
path.addLine(to: CGPoint(x: lineXPoint, y: lineYPoint))
var arcCenterX = lineXPoint + 10
// draw full circle
path.addArc(withCenter: CGPoint(x: arcCenterX, y: lineYPoint), radius: 10, startAngle: CGFloat(210.0).toRadians(), endAngle: CGFloat(150.0).toRadians(), clockwise: true)
lineYPoint += 10
// Enumerate to draw below line and half circle
for (index, _) in arrPointsRange.enumerated() {
if index < (self.arrPointsRange.count - 2) {
lineXPoint -= lineWidth
path.addLine(to: CGPoint(x: lineXPoint, y: lineYPoint))
let arcCenterX = lineXPoint - 10
path.addArc(withCenter: CGPoint(x: arcCenterX, y: lineYPoint), radius: 8, startAngle: CGFloat(0.0).toRadians(), endAngle: CGFloat(180.0).toRadians(), clockwise: true)
}
}
lineXPoint -= lineWidth
// Draw first below line
path.addLine(to: CGPoint(x: lineXPoint, y: lineYPoint))
arcCenterX = lineXPoint - 10
let y = lineYPoint - 5
// Draw first full circle
path.addArc(withCenter: CGPoint(x: arcCenterX, y: y), radius: 10, startAngle: CGFloat(30).toRadians(), endAngle: CGFloat(300).toRadians(), clockwise: true)
path.close()
let shapeLayer = CAShapeLayer()
shapeLayer.path = path.cgPath
shapeLayer.strokeColor = UIColor.darkGray.cgColor
shapeLayer.lineWidth = 3
shapeLayer.fillColor = UIColor.orange.cgColor
self.layer.addSublayer(shapeLayer)
}
}
extension CGFloat {
func toRadians() -> CGFloat {
return self * CGFloat(Double.pi) / 180.0
}
}
But I can't make exactly as above, I have following output.
Anyone know how to fix this ?
I have a class that draws a circle inside of a view. I know how to fill the circle, and I know how to have the circle only be an outline.
I want to fill the circle with stripes, so that the below circle would look like the outline with alternating stripes of red and white, but the white stripes would only look white against a white background because they would represent the lack a colour fill.
import UIKit
#IBDesignable
class Circle: UIView{
override func draw(_ rect: CGRect){
let radius = bounds.width / 4
let centerY = (bounds.maxY - bounds.minY) / 2
let centerX = (bounds.maxX - bounds.minX)/2
let centerPoint = CGPoint(x: centerX, y: centerY)
drawCircle(radius: radius, center: centerPoint)
}
private func drawCircle(radius: CGFloat, center: CGPoint){
let path = UIBezierPath()
path.addArc(withCenter: center, radius: radius, startAngle: 0, endAngle: 2*CGFloat.pi, clockwise: true)
path.close()
UIColor.red.set()
path.lineWidth = 5.0
path.stroke()
//path.fill()
}
}
Solution using clipping regions
import UIKit
#IBDesignable
class Circle: UIView{
override func draw(_ rect: CGRect){
let radius = bounds.width / 4
let centerY = (bounds.maxY - bounds.minY) / 2
let centerX = (bounds.maxX - bounds.minX)/2
let centerPoint = CGPoint(x: centerX, y: centerY)
drawCircle(radius: radius, center: centerPoint)
}
private func drawCircle(radius: CGFloat, center: CGPoint){
let path = UIBezierPath()
path.addArc(withCenter: center, radius: radius, startAngle: 0, endAngle: 2*CGFloat.pi, clockwise: true)
path.close()
UIColor.red.set()
path.lineWidth = 5.0
//////////////////////////The part the produces stripes ////////////////////
let bounds = path.bounds
let stripes = UIBezierPath()
for x in stride(from: 0, to: bounds.size.width, by: 20){
stripes.move(to: CGPoint(x: bounds.origin.x + x, y: bounds.origin.y ))
stripes.addLine(to: CGPoint(x: bounds.origin.x + x, y: bounds.origin.y + bounds.size.height ))
}
stripes.lineWidth = 10
path.addClip()
stripes.stroke()
//////////////////////////////////////////////////////////////////////////
path.stroke()
}
}