0

Created a sub class of a CATextLayer within which I attached a fadeIn animation, which I than add to a CATextLayer to which I have attached a dropThru animation. The goal to try and create matrix movie raining code effect. Works reasonably well but for the fact that it slowly but surely drives itself into the ground, I suspect cause I keep adding more and more layers. How can I detect when an layer had left the screen so I may delete it.

Here is the code...

class CATextSubLayer: CATextLayer, CAAnimationDelegate {

    private var starter:Float!
    private var ender:Float!

   required override init(layer: Any) {
    super.init(layer: layer)
    //UIFont.availableFonts()
    self.string = randomString(length: 1)
    self.backgroundColor = UIColor.black.cgColor
    self.foregroundColor = UIColor.white.cgColor
    self.alignmentMode = kCAAlignmentCenter
    self.font = CTFontCreateWithName("AvenirNextCondensed-BoldItalic" as CFString?, fontSize, nil)
    self.fontSize = 16
    self.opacity = 0.0
    makeFade()
}

required init?(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder)
    fatalError("init(coder:) has not been implemented")

}

func randomString(length: Int) -> String {

    let letters : NSString = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
    let len = UInt32(letters.length)

    var randomString = ""
    for _ in 0 ..< length {
        let rand = arc4random_uniform(len)
        var nextChar = letters.character(at: Int(rand))
        randomString += NSString(characters: &nextChar, length: 1) as String
    }

    return randomString
}

func makeFade() {
        let rands = Double(arc4random_uniform(UInt32(4)))
        let fadeInAndOut = CABasicAnimation(keyPath: "opacity")
        fadeInAndOut.duration = 16.0;
        fadeInAndOut.repeatCount = 1
        fadeInAndOut.fromValue = 0.0
        fadeInAndOut.toValue = 1
        fadeInAndOut.isRemovedOnCompletion = true
        fadeInAndOut.fillMode = kCAFillModeForwards;
        fadeInAndOut.delegate = self
        fadeInAndOut.beginTime = CACurrentMediaTime() + rands
        self.add(fadeInAndOut, forKey: "opacity")

}

func animationDidStop(_ anim: CAAnimation, finished flag: Bool) {
    self.removeAllAnimations()
}
}

With the outer loop/View Controller ..

class ViewController: UIViewController, CAAnimationDelegate {

var beeb: CATextSubLayer!
var meeb: CATextLayer!
var lines = [Int]()

override func viewDidLoad() {
    super.viewDidLoad()
    view.backgroundColor = UIColor.black
        // Do any additional setup after loading the view, typically from a nib.
    meeb = CATextLayer()
    for bing in stride(from:0, to: Int(view.bounds.width), by: 16) {
        lines.append(bing)
    }
    for _ in 0 ..< 9 {
        Timer.scheduledTimer(timeInterval: 0.5, target: self, selector: #selector(makeBeeb), userInfo: nil, repeats: true)
    }
}

func makeBeeb() {
    let rands = Double(arc4random_uniform(UInt32(4)))
    let beeb = CATextSubLayer(layer: meeb)
    let randx = Int(arc4random_uniform(UInt32(lines.count)))
    let monkey = lines[randx]
    beeb.frame = CGRect(x: monkey, y: 0, width: 16, height: 16)
    let dropThru = CABasicAnimation(keyPath: "position.y")
    dropThru.duration = 12.0;
    dropThru.repeatCount = 1
    dropThru.fromValue = 1
    dropThru.toValue = view.bounds.maxY
    dropThru.isRemovedOnCompletion = true
    dropThru.fillMode = kCAFillModeForwards;
    dropThru.beginTime = CACurrentMediaTime() + rands
    dropThru.delegate = self
    beeb.add(dropThru, forKey: "position.y")
    self.view.layer.addSublayer(beeb)
}

func animationDidStop(_ anim: CAAnimation, finished flag: Bool) {
    self.view.layer.removeAllAnimations()
}

override func didReceiveMemoryWarning() {
    super.didReceiveMemoryWarning()
    // Dispose of any resources that can be recreated.
}


}
user3069232
  • 8,587
  • 7
  • 46
  • 87

2 Answers2

1

How can I detect when an layer had left the screen so I may delete it

You have already given the CABasicAnimation a delegate which is called when the animation finishes. That is your signal to remove the layer. (You are removing animations but not the layer itself.)

matt
  • 515,959
  • 87
  • 875
  • 1,141
  • Yes, but how to get the handle for the layer with the animation? Cannot figure the missing code line. It is in anim? – user3069232 Nov 10 '16 at 20:18
  • Thanks matt for coming back so quickly too! – user3069232 Nov 10 '16 at 20:43
  • What I do is add the layer to the animation using key-value coding. I add it when I configure the animation and add it to the layer, and then in the delegate method I extract the layer and now I know what layer to remove. – matt Nov 10 '16 at 21:23
1

As far as I understand your code you can remove the layer, when it's position animation ends. In this moment it should have left the bounds of the parent view.

Btw. Removing and adding layers costs performance. Instead removing you should reuse the layer for the next animation.

clemens
  • 16,716
  • 11
  • 50
  • 65
  • Ok, this sounds good too. Reuse the layer as in reset the xCord on it. I going to give that a go! But know wait how do I get at the Layer on which the animation has just completed? – user3069232 Nov 10 '16 at 20:22
  • You can create the animation in a CATransaction, where can set a completion block. – clemens Nov 10 '16 at 20:28
  • This works!! I added this line to the VC. CATransaction.begin() CATransaction.setCompletionBlock({ beeb.removeFromSuperlayer() }) and this line to the animationDidStop within it. CATransaction.commit(). It seems to fix the memory hog that I had created! – user3069232 Nov 10 '16 at 20:40