循环保存并重绘 Swift 4

Save and Redraw in a loop Swift 4

我正尝试在循环中绘制仪表刻度,如下图所示。

我遵循了以下步骤:

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

代码

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)

}

问题

我不知道如何保存和重绘中心顶线

与其添加一堆旋转图层,不如使用单个 CAShapeLayer 来绘制所有刻度线,例如,

可能更容易
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

在哪里

func point(angle: CGFloat, from center: CGPoint, radius: CGFloat) -> CGPoint {
    return CGPoint(x: center.x + radius * cos(angle), y: center.y + radius * sin(angle))
}

产生:

显然,调整 angleinnerRadiusouterRadius 以适合您的目的,但这说明了渲染所有这些刻度线的更简单方法。


If comments, you seem to object to the above approach, and effectively asked if it is possible to save a snapshot of a view (大概是这样你以后可以在图像视图中显示这个图像)。是的,它是:

extension UIView {
    func snapshot(afterScreenUpdates: Bool = false) -> UIImage {
        UIGraphicsBeginImageContextWithOptions(bounds.size, false, 0)
        drawHierarchy(in: bounds, afterScreenUpdates: afterScreenUpdates)
        let image = UIGraphicsGetImageFromCurrentImageContext()!
        UIGraphicsEndImageContext()

        return image
    }
}

所以,跟进你 "can I add the shapelayer and save a snapshot" 的问题,答案是肯定的,你可以

let shapeLayer = ...
view.layer.addSublayer(shapeLayer)
let image = view.snapshot(afterScreenUpdates: true)

然后,如果您想使用该快照,则必须删除 CAShapeLayer 然后在某个图像视图中显示该快照:

shapeLayer.removeFromSuperlayer()
imageView.image = image

显然,这是一种非常低效的方法(特别是因为如果您希望最近添加的内容包含在快照中,您必须使用 afterScreenUpdatestrue)。

如果您真的想有效地创建所有刻度的图像,您可能只需 stroke UIBezierPath 直接到图像上下文。您只需要知道要创建的 UIImage 的大小:

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
}

话虽这么说,但我不确定您为什么要这样做而不是此答案开头的 CAShapeLayer。如果我需要 save/upload 此快照以供日后使用,我只会采用这种 "create UIImage" 方法。否则 CAShapeLayer 是一个很好的方法。