I am trying to draw gauge ticks as in the below image in a loop.
I followed the below steps:
1. create a CAShapeLayer and CALayer
2. for loop
3. draw center top line with UIBezier
4. rotate the layer in the loop
5. add CAShapeLayer to the CALayer with .addSubLayer
5. end loop
Code
let angle = CGFloat(Double.pi / 180 * 10)
turnLayer.frame = self.bounds
self.layer.addSublayer(turnLayer)
let strokeColor = UIColor.black.cgColor
thickLayer.frame = frame
thickLayer.strokeColor = strokeColor
thickLayer.fillColor = UIColor.clear.cgColor
thickLayer.lineWidth = 8.0
for idx in -10...10 {
var p: CGPoint = CGPoint.init(x: bounds.width/2, y: bounds.height/8)
let path = UIBezierPath()
path.move(to: p)
p = CGPoint.init(x: bounds.width/2, y: bounds.height/8 + 32)
path.addLine(to: p)
thickLayer.path = path.cgPath
thickLayer.setAffineTransform(CGAffineTransform(rotationAngle: angle * CGFloat(idx)))
turnLayer.addSublayer(thickLayer)
}
Problem
I am lost about how to save and redraw the center top line
Rather than adding a bunch of rotated layers, it might be easier to have a single CAShapeLayer
with the strokes for all the ticks, e.g.,
let shapeLayer = CAShapeLayer()
shapeLayer.lineWidth = 3
shapeLayer.fillColor = UIColor.clear.cgColor
shapeLayer.strokeColor = UIColor.lightGray.cgColor
view.layer.addSublayer(shapeLayer)
let maxRadius = min(view.bounds.width, view.bounds.height) / 2
let center = CGPoint(x: view.bounds.midX, y: view.bounds.midY)
let path = UIBezierPath()
for i in 0 ... 16 {
let angle = -(CGFloat.pi / 2.0) + CGFloat.pi / 2.0 * (CGFloat(i - 8)) / 9.0
let outerRadius = maxRadius
let innerRadius = maxRadius - (i % 2 == 0 ? 20 : 10)
path.move(to: point(angle: angle, from: center, radius: outerRadius))
path.addLine(to: point(angle: angle, from: center, radius: innerRadius))
}
shapeLayer.path = path.cgPath
Where
func point(angle: CGFloat, from center: CGPoint, radius: CGFloat) -> CGPoint {
return CGPoint(x: center.x + radius * cos(angle), y: center.y + radius * sin(angle))
}
That yields:
Obviously, tweak the angle
, innerRadius
and outerRadius
to suit your purposes, but this illustrates an easier way to render all of those tick marks.
If comments, you seemed to object to the above approach, and effectively asked if it is possible to save a snapshot of a view (presumably so you can later show this image in an image view). Yes, it is:
extension UIView {
func snapshot(afterScreenUpdates: Bool = false) -> UIImage {
UIGraphicsBeginImageContextWithOptions(bounds.size, false, 0)
drawHierarchy(in: bounds, afterScreenUpdates: afterScreenUpdates)
let image = UIGraphicsGetImageFromCurrentImageContext()!
UIGraphicsEndImageContext()
return image
}
}
So, following up with your question of "can I add the shapelayer and save a snapshot", the answer is yes, you can
let shapeLayer = ...
view.layer.addSublayer(shapeLayer)
let image = view.snapshot(afterScreenUpdates: true)
Then, if you wanted to use that snapshot, you'd have to remove the CAShapeLayer
and then show the snapshot in some image view:
shapeLayer.removeFromSuperlayer()
imageView.image = image
Obviously, that's an incredibly inefficient approach (particularly because you have to use afterScreenUpdates
value of true
if you want recently added content to be included in the snapshot).
If you really wanted to efficiently create an image of all the ticks, you'd probably just stroke
the UIBezierPath
directly to an image context. All you'd need to know was the size of the UIImage
to create:
func tickImage(size: CGSize) -> UIImage {
UIGraphicsBeginImageContextWithOptions(size, false, 0)
let path = UIBezierPath()
path.lineWidth = 3
let maxRadius = size.width / 2
let center = CGPoint(x: size.width / 2, y: size.height)
for i in 0 ... 16 {
let angle = -(CGFloat.pi / 2.0) + CGFloat.pi / 2.0 * (CGFloat(i - 8)) / 9.0
let outerRadius = maxRadius
let innerRadius = maxRadius - (i % 2 == 0 ? 20 : 10)
path.move(to: point(angle: angle, from: center, radius: outerRadius))
path.addLine(to: point(angle: angle, from: center, radius: innerRadius))
}
UIColor.lightGray.setStroke()
path.stroke()
let image = UIGraphicsGetImageFromCurrentImageContext()!
UIGraphicsEndImageContext()
return image
}
That having been said, I'm not sure why you'd do that instead of the CAShapeLayer
in the beginning of this answer. I'd only do this "create UIImage
" approach if I needed to save/upload this snapshot for use at some later date. Otherwise CAShapeLayer
is a fine approach.