Rog
Rog

Reputation: 17170

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

Upvotes: 2

Views: 2057

Answers (2)

Aaron Brager
Aaron Brager

Reputation: 66242

Matt Long's solution works. If you'd like to keep the switch syntax, though, you can just test for nil using the case … where syntax:

override func actionForKey(key: String!) -> CAAction! {
    switch key {
    case "maxCircles", "numCircles" where 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;
    default:
        return super.actionForKey(key)
    }
}

Alternatively, you could just provide a default fromValue if there's no presentation layer:

animation.fromValue = self.presentationLayer() != nil ? self.presentationLayer().valueForKey(key) : 0;

Upvotes: 1

Matt Long
Matt Long

Reputation: 24466

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.

Upvotes: 1

Related Questions