iOS动画绘制多个圆圈,只显示一个

时间:2018-06-12 17:02:22

标签: ios swift uiview cabasicanimation

根据这个问题的答案:Animate drawing of a circle

我现在想在同一个屏幕上同时显示其中两个圆圈但是在两个不同的视图中。如果我只想为一个圆圈制作动画,那就没有问题了。但是,如果我尝试添加第二个,只有第二个可见。

这是Circle类:

import UIKit

var circleLayer: CAShapeLayer!

class Circle: UIView {

init(frame: CGRect, viewLayer: CALayer) {

    super.init(frame: frame)
    self.backgroundColor = UIColor.clear

    // Use UIBezierPath as an easy way to create the CGPath for the layer.
    // The path should be the entire circle.
    let circlePath = UIBezierPath(arcCenter: CGPoint(x: frame.size.width / 2.0, y: frame.size.height / 2.0), radius: (frame.size.width - 10)/2, startAngle: (3.0 * .pi)/2.0, endAngle: CGFloat((3.0 * .pi)/2.0 + (.pi * 2.0)), clockwise: true)

    // Setup the CAShapeLayer with the path, colors, and line width
    circleLayer = CAShapeLayer()
    circleLayer.path = circlePath.cgPath
    circleLayer.fillColor = UIColor.clear.cgColor
    circleLayer.strokeColor = UIColor.green.cgColor
    circleLayer.lineWidth = 8.0;

    // Don't draw the circle initially
    circleLayer.strokeEnd = 0.0

    // Add the circleLayer to the view's layer's sublayers
    viewLayer.addSublayer(circleLayer)
}

required init?(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder)
}

func animateCircle(duration: TimeInterval) {

    // We want to animate the strokeEnd property of the circleLayer
    let animation = CABasicAnimation(keyPath: "strokeEnd")
    // Set the animation duration appropriately
    animation.duration = duration

    // Animate from 0 (no circle) to 1 (full circle)
    animation.fromValue = 0
    animation.toValue = 1

    // Do a linear animation (i.e The speed of the animation stays the same)
    animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear)

    // Set the circleLayer's strokeEnd property to 1.0 now so that it's the
    // Right value when the animation ends
    circleLayer.strokeEnd = 1.0

    // Do the actual animation
    circleLayer.add(animation, forKey: "animateCircle")
}

func removeCircle() {
    circleLayer.strokeEnd = 0.0
}

}

以下是我从ViewController中调用它的方法:

var rythmTimer: Circle?
var adrenalineTimer: Circle?

override func viewDidLoad() {

// Create two timers as circles
    self.rythmTimer = Circle(frame: CGRect(x: 0, y: 0, width: 100, height: 100), viewLayer: view1.layer)
    if let rt = rythmTimer {
        view1.addSubview(rt)
        rt.center = CGPoint(x: self.view1.bounds.midX, y: self.view1.bounds.midY);
    }

    self.adrenalineTimer = Circle(frame: CGRect(x: 0, y: 0, width: 100, height: 100), viewLayer: view2.layer)

    if let at = adrenalineTimer {
        view2.addSubview(at)
        at.center = CGPoint(x: self.view2.bounds.midX, y: self.view2.bounds.midY)
    }
}

如果我删除了adrenalineTimer的代码,我可以看到rythmTimer绘制的圆圈。如果我保留它,rythmTimer将显示在view2而不是view1中,并且将具有rythmTimer的持续时间/颜色

2 个答案:

答案 0 :(得分:0)

您似乎设置了具有错误原点的圆路径。此外,您可以在将圆圈放在视图上之前执行此操作。

将此代码添加到animate函数中:

