Search code examples
swiftcore-animation

Core animation, self.presentationLayer() is nil


I have a swift custom CALayer that has a few dynamic (actually @NSManaged properties) I have everything set up correctly and the layers actionForKey is being called.

override public func actionForKey(key: String!) -> CAAction! {
    switch key {
    case "maxCircles", "numCircles":
        let animation = CABasicAnimation(keyPath: key)
        animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear);
        animation.fromValue = self.presentationLayer().valueForKey(key);
        animation.duration = 0.2
        return animation;
    default:
        return super.actionForKey(key)
    }
}

Sometimes self.presentationLayer(). throws an exception because it is implicitly unwrapped and is nil. In objective-C the code normally just does:

[[self presentationLayer] valueForKey:key]

Which doesn't crash but I never actually realised it could call though nil and generate 0 - which feels very wrong to me. There's no guarantee I'm animating from nil.

What's the right way to access presentationLayer in Swift? Should I test for nil? i.e.:

override public func actionForKey(key: String!) -> CAAction! {
    if ( key == "maxCircles" || key == "numCircles" ) && self.presentationLayer() != nil {

        let animation = CABasicAnimation(keyPath: key)
        animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear);
        animation.fromValue = self.presentationLayer().valueForKey(key);
        animation.duration = 0.2
        return animation;
    }
    return super.actionForKey(key)
}

Solution

  • The presentationLayer property returns an optional, so yes you should test for nil. In Objective-C messaging nil is a no-op and therefore won't cause your app to crash. Remember that safety is one of the primary goals of swift, so you will need to do your own checking here. If you write your function like this, it will work similarly to the Objective-C counterpart:

    override public func actionForKey(key: String!) -> CAAction! {
        if key == "maxCircles" || key == "numCircles" {
            // Check your presentation layer first before doing anything else
            if let presoLayer = self.presentationLayer() as? CALayer {
                let animation = CABasicAnimation(keyPath: key)
                animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear);
                animation.fromValue = presoLayer.valueForKey(key);
                animation.duration = 0.2
                return animation;
            }
        }
        return super.actionForKey(key)
    }
    

    I'm assuming the rest of your code is right here and just answering the part about the optionality of presentationLayer. I switched your switch to an if because it seemed more readable to me.