NSAnimatablePropertyContainer

  • List:

      I'm finally playing around with Core Animation. I have a view which
    (for simplicity sake) has two properties:

    NSImage * imageToDisplay  (default nil)
    CGFloat imageAlpha    (default 0.0)

      I have the +defaultAnimationForKey: working fine. It dutifully
    returns a well-formed CABasicAnimation per the example in the
    NSAnimatablePropertyContainer protocol reference documentation.

      I want to do something like this:

    - (void)someMethodWithSomeImage:(NSImage *)image
    {
        [[myCustomView animator] setImageAlpha:0.0];
        [myCustomView setImageToDisplay:image];
        [[myCustomView animator] setImageAlpha:1.0];
    }

      When imageToDisplay is first set, the new image fades in from the
    default 0.0 beautifully. Subsequent sets fail to animate (they go
    straight to the new image). I *assume* this is because the animation
    is non-blocking and by the time the first interpolated alpha value
    gets set (and drawn) the custom view's imageToDisplay is already
    something else.

      I spent quite some time tonight trying to figure out how to set the
    'blocking mode' as NSAnimation allows ( [someNSAnimation
    setAnimationBlockingMode:NSAnimationBlocking] ) but CAAnimation,
    CABasicAnimation, etc. don't seem to allow this.

      Am I missing something or is it not possible to have a blocking
    animation in this way?

    --
    I.S.
  • Change your setImageAlpha:0.0 to message the view directly:

        [myCustomView setImageAlpha:0.0];

    That will set up the initial value, from which you'll then animate to
    1.0.

    The way the code is written now, [[myCustomView animator]
    setImageAlpha:0.0] queues up an animation from the present value to
    0.0 (which won't begin executing until control returns to the
    runloop).  Then [[myCustomView animator] setImageAlpha:1.0] schedules
    another animation to 1.0 that supersedes the animation to 0.0.  Almost
    certainly the reason that changes to "imageToDisplay" after the first
    fail to animate is that imageAlpha is already 1.0, so the animation to
    1.0, the one that replaces the animation to 0.0, has nothing to do.

    On Nov 15, 2007, at 7:22 PM, I. Savant wrote:

    > List:
    >
    > I'm finally playing around with Core Animation. I have a view which
    > (for simplicity sake) has two properties:
    >
    > NSImage * imageToDisplay  (default nil)
    > CGFloat imageAlpha    (default 0.0)
    >
    > I have the +defaultAnimationForKey: working fine. It dutifully
    > returns a well-formed CABasicAnimation per the example in the
    > NSAnimatablePropertyContainer protocol reference documentation.
    >
    > I want to do something like this:
    >
    > - (void)someMethodWithSomeImage:(NSImage *)image
    > {
    > [[myCustomView animator] setImageAlpha:0.0];
    > [myCustomView setImageToDisplay:image];
    > [[myCustomView animator] setImageAlpha:1.0];
    > }
    >
    > When imageToDisplay is first set, the new image fades in from the
    > default 0.0 beautifully. Subsequent sets fail to animate (they go
    > straight to the new image). I *assume* this is because the animation
    > is non-blocking and by the time the first interpolated alpha value
    > gets set (and drawn) the custom view's imageToDisplay is already
    > something else.
    >
    > I spent quite some time tonight trying to figure out how to set the
    > 'blocking mode' as NSAnimation allows ( [someNSAnimation
    > setAnimationBlockingMode:NSAnimationBlocking] ) but CAAnimation,
    > CABasicAnimation, etc. don't seem to allow this.
    >
    > Am I missing something or is it not possible to have a blocking
    > animation in this way?
    >
    > --
    > I.S.
  • On 11/16/07, Troy Stephens <tstephens...> wrote:
    > The way the code is written now, [[myCustomView animator]
    > setImageAlpha:0.0] queues up an animation from the present value to
    > 0.0 (which won't begin executing until control returns to the
    > runloop).  Then [[myCustomView animator] setImageAlpha:1.0] schedules
    > another animation to 1.0 that supersedes the animation to 0.0.  Almost
    > certainly the reason that changes to "imageToDisplay" after the first
    > fail to animate is that imageAlpha is already 1.0, so the animation to
    > 1.0, the one that replaces the animation to 0.0, has nothing to do.

      I sort of figured that's another part of what was going on (never
    mind that the image old image is no longer there the next time the
    view is asked to draw), but maybe I should clarify a bit. I want the
    following to happen when a new image is set:

    1 - Fade the old image out.
    2 - Swap images.
    3 - Fade the new image in.

      This requires the ability to set up a blocking animation, which (as
    suggested off-list) does not seem possible. In the same off-list
    response, I was told (paraphrasing) I should probably be forgetting
    about the alpha and just use a CATransition (fade) between old and new
    images.

      Even with that, though, the problem remains. I can't fade the old
    image out and the new one in asynchronously because the image changes
    before the animation is started. I can't help feeling I'm missing
    something ridiculously obvious.

    --
    I.S.
  • On Nov 16, 2007, at 9:49 AM, I. Savant wrote:

    > On 11/16/07, Troy Stephens <tstephens...> wrote:
    >> The way the code is written now, [[myCustomView animator]
    >> setImageAlpha:0.0] queues up an animation from the present value to
    >> 0.0 (which won't begin executing until control returns to the
    >> runloop).  Then [[myCustomView animator] setImageAlpha:1.0] schedules
    >> another animation to 1.0 that supersedes the animation to 0.0.
    >> Almost
    >> certainly the reason that changes to "imageToDisplay" after the first
    >> fail to animate is that imageAlpha is already 1.0, so the animation
    >> to
    >> 1.0, the one that replaces the animation to 0.0, has nothing to do.
    >
    > I sort of figured that's another part of what was going on (never
    > mind that the image old image is no longer there the next time the
    > view is asked to draw), but maybe I should clarify a bit. I want the
    > following to happen when a new image is set:
    >
    > 1 - Fade the old image out.
    > 2 - Swap images.
    > 3 - Fade the new image in.
    >
    > This requires the ability to set up a blocking animation, which (as
    > suggested off-list) does not seem possible. In the same off-list
    > response, I was told (paraphrasing) I should probably be forgetting
    > about the alpha and just use a CATransition (fade) between old and new
    > images.
    >
    > Even with that, though, the problem remains. I can't fade the old
    > image out and the new one in asynchronously because the image changes
    > before the animation is started. I can't help feeling I'm missing
    > something ridiculously obvious.

    If you're using layer-backed views, then the CATransition approach is
    definitely the path of least resistance.  See the "SlideshowView"
    class in the Cocoa Slides code sample, which demonstrates how to apply
    any of the available transition styles to a subview swap:

    http://developer.apple.com/samplecode/CocoaSlides/

    A simple fade-out, fade-in as you describe could also be implemented
    without using layers, in a variety of ways.  As you noted, the
    "animator"-proxy-based animation facility deals exclusively in
    asynchronous animations.  You could use the NSAnimation API to
    implement a blocking animation, but you probably don't really want to
    block your UI for the duration of the animation. Using the animator
    approach, you could, for example, animate a custom
    "transitionProgress" property from 0.0 to 1.0, and have your view's -
    drawRect: do something like:

    [[NSColor whiteColor] set];
    NSRectFill(rect);

    float t = [self transitionProgress];
    if (t < 0.5) {
        [oldImage drawAtPoint:NSZeroPoint fromRect:NSZeroRect
    operation:NSCompositeSourceOver fraction:(2.0 * (0.5 - t))];
    } else {
        [newImage drawAtPoint:NSZeroPoint fromRect:NSZeroRect
    operation:NSCompositeSourceOver fraction:(2.0 * (t - 0.5))];
    }

    That's one way it could be done.  Obviously you'd need to hold onto
    the old image for the duration of the transition, and you'd probably
    want some way for the view to be able to report whether it's in the
    middle of executing a transition to a new image, so that client code
    can plan timing accordingly.  But again, if you're using layer-backed
    views, then using a CATransition is the easy way to go.  See
    CocoaSlides for how to do that.

    Troy
  • > ... for example, animate a custom
    > "transitionProgress" property from 0.0 to 1.0, and have your view's -
    > drawRect: do something like:
    >
    > [[NSColor whiteColor] set];
    > NSRectFill(rect);
    >
    > float t = [self transitionProgress];
    > if (t < 0.5) {
    > [oldImage drawAtPoint:NSZeroPoint fromRect:NSZeroRect
    > operation:NSCompositeSourceOver fraction:(2.0 * (0.5 - t))];
    > } else {
    > [newImage drawAtPoint:NSZeroPoint fromRect:NSZeroRect
    > operation:NSCompositeSourceOver fraction:(2.0 * (t - 0.5))];
    > }
    >
    > That's one way it could be done.

      I've just tried this and it works beautifully. Thanks, Troy! The
    trick was (on setImage:) to do the following:

    [self setTransitionProgress:0.0]; // no animation, just start from 0
    oldImage = image;
    image = newImage;
    [[self animator] setTransitionProgress:1.0];

      ... I implemented your code above in my own -drawRect: and it worked
    just fine.

    --
    I.S.
previous month november 2007 next month
MTWTFSS
      1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30    
Go to today