Convert/Wrap Swift Struct as Nsvalue for Caanimation Purposes

CABasicAnimation and custom types

I thought I remembered passing by some Apple documentation when I was learning how to use Core Animation that showed how to set up animations that couldn't be handled by properticode describedes that are supplied with defined animations. Along the way I stumbled across some sample code from Apple that is described as:

A single gradient layer is displayed and continuously animated using new random colors.

That may be the answer to the specific task you already handled another way. I found it in the Documentation and API Reference within Xcode and the name of the sample code is simply Gradients. (Note that there is an original version 1.0 and an updated version 1.1 that was redone this year in April and so should be easier to use with current tools.

But, the larger question of creating a custom animation that can't be automated by Core Animation itself is to follow the example from Apple's Animation Programming Guide for Cocoa in the section Using an NSAnimation Object. It's described under the topic Subclassing NSAnimation and the recommended method is shown under the heading Smooth Animations. You override the setCurrentProgress: method so that each time it is called you first invoke Super so that NSAnimation updates the progress value, i.e., your custom animated property and then do any updating or drawing needed for the next frame of your animation. Here are the notes and example code provided by Apple in the referenced documentation:

As mentioned in “Setting and Handling Progress Marks,” you can attach a series of progress marks to an NSAnimation object and have the delegate implement the animation:didReachProgressMark: method to redraw an object at each progress mark. However, this is not the best way to animate an object. Unless you set a large number of progress marks (30 per second or more), the animation is probably going to appear jerky.

A better approach is to subclass NSAnimation and override the setCurrentProgress: method, as illustrated in Listing 4. The NSAnimation object invokes this method after each frame to change the progress value. By intercepting this message, you can perform any redrawing or updating you need for that frame. If you do override this method, be sure to invoke the implementation of super so that it can update the current progress.

Listing 4  Overriding the setCurrentProgress: method
- (void)setCurrentProgress:(NSAnimationProgress)progress
{
// Call super to update the progress value.
[super setCurrentProgress:progress];

// Update the window position.
NSRect theWinFrame = [[NSApp mainWindow] frame];
NSRect theScreenFrame = [[NSScreen mainScreen] visibleFrame];
theWinFrame.origin.x = progress *
(theScreenFrame.size.width - theWinFrame.size.width);
[[NSApp mainWindow] setFrame:theWinFrame display:YES animate:YES];
}

So basically you define a "progress value" (possibly composed of several values) that defines the state of your custom animation and write code that given the current "progress value" draws or changes what is drawn when the animation is at that particular state. Then you let NSAnimation run the animation using the normal methods of setting up an animation and it will execute your code to draw each frame of the animation at the appropriate time.

I hope that answers what you wanted to know. I doubt I could have found this easily by searching without having seen it before since I finally had to go to where I thought it might be and skim page by page through the entire topic to find it again!

Can I bind different associated values in a Swift enum to the same var?

That's an interesting case: An enum somehow isn't the right data type, because the value isn't either in radians or in degrees, both are just angles and not really different things. Also typealias Radians = Double wouldn't work because there's no unit safety.

Maybe you can use something like this though:

import Darwin

struct Angle {
enum Unit {
case Radians
case Degrees
case Rotations

var radiansFactor : Double {
switch self {
case Radians: return 1
case Degrees: return 180.0 / M_PI
case Rotations: return 1 / 2 / M_PI
}
}
}

var unit : Unit {
didSet {
value /= oldValue.radiansFactor
value *= unit.radiansFactor
}
}
var value : Double
}

func * (var lhs: Angle, rhs: Double) -> Angle {
lhs.value *= rhs
return lhs
}

var angle = Angle(unit: .Degrees, value: 180)

angle.value // 180.0
angle.unit = .Radians
angle.value // 3.141592...
angle.unit = .Rotations
angle.value // 0.5

Oh and as for the answer to your original question: No you cannot.



Related Topics



Leave a reply



Submit