func animateCircle(duration: TimeInterval) {

    let circlePath = UIBezierPath(arcCenter: CGPoint(x: self.frame.origin.x + self.frame.size.width / 2, y: self.frame.origin.y + self.frame.size.height / 2), radius: (frame.size.width - 10)/2, startAngle: (3.0 * .pi)/2.0, endAngle: CGFloat((3.0 * .pi)/2.0 + (.pi * 2.0)), clockwise: true)

    circleLayer.path = circlePath.cgPath

    // We want to animate the strokeEnd property of the circleLayer
    let animation = CABasicAnimation(keyPath: "strokeEnd")

    ...

答案 1 :(得分:0)

主要问题是您已在班级之外宣布circleLayer

import UIKit

var circleLayer: CAShapeLayer!

class Circle: UIView {

    init(frame: CGRect, viewLayer: CALayer) {

        super.init(frame: frame)
        self.backgroundColor = UIColor.clear

        ...
    }

}

结果是您只有 ONE circleLayer对象(实例)。

如果你将移到类中,那么Circle的每个实例都会有自己的circleLayer实例:

import UIKit

class Circle: UIView {

    var circleLayer: CAShapeLayer!

    init(frame: CGRect, viewLayer: CALayer) {

        super.init(frame: frame)
        self.backgroundColor = UIColor.clear

        ...
    }

}

你还有很多其他奇怪的事情,但这就是为什么你只能获得一个动画圈。

编辑: 以下是您的代码的修改版本,允许您使用自动布局而不是固定/硬编码的尺寸和位置。您可以直接在Playground页面中运行它:

import UIKit
import PlaygroundSupport

class Circle: UIView {

    var circleLayer: CAShapeLayer!

    override init(frame: CGRect) {

        super.init(frame: frame)
        self.backgroundColor = UIColor.clear

        // Setup the CAShapeLayer with colors and line width
        circleLayer = CAShapeLayer()
        circleLayer.fillColor = UIColor.clear.cgColor
        circleLayer.strokeColor = UIColor.green.cgColor
        circleLayer.lineWidth = 8.0;

        // We haven't set the path yet, so don't draw initially
        circleLayer.strokeEnd = 0.0

        // Add the layer to the self's layer
        self.layer.addSublayer(circleLayer)
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
    }

    override func layoutSubviews() {
        super.layoutSubviews()

        // Use UIBezierPath as an easy way to create the CGPath for the layer.
        // The path should be the entire circle.
        // this will update whenever the frame size changes (makes it easy to use with auto-layout)
        let circlePath = UIBezierPath(arcCenter: CGPoint(x: frame.size.width / 2.0, y: frame.size.height / 2.0), radius: (frame.size.width - 10)/2, startAngle: (3.0 * .pi)/2.0, endAngle: CGFloat((3.0 * .pi)/2.0 + (.pi * 2.0)), clockwise: true)
        circleLayer.path = circlePath.cgPath

    }

    func animateCircle(duration: TimeInterval) {

        // We want to animate the strokeEnd property of the circleLayer
        let animation = CABasicAnimation(keyPath: "strokeEnd")
        // Set the animation duration appropriately
        animation.duration = duration

        // Animate from 0 (no circle) to 1 (full circle)
        animation.fromValue = 0
        animation.toValue = 1

        // Do a linear animation (i.e The speed of the animation stays the same)
        animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear)

        // Set the circleLayer's strokeEnd property to 1.0 now so that it's the
        // Right value when the animation ends
        circleLayer.strokeEnd = 1.0

        // Do the actual animation
        circleLayer.add(animation, forKey: "animateCircle")
    }

    func removeCircle() {
        circleLayer.strokeEnd = 0.0
    }

}

class MyViewController : UIViewController {

    var rythmTimer: Circle?
    var adrenalineTimer: Circle?

    var theButton: UIButton = {
        let b = UIButton()
        b.setTitle("Tap Me", for: .normal)
        b.translatesAutoresizingMaskIntoConstraints = false
        b.backgroundColor = .red
        return b
    }()

    var view1: UIView = {
        let v = UIView()
        v.translatesAutoresizingMaskIntoConstraints = false
        v.backgroundColor = .blue
        return v
    }()

    var view2: UIView = {
        let v = UIView()
        v.translatesAutoresizingMaskIntoConstraints = false
        v.backgroundColor = .orange
        return v
    }()

    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = UIColor.white

        view.addSubview(theButton)
        // constrain button to Top: 32 and centerX
        NSLayoutConstraint.activate([
            theButton.topAnchor.constraint(equalTo: view.topAnchor, constant: 32.0),
            theButton.centerXAnchor.constraint(equalTo: view.centerXAnchor, constant: 0.0),
            ])

        // add an action for the button tap
        theButton.addTarget(self, action: #selector(didTap(_:)), for: .touchUpInside)

        view.addSubview(view1)
        view.addSubview(view2)

        NSLayoutConstraint.activate([
            view1.widthAnchor.constraint(equalToConstant: 120.0),
            view1.heightAnchor.constraint(equalTo: view1.widthAnchor, multiplier: 1.0),
            view1.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            view1.topAnchor.constraint(equalTo: theButton.bottomAnchor, constant: 40.0),
            view2.widthAnchor.constraint(equalTo: view1.widthAnchor, multiplier: 1.0),
            view2.heightAnchor.constraint(equalTo: view1.widthAnchor, multiplier: 1.0),
            view2.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            view2.topAnchor.constraint(equalTo: view1.bottomAnchor, constant: 40.0),
            ])

        rythmTimer = Circle(frame: CGRect.zero)
        adrenalineTimer = Circle(frame: CGRect.zero)

        if let rt = rythmTimer,
            let at = adrenalineTimer {
            view1.addSubview(rt)
            view2.addSubview(at)
            rt.translatesAutoresizingMaskIntoConstraints = false
            at.translatesAutoresizingMaskIntoConstraints = false
            NSLayoutConstraint.activate([
                rt.widthAnchor.constraint(equalToConstant: 100.0),
                rt.heightAnchor.constraint(equalToConstant: 100.0),
                rt.centerXAnchor.constraint(equalTo: view1.centerXAnchor),
                rt.centerYAnchor.constraint(equalTo: view1.centerYAnchor),
                at.widthAnchor.constraint(equalToConstant: 100.0),
                at.heightAnchor.constraint(equalToConstant: 100.0),
                at.centerXAnchor.constraint(equalTo: view2.centerXAnchor),
                at.centerYAnchor.constraint(equalTo: view2.centerYAnchor),
                ])
        }

    }

    // on button tap, change the text in the label(s)
    @objc func didTap(_ sender: Any?) -> Void {
        if let rt = rythmTimer,
            let at = adrenalineTimer {
            rt.removeCircle()
            at.removeCircle()
            rt.animateCircle(duration: 2.0)
            at.animateCircle(duration: 2.0)
        }
    }

}

// Present the view controller in the Live View window
PlaygroundPage.current.liveView = MyViewController()