How do I remove a CAShapeLayer and CABasicAnimation from my UIView?
Asked Answered
S

2

5

This is my code:

@objc func drawForm() {
    i = Int(arc4random_uniform(UInt32(formNames.count)))
    var drawPath = actualFormNamesFromFormClass[i]
    shapeLayer.fillColor = UIColor.clear.cgColor
    shapeLayer.strokeColor = UIColor.black.cgColor
    shapeLayer.lineWidth = 6
    shapeLayer.frame = CGRect(x: -115, y: 280, width: 350, height: 350)

    var paths: [UIBezierPath] = drawPath()
    let shapeBounds = shapeLayer.bounds
    let mirror = CGAffineTransform(scaleX: 1,
                                   y: -1)
    let translate = CGAffineTransform(translationX: 0,
                                      y: shapeBounds.size.height)
    let concatenated = mirror.concatenating(translate)

    for path in paths {
        path.apply(concatenated)
    }

    guard let path = paths.first else {
        return
    }

    paths.dropFirst()
        .forEach {
            path.append($0)
    }

    shapeLayer.transform = CATransform3DMakeScale(0.6, 0.6, 0)
    shapeLayer.path = path.cgPath

    self.view.layer.addSublayer(shapeLayer)

    strokeEndAnimation.duration = 30.0
    strokeEndAnimation.fromValue = 0.0
    strokeEndAnimation.toValue = 1.0
    shapeLayer.add(strokeEndAnimation, forKey: nil)
}

This code animates the drawing of the shapeLayer path, however I can't find anything online about removing this layer and stopping this basic animation or removing the cgPath that gets drawn... Any help would be greatly appreciated!

Shellacking answered 27/3, 2018 at 17:22 Comment(1)
BTW, you may not have noticed it, but I streamlined the code in the answer from which you got this path building logic. Namely, you can further simplify this to just let path = UIBezierPath() and paths.forEach { path.append($0) }.Edgington
E
9

You said:

I can't find anything online about removing this layer ...

It is removeFromSuperlayer().

shapeLayer.removeFromSuperlayer()

You go on to say:

... and stopping this basic animation ...

It is removeAllAnimations:

shapeLayer.removeAllAnimations()

Note, this will immediately change the strokeEnd (or whatever property you were animating) back to its previous value. If you want to "freeze" it where you stopped it, you have to grab the presentation layer (which captures the layer's properties as they are mid-animation), save the appropriate property, and then update the property of the layer upon which you are stopping the animation:

if let strokeEnd = shapeLayer.presentation()?.strokeEnd {
    shapeLayer.removeAllAnimations()
    shapeLayer.strokeEnd = strokeEnd
}

Finally, you go on to say:

... or removing the cgPath that gets drawn.

Just set it to nil:

shapeLayer.path = nil

By the way, when you're browsing for the documentation for CAShapeLayer and CABasicAnimation, don't forget to check out the documentation for their superclasses, namely and CALayer and CAAnimation » CAPropertyAnimation, respectively. Bottom line, when digging around looking for documentation on properties or methods for some particular class, you often will have to dig into the superclasses to find the relevant information.

Finally, the Core Animation Programming Guide is good intro and while its examples are in Objective-C, all of the concepts are applicable to Swift.

Edgington answered 27/3, 2018 at 19:6 Comment(0)
M
2

You can use an animation delegate CAAnimationDelegate to execute additional logic when an animation starts or ends. For example, you may want to remove a layer from its parent once a fade out animation has completed.

Below code taken from a class that implements CAAnimationDelegate on the layer, when you call The fadeOut function animates the opacity of that layer and, once the animation has completed, animationDidStop(_:finished:) removes it from its superlayer.

extension CALayer : CAAnimationDelegate  {
    func fadeOut() {
        let fadeOutAnimation = CABasicAnimation()
        fadeOutAnimation.keyPath = "opacity"
        fadeOutAnimation.fromValue = 1
        fadeOutAnimation.toValue = 0
        fadeOutAnimation.duration = 0.25
        fadeOutAnimation.delegate = self
        self.add(fadeOutAnimation,
                     forKey: "fade")
    }

    public func animationDidStop(_ anim: CAAnimation, finished flag: Bool) {
        self.removeFromSuperlayer()
    }
}
Maximilian answered 27/3, 2018 at 17:58 Comment(2)
The delegate is a great way to do something at the end of the animation. I must confess, though, that this particular pattern (animation.delegate = self) always gives me the willies because unlike most delegates, the CAAnimation delegate is a strong reference. You're fine here because isRemovedOnCompletion is true, but you're one trivial property setting away from a strong reference cycle. Nothing wrong with your code here, but just a warning for future readers who try this pattern.Edgington
This is very clever. Combined with a call to setValue(::) on the instance, to record tag information, this is super-useful approach. Thanks.Cullin

© 2022 - 2024 — McMap. All rights reserved.