在 swift 中分配对 Shape 的引用

Assigning reference to Shape in swift

刚开始使用 Swift(OOO 大约一周前,所以请耐心等待)。

在这个 "tutorial" (https://www.youtube.com/watch?v=bwDJspl5sbg) 之后,我能够得到以下绘制圆网格的代码。

 //get the bounds of the view we're in
    let viewWidth = self.view.bounds.width
    let viewHeight = self.view.bounds.height

    //set parameters for gride of circles
    let margin: CGFloat = 25
    let xMargin = margin
    let yMargin = margin
    let circleBreathingSpace: CGFloat = 5
    let circleLineWidth: CGFloat = 4

    let across: CGFloat = 6
    let gridRowWidth: CGFloat = viewWidth - (2.0 * margin)
    let cellWidth: CGFloat = gridRowWidth / across
    let cellHeight: CGFloat = cellWidth
    let down: CGFloat = (viewHeight - (2 * yMargin)) / cellHeight

    let circleRadius: CGFloat = (cellWidth - (2 * circleBreathingSpace)) / 2


    //draw the grid
    for xx in 0..<Int(across){
        for yy in 0..<Int(down){
            let circleX: CGFloat = xMargin + ((CGFloat(xx) + 0.5) * cellWidth)
            let circleY: CGFloat = yMargin + ((CGFloat(yy) + 0.5) * cellHeight)

            //Draw Circle [ref:  ]
            let circlePath = UIBezierPath(arcCenter: CGPoint(x: circleX,y: circleY),
                                          radius: circleRadius, startAngle: CGFloat(0),
                                          endAngle:CGFloat(M_PI * 2),
                                          clockwise: true)


            let shapeLayer = CAShapeLayer()
            shapeLayer.path = circlePath.cgPath

            //change the fill color
            shapeLayer.fillColor = UIColor.blue.cgColor
            //you can change the stroke color
            shapeLayer.strokeColor = UIColor.black.cgColor
            //you can change the line width
            shapeLayer.lineWidth = 6.0

            view.layer.addSublayer(shapeLayer)




        }
    }

问题 现在我想知道的是如何为这些单独的圈子中的每一个提供 "reference" 以便稍后操纵它们的属性。

上下文
我基本上想让这些圆圈的网格根据时间的输入慢慢消失。因此,如果我有 20 分钟的输入时间,我将得到 20 个圆圈,每个圆圈需要 60 秒才能消失。这只是我正在做的事情的背景,不一定是问题本身(尽管该领域的任何方向都不会伤害 :))

我的想法 我的想法可能是创建一个数组并将当前正在创建的圆添加到该数组中。然后我可以循环遍历数组并慢慢减小每个圆圈的大小,使它们一个接一个地缩小并消失。即使这是一个可行的解决方案,我也不太确定该怎么做。

提前致谢!

绘图和稍后更改图层的说明如下:https://www.raywenderlich.com/90488/calayer-in-ios-with-swift-10-examples 在您的情况下,您可以将图层放在一个数组中,然后按照您在想法中提到的那样更改它们。

或者您可以尝试使用 CABasicAnimation https://developer.apple.com/reference/quartzcore/cabasicanimation 并让动画 属性 成为半径。

是的,您可以为这些层创建一个数组:

var shapeLayers = [CAShapeLayer]()

然后,当您创建形状图层时,您可以将它们 add 添加到该数组。


或者,您可能想要使用数组的数组(实际上是二维数组),以便更准确地反映视觉表示。你可以像上面那样做,创建一个数组并为每一行添加形状到数组,然后创建另一个数组来保存形状图层行的那些单独的数组。但也许更优雅,您可以使用 map 调用来构建 CAShapeLayer 个对象的数组。

基本思想是,您采用一系列整数来构建一个数组。让我们考虑一个简单的例子,我想要一个包含 10 Int 个对象的简单数组,0, 2, 4, ... 18:

let array = (0 ..< 10).map { (i) -> Int in
    return i * 2
}

但这也适用于构建 CAShapeLayer 个对象的数组:

let shapeArray = (0 ..< 10).map { (xx) -> CAShapeLayer in
    let shapeLayer = CAShapeLayer()
    // ... configure that shapeLayer here
    return shapeLayer
}

因此构建 "row" 个 CAShapeLayer 个对象,xx 从 0 到 10。

然后您也可以将其嵌套在所有行的 map 中。所以内部 map returns 一个 CAShapeLayer 对象的数组,但是外部 map returns 这些数组的一个数组,并以 [[CAShapeLayer]]:

let arrayOfArrays = (0 ..< 20).map { (yy) -> [CAShapeLayer] in
    return (0 ..< 10).map { (xx) -> CAShapeLayer in
        let shapeLayer = CAShapeLayer()
        // ... configure that shapeLayer here
        return shapeLayer
    }
}

然后您可以像这样遍历那些:

for shapeLayers in arrayOfArrays {
    for shapeLayer in shapeLayers {
        // do something with `shapeLayer`, e.g.

        view.layer.addSublayer(shapeLayer)
    }
}

所以,回到你的例子:

let viewWidth = view.bounds.width
let viewHeight = view.bounds.height

//set parameters for gride of circles
let margin: CGFloat = 25
let xMargin = margin
let yMargin = margin
let circleBreathingSpace: CGFloat = 5
let circleLineWidth: CGFloat = 6

