Trying to create a wheel of fortune, difficulties creating wheel sections - ios

I'm new to swift and to development at all.
I'm trying to create a wheel fortune for my app but I'm having some difficulties. I need to be able to set the number of sections of the wheel each time differently. Any help how to do it?
I tried to create ellipse like that:
import UIKit
class addShape: UIView {
let context = UIGraphicsGetCurrentContext();
override func drawRect(rect: CGRect) {
CGContextSetLineWidth(context, 3.0)
//CGContextSetStrokeColorWithColor(context, UIColor.purpleColor().CGColor)
CGContextStrokeEllipseInRect(context, rect)
UIColor.redColor().set()
//Actually draw the path
CGContextStrokePath(context)
}
}
but I could not create the ellipse at all :(
I tried this way as well:
override func drawRect(rect: CGRect) {
////UTUBE
UIGraphicsBeginImageContextWithOptions(CGSize(width: 512, height: 512), false, 0)
// Define the center point of the view where you’ll rotate the arc around
let center = CGPoint(x:bounds.width/2, y: bounds.height/2)
// Calculate the radius based on the max dimension of the view.
let radius: CGFloat = max(bounds.width, bounds.height)
// Define the thickness of the arc.
let arcWidth: CGFloat = 115
/////FIRST Shape
// Define the start and end angles for the arc.
let startAngle: CGFloat = 2 * π
let endAngle: CGFloat = π / 4
// Create a path based on the center point, radius, and angles you just defined.
var path = UIBezierPath(arcCenter: center,
radius: radius/2 - arcWidth/2,
startAngle: startAngle,
endAngle: endAngle,
clockwise: true)
// Set the line width and color before finally stroking the path.
path.lineWidth = arcWidth
counterColor.setStroke()
path.stroke()
// Second Shape
let startAngle2: CGFloat = π / 4
let endAngle2: CGFloat = π / 2
// Create a path based on the center point, radius, and angles you just defined.
path = UIBezierPath(arcCenter: center,
radius: radius/2 - arcWidth/2,
startAngle: startAngle2,
endAngle: endAngle2,
clockwise: true)
path.lineWidth = arcWidth
UIColor.greenColor().setStroke()
path.stroke()
// 3rd Shape
let startAngle3: CGFloat = π / 2
let endAngle3: CGFloat = 3 * π / 4
// Create a path based on the center point, radius, and angles you just defined.
path = UIBezierPath(arcCenter: center,
radius: radius/2 - arcWidth/2,
startAngle: startAngle3,
endAngle: endAngle3,
clockwise: true)
path.lineWidth = arcWidth
UIColor.blueColor().setStroke()
path.stroke()
// 4th Shape
let startAngle4: CGFloat = 3 * π / 4
let endAngle4: CGFloat = π
// Create a path based on the center point, radius, and angles you just defined.
path = UIBezierPath(arcCenter: center,
radius: radius/2 - arcWidth/2,
startAngle: startAngle4,
endAngle: endAngle4,
clockwise: true)
path.lineWidth = arcWidth
UIColor.redColor().setStroke()
path.stroke()
// 5th Shape
let startAngle5: CGFloat = π
let endAngle5: CGFloat = 5 * π / 4
// Create a path based on the center point, radius, and angles you just defined.
path = UIBezierPath(arcCenter: center,
radius: radius/2 - arcWidth/2,
startAngle: startAngle5,
endAngle: endAngle5,
clockwise: true)
path.lineWidth = arcWidth
UIColor.yellowColor().setStroke()
path.stroke()
// 6th Shape
let startAngle6: CGFloat = 5 * π / 4
let endAngle6: CGFloat = 3 * π / 2
// Create a path based on the center point, radius, and angles you just defined.
path = UIBezierPath(arcCenter: center,
radius: radius/2 - arcWidth/2,
startAngle: startAngle6,
endAngle: endAngle6,
clockwise: true)
path.lineWidth = arcWidth
UIColor.grayColor().setStroke()
path.stroke()
// 7th Shape
let startAngle7: CGFloat = 3 * π / 2
let endAngle7: CGFloat = 7 * π / 4
// Create a path based on the center point, radius, and angles you just defined.
path = UIBezierPath(arcCenter: center,
radius: radius/2 - arcWidth/2,
startAngle: startAngle7,
endAngle: endAngle7,
clockwise: true)
path.lineWidth = arcWidth
UIColor.purpleColor().setStroke()
path.stroke()
// 8th Shape
let startAngle8: CGFloat = 7 * π / 4
let endAngle8: CGFloat = 2 * π
// Create a path based on the center point, radius, and angles you just defined.
path = UIBezierPath(arcCenter: center,
radius: radius/2 - arcWidth/2,
startAngle: startAngle8,
endAngle: endAngle8,
clockwise: true)
path.lineWidth = arcWidth
UIColor.lightGrayColor().setStroke()
path.stroke()
}
I don't think that's the way to do it, but I did not find any other way to do this.

The main problem with your first example is that your context needs to be retrieved as part of drawRect. Once you have a circle, creating n segments is just a question of drawing n-1 lines.
(Insetting for line width increases complication but looks nicer.)
class WheelView: UIView {
var pieSections: Int = 1
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override func drawRect(rect: CGRect) {
let context = UIGraphicsGetCurrentContext();
let lineWidth: CGFloat = 3.0
// Draw outline
CGContextSetLineWidth(context, lineWidth)
UIColor.redColor().set()
let smaller = rect.insetBy(dx: lineWidth / 2.0, dy: lineWidth / 2.0)
CGContextStrokeEllipseInRect(context, smaller)
let radius = smaller.width / 2.0
let segmentAngle = 2.0 / CGFloat(pieSections) * CGFloat(M_PI)
// Draw segment dividers
for index in 1...pieSections {
let drawAngle = segmentAngle * CGFloat(index)
let x = radius * cos(drawAngle) + rect.width / 2.0
let y = radius * sin(drawAngle) + rect.height / 2.0
CGContextBeginPath(context)
CGContextMoveToPoint(context, rect.width / 2, rect.height / 2)
CGContextAddLineToPoint(context, x, y)
CGContextStrokePath(context)
}
}
}

Related

doughnut chart with rounded

I want to create doughnut chart with overlapping one side with rounded corner. like
requirement is starting end should be bellow previous line and ending end should above next line. I tired to do it but last line layer show both on top. I tried with bellow code
private func drawCircle(){
var startAngle: CGFloat = .pi / -2
var endAngle: CGFloat = 0.0
drawableItems.forEach { (item) in
print(item.title)
print(startAngle)
let center = calculateCenter()
endAngle = startAngle + calculateAngles(item: item)
if endAngle > 2 * CGFloat.pi {
endAngle -= 2 * .pi
}
// Radius of the view
let radius: CGFloat = calculateRadius()
let arcWidth: CGFloat = self.arcWidth
let circlePath = UIBezierPath(arcCenter: center,
radius: radius/2 - arcWidth/2,
startAngle: startAngle,
endAngle: endAngle,
clockwise: true)
circlePath.lineCapStyle = .round
let shapeLayer: CAShapeLayer = CAShapeLayer()
shapeLayer.path = circlePath.cgPath
shapeLayer.strokeColor = item.color.cgColor
shapeLayer.lineWidth = arcWidth
shapeLayer.fillColor = UIColor.clear.cgColor
shapeLayer.lineCap = .round
layer.addSublayer(shapeLayer)
startAngle = endAngle
}
}
output gives as bellow. in which last layer with green colour show both end top instead of one bellow previous and other on top of next
please help me to do so.
The issue is that your are drawing a simple arc with a thick line and rounded ends. The two rounded ends of the last arc is always going to cover the previous and first arcs.
The solution is to draw arcs that have a concave end that doesn't cover the convex end of the previous arc.
The following UIBezierPath extension will draw such an arc:
extension UIBezierPath {
convenience init(
chartArcCenter center: CGPoint,
radius: CGFloat,
lineWidth: CGFloat,
startAngle: CGFloat,
endAngle: CGFloat,
clockwise: Bool
) {
self.init()
// Draw inner radius arc
addArc(withCenter: center, radius: radius - lineWidth/2, startAngle: startAngle, endAngle: endAngle, clockwise: clockwise)
// Draw convex endcap
var x = cos(endAngle) * radius + center.x
var y = sin(endAngle) * radius + center.y
addArc(withCenter: CGPoint(x: x, y: y), radius: lineWidth / 2, startAngle: endAngle - .pi, endAngle: endAngle, clockwise: !clockwise)
// Draw outer radius arc
addArc(withCenter: center, radius: radius + lineWidth/2, startAngle: endAngle, endAngle: startAngle, clockwise: !clockwise)
// Draw concave startcap
x = cos(startAngle) * radius + center.x
y = sin(startAngle) * radius + center.y
addArc(withCenter: CGPoint(x: x, y: y), radius: lineWidth / 2, startAngle: startAngle, endAngle: startAngle + .pi, clockwise: clockwise)
close()
}
}
Use this in your code instead of the current UIBezierPath call. You can also make changes to the drawing settings of the shape layer.
With all changes in place your method becomes:
private func drawCircle(){
var startAngle: CGFloat = .pi / -2
var endAngle: CGFloat = 0.0
// Radius of the view
let radius: CGFloat = calculateRadius()
let arcWidth: CGFloat = self.arcWidth
let center = calculateCenter()
drawableItems.forEach { (item) in
endAngle = startAngle + calculateAngles(item: item)
let circlePath = UIBezierPath(chartArcCenter: center,
radius: radius/2 - arcWidth/2,
lineWidth: arcWidth,
startAngle: startAngle,
endAngle: endAngle,
clockwise: true)
let shapeLayer: CAShapeLayer = CAShapeLayer()
shapeLayer.path = circlePath.cgPath
shapeLayer.fillColor = item.color.cgColor
layer.addSublayer(shapeLayer)
startAngle = endAngle
}
}
Also note that there's no reason for the if endAngle > 2 * CGFloat.pi { block. It's OK if the angle "wraps". And the calculations of radius, width and center only need to be done once so no need for those inside the loop.

Circular Bar Graph

One of the app I'm using has bar graph in circular shape. I've tried to create this by using UIBezierPath with arcCenter but I felt like this is completely wrong approach.
The code I'm currently using:
func drawGraph(){
var startDegrees = 120
for _ in 0...10{
let startAngle: CGFloat = radians(of: startDegrees)
let endAngle: CGFloat = radians(of: startDegrees + 5)
let center = CGPoint(x: bounds.midX, y: bounds.midY)
let radius = min(center.x, center.y) - trackBorderWidth / 2 - CGFloat(Int.random(in: 10...50))
let trackPath = UIBezierPath(arcCenter: center, radius: radius, startAngle: startAngle, endAngle: endAngle, clockwise: true)
trackPath.lineWidth = trackBorderWidth
trackPath.lineCapStyle = .square
trackBackgroundColor.set()
trackPath.stroke()
startDegrees += 7
}
}
Do you have better solutions or do you know any similar graph libraries like this?
EDIT:
So I've been trying to change my code little bit, I'm able to create something similar with this code:
func drawGraph(){
var temp:CGFloat = 120.0
for i in 0...64{
let startAngle: CGFloat = radians(of: temp)
let endAngle: CGFloat = radians(of: temp + 5)
let center = CGPoint(x: bounds.midX, y: bounds.midY)
let radius = min(center.x, center.y) - trackBorderWidth / 2 - 10
let randomNumber = CGFloat(Int.random(in: 10...20))
let trackPath = UIBezierPath(arcCenter: center, radius: radius + randomNumber / 2, startAngle: startAngle, endAngle: endAngle, clockwise: true)
trackPath.lineWidth = randomNumber
trackPath.lineCapStyle = .butt
UIColor.randomColor().set()
trackPath.stroke()
temp += 5.5
}
let circlePath = UIBezierPath(arcCenter: CGPoint(x: bounds.midX, y: bounds.midY), radius: 101, startAngle: CGFloat(0), endAngle:CGFloat(Double.pi * 2), clockwise: true)
let shapeLayer = CAShapeLayer()
shapeLayer.path = circlePath.cgPath
//change the fill color
shapeLayer.fillColor = UIColor.clear.cgColor
//you can change the stroke color
shapeLayer.strokeColor = UIColor.red.cgColor
//you can change the line width
shapeLayer.lineWidth = 3.0
self.layer.addSublayer(shapeLayer)
}

How to make circle based on percentage?

I can create a circle, but instead I need it to start at "12:00" and go to a certain percentage.
let circlePath = UIBezierPath(arcCenter: CGPoint(x: 100,y: 100), radius: CGFloat(20), startAngle: CGFloat(0), endAngle:CGFloat(M_PI * 2), clockwise: true)
let shapeLayer = CAShapeLayer()
shapeLayer.path = circlePath.CGPath
//change the fill color
shapeLayer.fillColor = UIColor.clearColor().CGColor
//you can change the stroke color
shapeLayer.strokeColor = UIColor.redColor().CGColor
//you can change the line width
shapeLayer.lineWidth = 3.0
cell.progress.layer.addSublayer(shapeLayer)
You need to adjust the start and end angles to meet your criteria.
"12:00" on the circle would be 3pi/2 or -pi/2 (- CGFloat.pi / 2). A full circle is 2pi (2 * CGFloat.pi). A percentage of that is of course CGFloat.pi * 2 * percentage. So the end would be your start + that percentage.
let circlePath = UIBezierPath(
arcCenter: CGPoint(x: 100, y: 100),
radius: 20,
startAngle: - .pi / 2,
endAngle: - .pi / 2 + .pi * 2 * percentage ,
clockwise: true
)
where percentage is some CGFloat in the range 0.0 to 1.0.

custom UIButton with UIBezierPath

I'm making my own custom UIButton.
I subcalssed the UIButton class and drawRect function
this is my code:
override func drawRect(rect: CGRect)
{
let midPoint = CGPointMake(rect.midX, rect.midY)
if !self.selected
{
let redCircle = UIBezierPath()
let redCircleRadius = rect.width - self.whiteCirlceWidth - self.spaceBetweenCirclesWidth
redCircle.addArcWithCenter(CGPointMake(rect.midX, rect.midY), radius: redCircleRadius,
startAngle: 0 , endAngle: 2 * CGFloat(M_PI), clockwise: true)
let shapeLayer = CAShapeLayer()
shapeLayer.path = redCircle.CGPath
shapeLayer.fillColor = UIColor.redColor().CGColor
shapeLayer.strokeColor = UIColor.blackColor().CGColor
shapeLayer.lineWidth = 4.0
self.layer.addSublayer(shapeLayer)
let whiteCircle = UIBezierPath()
whiteCircle.addArcWithCenter(midPoint,
radius: self.bounds.width / 2,
startAngle: 0, endAngle: 2 * CGFloat(M_PI) , clockwise: true)
UIColor.whiteColor().setFill()
whiteCircle.fill()
}
else
{
let whiteCircle = UIBezierPath()
whiteCircle.addArcWithCenter(midPoint,
radius: self.bounds.width / 2,
startAngle: 0, endAngle: 2 * CGFloat(M_PI) , clockwise: true)
UIColor.whiteColor().setFill()
whiteCircle.fill()
let blankCircle = UIBezierPath()
blankCircle.addArcWithCenter(midPoint, radius: rect.width - self.whiteCirlceWidth - self.spaceBetweenCirclesWidth,
startAngle: 0, endAngle: 2 * CGFloat(M_PI), clockwise: true)
UIColor.blackColor().setFill()
blankCircle.fill()
var rect = CGRect(origin: midPoint, size: CGSizeMake(rect.width / 2, rect.width / 2))
rect.origin.x -= rect.origin.x / 2
rect.origin.y -= rect.origin.y / 2
let roundedRect = UIBezierPath(roundedRect: rect, cornerRadius: 3)
UIColor.redColor().setFill()
roundedRect.fill()
}
}
when I click on the button I do this
#IBAction func recordBtnClicked(sender : UIButton)
{
self.recordBtn.selected = !self.recordBtn.selected
self.recordBtn.setNeedsDisplay()
}
but it doesn't change anything!.
I checked in the debugger and it does the right block of code .
I checked two blocks and they do the right drawing
what's wrong?
I figured it out it was because I added a subLayer and it stucks and I didn't remove it ' so I changed the code and didn't used the sublayer.
you can remove the subLayer each time you call drawRect

how to calculate middle points on circular arc draw using UIBezierPath path ios or swift

i'm drawing arc using UIBezierPath. how can i calculate middle point of this arc.
let center = CGPoint(x: bounds.width / 2, y: bounds.height / 2)
var startValue: CGFloat = 0
var startAngle: CGFloat = 0
var endValue: CGFloat = 0
var endAngle: CGFloat = 0
startAngle = (startValue * 2 * CGFloat(M_PI)) - CGFloat(M_PI_2)
endValue = startValue + 0.20
endAngle = (endValue * 2 * CGFloat(M_PI)) -CGFloat(M_PI_2)
let path = UIBezierPath()
path.moveToPoint(center)
path.addArcWithCenter(center, radius: radius.outer, startAngle: startAngle, endAngle: endAngle, clockwise: true)
path.fill()
path.stroke()
The midPoint angle will be the average of your startAngle and your endAngle. This calculates the point based upon the center point and the radius.outer:
let midPointAngle = (startAngle + endAngle) / 2.0
let midPoint = CGPoint(x: center.x + radius.outer * cos(midPointAngle), y: center.y - radius.outer * sin(midPointAngle))

Resources