I am modifying this swift radar chart. I would like to draw an image on each of the edge vertices. I cannot figure out how to draw an image at the same time as drawing a path. Barring that, I figured I could let the shape draw itself and then somehow recover the edge vertices from its path and draw images as overlays on the shape, but I cannot figure out how to get those vertices.
So I have two questions, and I only need to know the answer to one of them to proceed.
How can I draw an image at the same time as drawing a path?
How can I pull out the edge vertices of this shape and then use them to draw overlays on the shape (the code below is closer to this version, but I'd rather be able to draw the images at the same time as drawing the path).
import SwiftUI
struct RadarChart: View {
var data: [Double]
let gridColor: Color
let dataColor: Color
private var edgeVertices: [CGPoint]
init(data: [Double], gridColor: Color = .gray, dataColor: Color = .blue) {
self.data = data
self.gridColor = gridColor
self.dataColor = dataColor
self.edgeVertices = []
}
var body: some View {
GeometryReader { geo in
ZStack {
RadarChartGrid(categories: data.count, divisions: 5)
.stroke(gridColor, lineWidth: 0.5)
.overlay(
Image(systemName: "star")
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width:20)
.position(x: geo.size.width/2, y: geo.size.height/2)
)
RadarChartPath(data: data)
.fill(dataColor.opacity(0.3))
RadarChartPath(data: data)
.stroke(dataColor, lineWidth: 2.0)
}
.padding(20)
}
}
}
struct RadarChartGrid: Shape {
let categories: Int
let divisions: Int
private func calculateEdgePoints(rect: CGRect) -> [CGPoint]{
var edgeVertices: [CGPoint] = []
let radius = min(rect.maxX - rect.midX, rect.maxY - rect.midY)
for category in 1 ... categories {
let edgePoint = CGPoint(x: rect.midX + cos(CGFloat(category) * 2 * .pi / CGFloat(categories) - .pi / 2) * radius,
y: rect.midY + sin(CGFloat(category) * 2 * .pi / CGFloat(categories) - .pi / 2) * radius)
edgeVertices.append(edgePoint)
}
return edgeVertices
}
func path(in rect: CGRect) -> Path {
let radius = min(rect.maxX - rect.midX, rect.maxY - rect.midY)
let stride = radius / CGFloat(divisions)
var path = Path()
for category in 1 ... categories {
path.move(to: CGPoint(x: rect.midX, y: rect.midY))
path.addLine(to: CGPoint(x: rect.midX + cos(CGFloat(category) * 2 * .pi / CGFloat(categories) - .pi / 2) * radius,
y: rect.midY + sin(CGFloat(category) * 2 * .pi / CGFloat(categories) - .pi / 2) * radius))
}
for step in 1 ... divisions {
let rad = CGFloat(step) * stride
path.move(to: CGPoint(x: rect.midX + cos(-.pi / 2) * rad,
y: rect.midY + sin(-.pi / 2) * rad))
for category in 1 ... categories {
path.addLine(to: CGPoint(x: rect.midX + cos(CGFloat(category) * 2 * .pi / CGFloat(categories) - .pi / 2) * rad,
y: rect.midY + sin(CGFloat(category) * 2 * .pi / CGFloat(categories) - .pi / 2) * rad))
}
}
return path
}
}
struct RadarChartPath: Shape {
let data: [Double]
func path(in rect: CGRect) -> Path {
guard
3 <= data.count,
let minimum = data.min(),
0 <= minimum,
let maximum = data.max()
else { return Path() }
let radius = min(rect.maxX - rect.midX, rect.maxY - rect.midY)
var path = Path()
for (index, entry) in data.enumerated() {
switch index {
case 0:
path.move(to: CGPoint(x: rect.midX + CGFloat(entry / maximum) * cos(CGFloat(index) * 2 * .pi / CGFloat(data.count) - .pi / 2) * radius,
y: rect.midY + CGFloat(entry / maximum) * sin(CGFloat(index) * 2 * .pi / CGFloat(data.count) - .pi / 2) * radius))
default:
path.addLine(to: CGPoint(x: rect.midX + CGFloat(entry / maximum) * cos(CGFloat(index) * 2 * .pi / CGFloat(data.count) - .pi / 2) * radius,
y: rect.midY + CGFloat(entry / maximum) * sin(CGFloat(index) * 2 * .pi / CGFloat(data.count) - .pi / 2) * radius))
}
}
path.closeSubpath()
return path
}
}
struct RadarChart_Previews: PreviewProvider {
static var previews: some View {
RadarChart(data: [1, 2, 4, 3, 5, 2, 3])
}
}
The above code produces this image. Note that I would like to put a different image on each vertex, but cannot figure out how to get the vertices.
For posterity and for anyone who also struggled in piecing together how to do stuff on the canvas, here is an implementation following what #rob mayoff said by drawing everything on the canvas.
import SwiftUI
struct RadarChart: View {
var data: [Double]
let gridColor: Color
let fillColor: Color
let strokeColor: Color
let divisions: Int
let radiusBuffer: Double
init(data: [Double], gridColor: Color = .gray, fillColor: Color = .blue, strokeColor: Color = .blue, divisions: Int = 10, radiusBuffer: Double = 0.0) {
self.data = data
self.gridColor = gridColor
self.fillColor = fillColor
self.strokeColor = strokeColor
self.divisions = divisions
self.radiusBuffer = radiusBuffer
}
var body: some View {
Canvas { context, size in
let edges = calculateEdgePoints(rect: context.clipBoundingRect, categories: data.count)
let radarCharGridPath = radarChartGridPath(in: context.clipBoundingRect, categories: data.count)
let dataPath = radarChartPath(in: context.clipBoundingRect)
context.stroke(radarCharGridPath, with: .color(gridColor), lineWidth: 0.5)
context.fill(dataPath, with: .color(fillColor.opacity(0.3)))
context.stroke(dataPath, with: .color(strokeColor), lineWidth: 2.0)
for idx in 0...data.count-1 {
var edge = edges[idx]
let image = Image(systemName: "star.fill")
let imageEdgeSize = 20.0
edge.x -= imageEdgeSize / 2
edge.y -= imageEdgeSize / 2
let rect = CGRect(origin: edge, size: CGSize(width:imageEdgeSize, height:imageEdgeSize))
context.draw(image, in: rect)
}
}.navigationBarTitle("Radar Chart")
}
private func calculateEdgePoints(rect: CGRect, categories: Int) -> [CGPoint] {
var edgeVertices: [CGPoint] = []
let radius = min(rect.maxX - rect.midX, rect.maxY - rect.midY) - radiusBuffer
print("Edgepoints radius: \(radius)")
for category in 1 ... categories {
let edgePoint = CGPoint(x: rect.midX + cos(CGFloat(category) * 2 * .pi / CGFloat(categories) - .pi / 2) * radius,
y: rect.midY + sin(CGFloat(category) * 2 * .pi / CGFloat(categories) - .pi / 2) * radius)
edgeVertices.append(edgePoint)
}
return edgeVertices
}
func radarChartGridPath(in rect: CGRect, categories: Int) -> Path {
let radius = min(rect.maxX - rect.midX, rect.maxY - rect.midY) - radiusBuffer
let stride = radius / CGFloat(divisions)
var path = Path()
for category in 1 ... categories {
path.move(to: CGPoint(x: rect.midX, y: rect.midY))
path.addLine(to: CGPoint(x: rect.midX + cos(CGFloat(category) * 2 * .pi / CGFloat(categories) - .pi / 2) * radius,
y: rect.midY + sin(CGFloat(category) * 2 * .pi / CGFloat(categories) - .pi / 2) * radius))
}
for step in 1 ... divisions {
let rad = CGFloat(step) * stride
path.move(to: CGPoint(x: rect.midX + cos(-.pi / 2) * rad,
y: rect.midY + sin(-.pi / 2) * rad))
for category in 1 ... categories {
path.addLine(to: CGPoint(x: rect.midX + cos(CGFloat(category) * 2 * .pi / CGFloat(categories) - .pi / 2) * rad,
y: rect.midY + sin(CGFloat(category) * 2 * .pi / CGFloat(categories) - .pi / 2) * rad))
}
}
return path
}
func radarChartPath(in rect: CGRect) -> Path {
guard
3 <= data.count,
let minimum = data.min(),
0 <= minimum,
let maximum = data.max()
else { return Path() }
let radius = min(rect.maxX - rect.midX, rect.maxY - rect.midY) - radiusBuffer
var path = Path()
for (index, entry) in data.enumerated() {
switch index {
case 0:
path.move(to: CGPoint(x: rect.midX + CGFloat(entry / maximum) * cos(CGFloat(index) * 2 * .pi / CGFloat(data.count) - .pi / 2) * radius,
y: rect.midY + CGFloat(entry / maximum) * sin(CGFloat(index) * 2 * .pi / CGFloat(data.count) - .pi / 2) * radius))
default:
path.addLine(to: CGPoint(x: rect.midX + CGFloat(entry / maximum) * cos(CGFloat(index) * 2 * .pi / CGFloat(data.count) - .pi / 2) * radius,
y: rect.midY + CGFloat(entry / maximum) * sin(CGFloat(index) * 2 * .pi / CGFloat(data.count) - .pi / 2) * radius))
}
}
path.closeSubpath()
return path
}
}
func makeList(_ n: Int) -> [Double] {
return (0..<n).map{ _ in Double.random(in: 1 ... 20) }
}
struct RadarChart_Previews: PreviewProvider {
static var previews: some View {
RadarChart(
data: makeList(30),
divisions: 5,
radiusBuffer: 10
)
.padding()
}
}
Related
I used this tutorial to create a hexagon shape:
https://www.hackingwithswift.com/quick-start/swiftui/how-to-draw-polygons-and-stars
My goal is to try to round the corners of my hexagon shape. I know I have to use the path.addCurve somehow, but I cannot figure out where I need to do that. I am only getting weird results. Has anyone got an idea?
struct Polygon: Shape {
let corners: Int
let smoothness: CGFloat
func path(in rect: CGRect) -> Path {
guard corners >= 2 else { return Path() }
let center = CGPoint(x: rect.width / 2, y: rect.height / 2)
var currentAngle = -CGFloat.pi / 2
let angleAdjustment = .pi * 2 / CGFloat(corners * 2)
let innerX = center.x * smoothness
let innerY = center.y * smoothness
var path = Path()
path.move(to: CGPoint(x: center.x * cos(currentAngle), y: center.y * sin(currentAngle)))
var bottomEdge: CGFloat = 0
for corner in 0 ..< corners * 2 {
let sinAngle = sin(currentAngle)
let cosAngle = cos(currentAngle)
let bottom: CGFloat
if corner.isMultiple(of: 2) {
bottom = center.y * sinAngle
path.addLine(to: CGPoint(x: center.x * cosAngle, y: bottom))
} else {
bottom = innerY * sinAngle
path.addLine(to: CGPoint(x: innerX * cosAngle, y: bottom))
}
if bottom > bottomEdge {
bottomEdge = bottom
}
currentAngle += angleAdjustment
}
let unusedSpace = (rect.height / 2 - bottomEdge) / 2
let transform = CGAffineTransform(translationX: center.x, y: center.y + unusedSpace)
return path.applying(transform)
}
}
struct Hexagon: View {
#Environment(\.colorScheme) var colorScheme
var body: some View {
Polygon(corners: 3, smoothness: 1)
.fill(.clear)
.frame(width: 76, height: 76)
}
}
Haven't found a fix but this library does what I want:
https://github.com/heestand-xyz/PolyKit
I have the following code, to draw a circle, with segments inside of it :
override func draw(_ rect: CGRect) {
super.draw(rect)
if let context = UIGraphicsGetCurrentContext()
{
let width = fmin(self.frame.size.width, self.frame.size.height)
let offset_x = abs(width - self.frame.size.width)/2
let offset_y = abs(width - self.frame.size.height)/2
let padding = CGFloat(0.5)
let radius_size = (width/2) - (padding*2)
let circle_width = radius_size/4
context.setStrokeColor(UIColor.black.cgColor)
// Draw a circle
for i in 0 ..< 4
{
let offset = CGFloat(i) * circle_width
context.strokeEllipse(in:
CGRect(
x: padding + offset + offset_x,
y: padding + offset + offset_y,
width: (radius_size - offset)*2,
height: (radius_size - offset)*2))
}
let angles: [CGFloat] = [87.0, 112.0, 150]
let angles2: [CGFloat] = [210.0, 250.0, 330.0]
let center = CGPoint(x: width/2 + offset_x, y: width/2 + offset_y)
for angle in angles {
drawLine(context: context, center: center, radius: radius_size, angle: angle)
}
for angle in angles2 {
drawLine(context: context, center: center, radius: radius_size * 3 / 4, angle: angle)
}
context.strokePath()
}
}
func drawLine(context: CGContext, center: CGPoint, radius: CGFloat, angle: CGFloat) {
context.move(to: center)
context.addLine(to: CGPoint(x: center.x + radius * cos(angle * .pi / 180), y: center.y - radius * sin(angle * .pi / 180)))
}
I want to be able, to create a centered between the segments UILabels, with rotation to the right angle, I made an example in photo editor:
Thanks in advance.
I found the answer:
func drawCurvedText(angle: CGFloat, text: String) {
drawCurvedString(
on: textView.layer,
text: NSAttributedString(
string: text,
attributes: [
NSAttributedString.Key.foregroundColor: UIColor.white,
NSAttributedString.Key.font: UIFont.systemFont(ofSize: 15)
]),
angle: -angle,
radius: 130)
}
func drawCurvedString(on layer: CALayer, text: NSAttributedString, angle: CGFloat, radius: CGFloat) {
var radAngle = angle.radians
let textSize = text.boundingRect(
with: CGSize(width: .max, height: .max),
options: [.usesLineFragmentOrigin, .usesFontLeading],
context: nil)
.integral
.size
let perimeter: CGFloat = 2 * .pi * radius
let textAngle: CGFloat = textSize.width / perimeter * 2 * .pi
var textRotation: CGFloat = 0
var textDirection: CGFloat = 0
if angle > CGFloat(10).radians, angle < CGFloat(170).radians {
// bottom string
textRotation = 0.5 * .pi
textDirection = -2 * .pi
radAngle += textAngle / 2
} else {
// top string
textRotation = 1.5 * .pi
textDirection = 2 * .pi
radAngle -= textAngle / 2
}
for c in 0..<text.length {
let letter = text.attributedSubstring(from: NSRange(c..<c+1))
let charSize = letter.boundingRect(
with: CGSize(width: .max, height: .max),
options: [.usesLineFragmentOrigin, .usesFontLeading],
context: nil)
.integral
.size
let letterAngle = (charSize.width / perimeter) * textDirection
let x = radius * cos(radAngle + (letterAngle / 2))
let y = radius * sin(radAngle + (letterAngle / 2))
let singleChar = drawText(
on: layer,
text: letter,
frame: CGRect(
x: (layer.frame.size.width / 2) - (charSize.width / 2) + x,
y: (layer.frame.size.height / 2) - (charSize.height / 2) + y,
width: charSize.width,
height: charSize.height))
layer.addSublayer(singleChar)
singleChar.transform = CATransform3DMakeAffineTransform(CGAffineTransform(rotationAngle: radAngle - textRotation))
radAngle += letterAngle
}
}
func drawText(on layer: CALayer, text: NSAttributedString, frame: CGRect) -> CATextLayer {
let textLayer = CATextLayer()
textLayer.frame = frame
textLayer.string = text
textLayer.alignmentMode = CATextLayerAlignmentMode.center
textLayer.contentsScale = UIScreen.main.scale
return textLayer
}
I'm trying to draw the following picture with each circle segment consisting of 3 buttons which can be clicked.
I've copied code from this forum, but when I try to use it, I get an error message and the code will not compile. Tried various versions (latest one printed), but none work. What am I doing wrong? Also, will the integers i and j be passed on to the call of the class CustomShapeButton?
import SwiftUI
import UIKit
struct ContentView: View {
static let segmentCount = 4
static let circleCount = 4
var i: Int = 0
var j: Int = 0
var NewButtons: CustomShapeButton
var body: some View {
ZStack {
ForEach(1..<ContentView.circleCount){ j in
ForEach(1..<ContentView.segmentCount){ i in
NewButtons = CustomShapeButton()
}
}
}
}
}
class CustomShapeButton: UIButton {
lazy var pantsShapeBezierPath: UIBezierPath = {
// Crate new path
let path = UIBezierPath()
var r = CGFloat(75.0)
r = CGFloat(50.0 + (CGFloat(j) - 1.0) * 50.0)
let center_x = CGFloat(200.0)
let center_y = CGFloat(200.0)
var arc_start = CGFloat(45.0 * CGFloat(Double.pi) / 180.0)
arc_start = CGFloat((45.0 + (CGFloat(i) - 1.0) * 90.0)) * CGFloat(Double.pi) / 180.0
let arc_length = CGFloat(90.0 * CGFloat(Double.pi) / 180.0)
let arc_width = CGFloat(45.0)
let line0Target_x = center_x + r * CGFloat(cos(Double(arc_start)))
let line0Target_y = center_y + r * CGFloat(sin(Double(arc_start)))
let line1Target_x = center_x + (r + arc_width) * CGFloat(cos(Double(arc_start + arc_length)))
let line1Target_y = center_x + (r + arc_width) * CGFloat(sin(Double(arc_start + arc_length)))
path.move(to: CGPoint(x: line0Target_x, y: line0Target_y))
path.addArc(center: CGPoint(x: center_x, y: center_y), radius: r, startAngle: Angle(radians: Double(arc_start)), endAngle: Angle(radians: Double(arc_start + arc_length)), clockwise: false)
path.addLine(to: CGPoint(x: line1Target_x, y: line1Target_y))
path.addArc(center: CGPoint(x: center_x, y: center_y), radius: (r + arc_width), startAngle: Angle(radians: Double(arc_start + arc_length)), endAngle: Angle(radians: Double(arc_start)), clockwise: true)
path.addLine(to: CGPoint(x: line0Target_x, y: line0Target_y))
path.close()
return path
}()
override func draw(_ rect: CGRect) {
super.draw(rect)
// Set shape filling color
UIColor.red.setFill()
// Fill the shape
pantsShapeBezierPath.fill()
}
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
// Handling touch events
if (pantsShapeBezierPath.contains(point)) {
return self
} else {
return nil
}
}
Here you go. You just need to implement the button style first then use it. This result is 9 buttons. I would suggest having 9 functions for each button and not using the same one.
struct CustomShapeButtonStyle: ButtonStyle {
var j: Int
var i: Int
var fillColor: Color
func makeBody(configuration: Self.Configuration) -> some View {
GeometryReader { geometry in
Path { path in
var r = CGFloat(75.0)
r = CGFloat(50.0 + (CGFloat(self.j) - 1.0) * 50.0)
let center_x = CGFloat(200.0)
let center_y = CGFloat(200.0)
var arc_start = CGFloat(45.0 * CGFloat(Double.pi) / 180.0)
arc_start = CGFloat((45.0 + (CGFloat(self.i) - 1.0) * 90.0)) * CGFloat(Double.pi) / 180.0
let arc_length = CGFloat(90.0 * CGFloat(Double.pi) / 180.0)
let arc_width = CGFloat(45.0)
let line0Target_x = center_x + r * CGFloat(cos(Double(arc_start)))
let line0Target_y = center_y + r * CGFloat(sin(Double(arc_start)))
let line1Target_x = center_x + (r + arc_width) * CGFloat(cos(Double(arc_start + arc_length)))
let line1Target_y = center_x + (r + arc_width) * CGFloat(sin(Double(arc_start + arc_length)))
path.move(to: CGPoint(x: line0Target_x, y: line0Target_y))
path.addArc(center: CGPoint(x: center_x, y: center_y), radius: r, startAngle: Angle(radians: Double(arc_start)), endAngle: Angle(radians: Double(arc_start + arc_length)), clockwise: false)
path.addLine(to: CGPoint(x: line1Target_x, y: line1Target_y))
path.addArc(center: CGPoint(x: center_x, y: center_y), radius: (r + arc_width), startAngle: Angle(radians: Double(arc_start + arc_length)), endAngle: Angle(radians: Double(arc_start)), clockwise: true)
path.addLine(to: CGPoint(x: line0Target_x, y: line0Target_y))
path.closeSubpath()
}.fill(self.fillColor)
}
}
}
struct ContentView: View {
static let segmentCount = 4
static let circleCount = 4
var body: some View {
ZStack {
ForEach(1..<Self.circleCount) { j in
ForEach(1..<Self.segmentCount) { i in
Button(action: {
print("I was clicked")
}) {
Text("") // Just a placeholder
}.buttonStyle(CustomShapeButtonStyle(j: j, i: i, fillColor: Color(red: 177/255, green: 152/255, blue: 177/255)))
}
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
Your code is mixing both UIKit and SwiftUI in an improper way. You can't do this in a body statement:
NewButtons = CustomShapeButton()
Everything inside of the body function has to be a View. This is an assignment statement.
Next, CustomShapeButton is a UIButton subclass. You can't use that here, either. If you want to use this random internet code (which I do not suggest), you will have to wrap it inside of a UIViewRepresentable. Instead, consider following Apple's guide on how to create custom path-based Views.
https://developer.apple.com/tutorials/swiftui/drawing-paths-and-shapes
Once you can draw the shapes that you want, wrap it in a Button:
Button(action: { /* do your action */ }) {
CircularCutoutShape()
}
This question already has answers here:
Animate CAShapeLayer path change
(1 answer)
Circular Progress Bars in IOS
(7 answers)
Closed 4 years ago.
I am trying to implement a custom circular analysis view.
The view should be circular but cut-off.
Goal:
My Code:
let circlePath = UIBezierPath(ovalIn: CGRect(x: innerRect.minX, y: innerRect.minY, width: innerRect.width, height: innerRect.height))
if trackBackgroundColor != UIColor.clear {
trackBackgroundColor.setFill()
circlePath.fill();
}
if trackBorderWidth > 0 {
circlePath.lineWidth = trackBorderWidth
trackBorderColor.setStroke()
circlePath.stroke()
}
// progress Drawing
let progressPath = UIBezierPath()
let progressRect: CGRect = CGRect(x: innerRect.minX, y: innerRect.minY, width: innerRect.width, height: innerRect.height)
let center = CGPoint(x: progressRect.midX, y: progressRect.midY)
let radius = progressRect.width / 2.0
let startAngle:CGFloat = clockwise ? CGFloat(-internalProgress * Double.pi / 180.0) : CGFloat(constants.twoSeventyDegrees * Double.pi / 180)
let endAngle:CGFloat = clockwise ? CGFloat(constants.twoSeventyDegrees * Double.pi / 180) : CGFloat(-internalProgress * Double.pi / 180.0)
progressPath.addArc(withCenter: center, radius:radius, startAngle:startAngle, endAngle:endAngle, clockwise:!clockwise)
Current Output:
How do I draw the custom circle described as Goal.
Here's a rough implementation of what you need. This draws the two arcs.
class CircularProgressView: UIView {
var trackBackgroundColor = UIColor.lightGray
var trackBorderWidth: CGFloat = 10
var progressColor = UIColor.red
var percent: Double = 0 {
didSet {
setNeedsDisplay()
}
}
// Adjust these to meet your needs. 90 degrees is the bottom of the circle
static let startDegrees: CGFloat = 120
static let endDegrees: CGFloat = 60
override func draw(_ rect: CGRect) {
let startAngle: CGFloat = radians(of: CircularProgressView.startDegrees)
let endAngle: CGFloat = radians(of: CircularProgressView.endDegrees)
let progressAngle = radians(of: CircularProgressView.startDegrees + (360 - CircularProgressView.startDegrees + CircularProgressView.endDegrees) * CGFloat(max(0.0, min(percent, 1.0))))
let center = CGPoint(x: bounds.midX, y: bounds.midY)
let radius = min(center.x, center.y) - trackBorderWidth / 2 - 10
print(startAngle, endAngle, progressAngle)
let trackPath = UIBezierPath(arcCenter: center, radius: radius, startAngle: startAngle, endAngle: endAngle, clockwise: true)
let progressPath = UIBezierPath(arcCenter: center, radius: radius, startAngle: startAngle, endAngle: progressAngle, clockwise: true)
trackPath.lineWidth = trackBorderWidth
trackPath.lineCapStyle = .round
progressPath.lineWidth = trackBorderWidth
progressPath.lineCapStyle = .round
trackBackgroundColor.set()
trackPath.stroke()
progressColor.set()
progressPath.stroke()
}
private func radians(of degrees: CGFloat) -> CGFloat {
return degrees / 180 * .pi
}
}
let progress = CircularProgressView(frame: CGRect(x: 0, y: 0, width: 500, height: 400))
progress.backgroundColor = .white
progress.percent = 0.95
I have to draw 12 points in a circle with different radiant. From point 1, draw a line to point 2, from point 2 to 3, etc. The lines will not be a problem.
I can not find a formula to find the 12 * (x,y), but I think it's something with polar coordinates / circle?
Is anyone working with it and maybe want to share with me?
See the picture which might explain better than I can:
This is the result I got:
And This is my Playground:
//: Playground - noun: a place where people can play
import Foundation
import UIKit
class DemoView: UIView {
override func draw(_ rect: CGRect) {
let origin = CGPoint(x: frame.size.width / 2, y: frame.size.height / 2)
let radius = frame.size.width / 2
self.createCircle(origin: origin, radius: radius)
self.addLinesInCircle(origin: origin, radius: radius)
}
func createCircle(origin: CGPoint, radius: CGFloat) {
let path = UIBezierPath()
path.addArc(withCenter: origin, radius: radius, startAngle: 0, endAngle: CGFloat(2 * Double.pi), clockwise: true)
path.close()
UIColor.orange.setFill()
path.fill()
}
func addLinesInCircle(origin: CGPoint, radius: CGFloat) {
let path = UIBezierPath()
let incrementAngle: CGFloat = CGFloat.pi / 6
let ratios: [CGFloat] = [3/6, 5/6, 3/6, 1/6, 5/6, 2/6, 4/6, 2/6, 4/6, 4/6, 4/6, 4/6, 3/6]
for (index, ratio) in ratios.enumerated() {
let point = CGPoint(x: origin.x + cos(CGFloat(index) * incrementAngle) * radius * ratio,
y: origin.y + sin(CGFloat(index) * incrementAngle) * radius * ratio)
if index == 0 {
path.move(to: point)
} else {
path.addLine(to: point)
}
}
path.close()
UIColor.black.set()
path.stroke()
}
}
let demoView = DemoView(frame: CGRect(x: 0, y: 0, width: 320, height: 320))