let across: CGFloat = 6
let gridRowWidth: CGFloat = viewWidth - (2.0 * margin)
let cellWidth: CGFloat = gridRowWidth / across
let cellHeight: CGFloat = cellWidth
let down: CGFloat = (viewHeight - (2 * yMargin)) / cellHeight

let circleRadius: CGFloat = (cellWidth - (2 * circleBreathingSpace)) / 2

//build the grid
let circles = (0 ..< Int(down)).map { (yy) -> [CAShapeLayer] in
    return (0 ..< Int(across)).map { (xx) -> CAShapeLayer in
        let circleX: CGFloat = xMargin + ((CGFloat(xx) + 0.5) * cellWidth)
        let circleY: CGFloat = yMargin + ((CGFloat(yy) + 0.5) * cellHeight)

        let path = UIBezierPath(arcCenter: CGPoint(x: circleX, y: circleY),
                                      radius: circleRadius, startAngle: 0,
                                      endAngle: .pi * 2,
                                      clockwise: true)

        let shapeLayer = CAShapeLayer()
        shapeLayer.path = path.cgPath

        shapeLayer.fillColor = UIColor.blue.cgColor
        shapeLayer.strokeColor = UIColor.black.cgColor
        shapeLayer.lineWidth = circleLineWidth

        return shapeLayer
    }
}

for row in circles {
    for shapeLayer in row {
        view.layer.addSublayer(shapeLayer)
    }
}

顺便说一句,如果你想让它们随着时间的推移消失,你可以考虑一个重复计时器。我会让他们动画化 clear 颜色:

var row = 0
var col = 0
Timer.scheduledTimer(withTimeInterval: 0.5, repeats: true) { timer in
    let shapeLayer = circles[col][row]

    let animation = CABasicAnimation(keyPath: "opacity")
    animation.fromValue = 1
    animation.toValue = 0
    animation.duration = 2
    shapeLayer.opacity = 0

    shapeLayer.add(animation, forKey: nil)

    row += 1
    if row >= circles[0].count {
        row = 0
        col += 1
        if col >= circles.count {
            timer.invalidate()
        }
    }
}

产生:

请注意,使用嵌套 for 循环模式(就像我们在将形状图层添加到图层层次结构时所做的那样)可能很诱人,为要删除的每个圆创建一个单独的计时器,但是引入两个问题:

首先,如果您必须取消所有这些,您真的不希望必须遍历计时器数组才能取消它们。能够 invalidate 一个单一的重复计时器更好。其次,可以通过 "timer coalescing" 取消计划在未来触发的多个计时器(其中,为了节省电量,OS 将开始成组地触发远处的计时器)。是的,您可以使用 GCD 计时器并关闭计时器合并,但重复计时器可以完全消除该问题。


我注意到您希望圆圈缩小而不是消失,就像我在上面所做的那样。因此,您可以执行以下操作:

let viewWidth = view.bounds.width
let viewHeight = view.bounds.height

//set parameters for gride of circles
let margin: CGFloat = 25
let xMargin = margin
let yMargin = margin
let circleBreathingSpace: CGFloat = 5
let circleLineWidth: CGFloat = 6

let across: CGFloat = 6
let gridRowWidth: CGFloat = viewWidth - (2.0 * margin)
let cellWidth: CGFloat = gridRowWidth / across
let cellHeight: CGFloat = cellWidth
let down: CGFloat = (viewHeight - (2 * yMargin)) / cellHeight

let circleRadius: CGFloat = (cellWidth - (2 * circleBreathingSpace)) / 2

//build the grid
let circles = (0 ..< Int(down)).map { (yy) -> [CAShapeLayer] in
    (0 ..< Int(across)).map { (xx) -> CAShapeLayer in
        let circleX: CGFloat = xMargin + ((CGFloat(xx) + 0.5) * cellWidth)
        let circleY: CGFloat = yMargin + ((CGFloat(yy) + 0.5) * cellHeight)

        let path = UIBezierPath(arcCenter: .zero,
                                radius: circleRadius, startAngle: 0,
                                endAngle: .pi * 2,
                                clockwise: true)

        let shapeLayer = CAShapeLayer()
        shapeLayer.position = CGPoint(x: circleX, y: circleY)
        shapeLayer.path = path.cgPath

        shapeLayer.fillColor = UIColor.blue.cgColor
        shapeLayer.strokeColor = UIColor.black.cgColor
        shapeLayer.lineWidth = circleLineWidth

        return shapeLayer
    }
}

//add grid to view's layer
for row in circles {
    for shapeLayer in row {
        view.layer.addSublayer(shapeLayer)
    }
}

请注意,我对其进行了调整,使路径约为 .zero,但将图层的 position 移动到了我想要圆圈的位置。当我想让它们消失时,这让我可以简单地调整图层 transform 的比例:

var row = 0
var col = 0

Timer.scheduledTimer(withTimeInterval: 0.5, repeats: true) { timer in
    let shapeLayer = circles[col][row]

    let transformAnimation = CABasicAnimation(keyPath: "transform")

    let transform = CATransform3DMakeScale(0, 0, 1)
    transformAnimation.fromValue = shapeLayer.transform
    transformAnimation.toValue = transform
    transformAnimation.duration = 2
    shapeLayer.transform = transform

    shapeLayer.add(transformAnimation, forKey: nil)

    row += 1
    if row >= circles[0].count {
        row = 0
        col += 1
        if col >= circles.count {
            timer.invalidate()
        }
    }
}

产生: