iOS анимирует рисование нескольких кругов, отображается только один - PullRequest
0 голосов
/ 12 июня 2018

Основываясь на ответе на этот вопрос: Анимированный рисунок круга

Теперь я хочу отобразить два из этих кругов одновременно на одном экране, но в двух разных видах.Если я просто хочу оживить один круг, то проблем нет.Однако, если я попытаюсь добавить секунду, будет видна только вторая.

Это класс 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 голосов
/ 12 июня 2018

Основная проблема в том, что вы объявили 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

        ...
    }

}

Есть ряд других странных вещей, которые вы делаете, но именно поэтому вы получаете только один анимированный круг.


Редактировать: Вот модифицированная версия вашего кода, которая позволит вам использовать автоматическую разметку вместо фиксированных / жестко заданных размеров и позиций.Вы можете запустить это прямо на странице игровой площадки:

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()
0 голосов
/ 12 июня 2018

Кажется, вы установили круговой путь с неверными точками начала.Кроме того, вы делаете это до того, как поместите круг в представление.

Вместо этого добавьте этот код в функцию анимации:

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")

    ...
...