I'm developing an internet speed test app. Something I will do to practice and learn more about a future project.
This is my Swift Code:
import UIKit
@IBDesignable
class Arc: UIView {
@IBInspectable var dashWidth: CGFloat = 12.0
@IBInspectable var smallDashWidth: CGFloat = 5.0
override func draw(_ rect: CGRect) {
// Position and Radius of Arc
let center = CGPoint(x: bounds.width / 2, y: bounds.height / 2)
// Calculate Angles
let π = CGFloat(M_PI)
let startAngle = 3 * π / 4
let endAngle = π / 4
let radius = max(bounds.width / 1.15, bounds.height / 1.15) / 2 - dashWidth / 2
// Start Context
let context = UIGraphicsGetCurrentContext()
// MARK: Base
let arc = UIBezierPath(arcCenter: center, radius: radius,startAngle: startAngle,endAngle: endAngle,clockwise: true)
arc.addArc(withCenter: center, radius: radius, startAngle: endAngle, endAngle: startAngle, clockwise: false)
arc.lineJoinStyle = .bevel
arc.lineCapStyle = .round
arc.close()
UIColor.yellow().setStroke()
arc.lineWidth = smallDashWidth
//context!.saveGState()
context!.setLineDash(phase: 0, lengths: [0, 0], count: 2)
arc.stroke()
context!.saveGState()
// MARK: dash Arc
let dashArc = UIBezierPath()
dashArc.addArc(withCenter: center, radius: radius, startAngle: startAngle, endAngle: endAngle, clockwise: true)
// Round Line
dashArc.lineJoinStyle = .round;
// Set Stroke and Width of Dash
UIColor.white().setStroke()
dashArc.lineWidth = dashWidth
// Save Context and Set Line Dash
context!.saveGState()
context!.setLineDash(phase: 0, lengths: [2, 54], count: 2)
// Draw Line
dashArc.stroke()
// Restore Context
context!.restoreGState()
}
}
The result is this:
What I need to do:
I need to automate this line of code:
context!.setLineDash(phase: 0, lengths: [2, 54], count: 2)
The lengths are [2, 54] which numbers are added without calculation, only to get the final equation number taken to obtain this dynamically.
1: need to add dashes 12 (which may later be changed, being assigned as a variable) across the arc. The arc begins and ends at a variable angle (possibility to change later).
2: The value of dashArc.lineWidth = dashWidth
can also be changed, and is an important item to calculate the space between the 12 dashes.
3: Since all the variables presented values can be variable, which is the best way to do this calculation.
4: The first and the last dash should be at the same angle that the respective startAngle and endAngle.
What I need:
I need a calculation that looks and spreads as symmetrically as possible the dashes during the arc.
I thought of a similar calculation to this:
var numberOfDashes = 12
var perimeterArc = ?
var widthDash = 2
spacing = (perimeterArc - (widthDash * numberOfDashes)) / numberOfDashes
context!.setLineDash(phase: 0, lengths: [widthDash, spacing], count: 2)
But I do not know how to calculate the perimeterArc. Can someone help me? I could not think of anything to create a logical calculation for this in Swift 2/3.
I appreciate any tip.
Instead of trying to compute spaces directly by trying to use a dash pattern, first of all, think in terms of angles.
I lifted some code that was originally created as an example in my book PostScript By Example (page 281). I transliterated the postscript code to Swift (version 2, as version 3 can't seem to do anything useful).
I also eschewed your use of UIBezierPath mixed in with an access to the Graphics Context, since I have the feeling that there are some strange interactions between the two. UIBezierPath is intended to manage the Graphics Context under the covers.
Here is the code:
class ComputeDashes : UIView
{
let insetAmount: CGFloat = 40.0
let numberOfDashes: Int = 16
let startAngle: CGFloat = CGFloat(1.0 * M_PI / 4.0)
let endAngle: CGFloat = CGFloat(3.0 * M_PI / 4.0)
let subtendedAngle: CGFloat = CGFloat(2.0 * M_PI) - CGFloat(2.0 * M_PI / 4.0)
override init(frame: CGRect) {
super.init(frame: frame)
}
required init(coder: NSCoder) {
super.init(coder: coder)!
}
override func drawRect(rect: CGRect)
{
let insets: UIEdgeInsets = UIEdgeInsetsMake(insetAmount, insetAmount, insetAmount, insetAmount)
let newbounds: CGRect = UIEdgeInsetsInsetRect(self.bounds, insets)
let centre: CGPoint = CGPointMake(CGRectGetMidX(newbounds), CGRectGetMidY(newbounds))
let radius: CGFloat = CGRectGetWidth(newbounds) / 2.0
let context: CGContext = UIGraphicsGetCurrentContext()!
CGContextAddArc(context, centre.x, centre.y, radius, startAngle, endAngle, 1)
CGContextSetLineWidth(context, 10.0)
UIColor.magentaColor().set()
CGContextStrokePath(context)
// MARK: paint dashes
let innerRadius: CGFloat = radius * 0.75
CGContextSaveGState(context)
CGContextTranslateCTM(context, centre.x, centre.y)
let angle = subtendedAngle / CGFloat(numberOfDashes)
CGContextRotateCTM(context, endAngle)
for rot in 0...numberOfDashes {
let innerPoint: CGPoint = CGPointMake(innerRadius, 0.0)
CGContextMoveToPoint(context, innerPoint.x, innerPoint.y)
let outerPoint: CGPoint = CGPointMake(radius, 0.0)
CGContextAddLineToPoint(context, outerPoint.x, outerPoint.y)
CGContextRotateCTM(context, angle)
}
CGContextSetLineWidth(context, 2.0)
UIColor.blackColor().set()
CGContextStrokePath(context)
CGContextRestoreGState(context)
}
}
I believe that this approach is much more flexible, and avoids the tricky computations you would need to do to account for line widths in the 'on' phase of the dash pattern.
I hope this helps. Let me know what you think.