ScreenSaverView and Core Animation

  • Hi,

    I am trying to get core animation to work with a ScreenSaverView.
    The problem is that the layer is nil.
    I have tried to setup the coreanimation layer manually but when I check with
    the debugger it comes back empty.

    This is in the screensaverview class init

    - (id)initWithFrame:(NSRect)frame isPreview:(BOOL)isPreview
    {
        self = [super initWithFrame:frame isPreview:isPreview];
        if (self) {

      self.layer = [CALayer layer];
      self.layer.frame = NSRectToCGRect(self.bounds);
      self.layer.delegate = self;
      self.layer.needsDisplayOnBoundsChange = YES;
      self.wantsLayer = YES;
        }
        return self;
    }

    - (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)context
    {
    NSLog(@"drawlayer");
    [NSGraphicsContext saveGraphicsState];
        [NSGraphicsContext setCurrentContext:[NSGraphicsContext
    graphicsContextWithGraphicsPort:context flipped:NO]];

    [[NSImage imageNamed:@"water"]
    drawInRect:NSRectFromCGRect(CGContextGetClipBoundingBox(context))
    fromRect:NSZeroRect operation:NSCompositeSourceOver fraction:1.0];

        [NSGraphicsContext restoreGraphicsState];
    }

    Any ideas?

    Thanks
    Brian
  • On Feb 5, 2008, at 03:17, Brian Williams wrote:

    > I am trying to get core animation to work with a ScreenSaverView.
    > The problem is that the layer is nil.
    > I have tried to setup the coreanimation layer manually but when I
    > check with
    > the debugger it comes back empty.
    >
    > This is in the screensaverview class init
    >
    > - (id)initWithFrame:(NSRect)frame isPreview:(BOOL)isPreview
    > {
    > self = [super initWithFrame:frame isPreview:isPreview];
    > if (self) {
    >
    > self.layer = [CALayer layer];
    > self.layer.frame = NSRectToCGRect(self.bounds);
    > self.layer.delegate = self;
    > self.layer.needsDisplayOnBoundsChange = YES;
    > self.wantsLayer = YES;
    > }
    > return self;
    > }

    In order for your drawLayer:inContext: delegate method to be invoked,
    you need to change the frame property after your
    "needsDisplayOnBoundsChange = YES" declaration:

    self.layer = [CALayer layer];
    self.layer.delegate = self;
    self.layer.needsDisplayOnBoundsChange = YES;
    self.layer.frame = NSRectToCGRect(self.bounds);
    self.wantsLayer = YES;

    As it stands with your original code, you're changing the bounds
    before you're telling it that you want it to redisplay when the bounds
    are changed.

    /brian
  • well the way he's doing it is fine, if he adds a setNeedsDisplay at
    the end.

    you need to explicitly tell it to cache the data.

    but that wouldn't account for the layer being nil.

    if the method ever getting called??

    On Feb 5, 2008, at 1:26 AM, Brian Christensen wrote:

    > On Feb 5, 2008, at 03:17, Brian Williams wrote:
    >
    >> I am trying to get core animation to work with a ScreenSaverView.
    >> The problem is that the layer is nil.
    >> I have tried to setup the coreanimation layer manually but when I
    >> check with
    >> the debugger it comes back empty.
    >>
    >> This is in the screensaverview class init
    >>
    >> - (id)initWithFrame:(NSRect)frame isPreview:(BOOL)isPreview
    >> {
    >> self = [super initWithFrame:frame isPreview:isPreview];
    >> if (self) {
    >>
    >> self.layer = [CALayer layer];
    >> self.layer.frame = NSRectToCGRect(self.bounds);
    >> self.layer.delegate = self;
    >> self.layer.needsDisplayOnBoundsChange = YES;
    >> self.wantsLayer = YES;
    >> }
    >> return self;
    >> }
    >
    > In order for your drawLayer:inContext: delegate method to be
    > invoked, you need to change the frame property after your
    > "needsDisplayOnBoundsChange = YES" declaration:
    >
    > self.layer = [CALayer layer];
    > self.layer.delegate = self;
    > self.layer.needsDisplayOnBoundsChange = YES;
    > self.layer.frame = NSRectToCGRect(self.bounds);
    > self.wantsLayer = YES;
    >
    > As it stands with your original code, you're changing the bounds
    > before you're telling it that you want it to redisplay when the
    > bounds are changed.
    >
    > /brian
  • > On Feb 5, 2008, at 9:46, Scott Anguish wrote:
    > well the way he's doing it is fine, if he adds a setNeedsDisplay at the end.
    > you need to explicitly tell it to cache the data.
    > but that wouldn't account for the layer being nil.
    > if the method ever getting called??

    Thanks for responding Scott

    I am calling setNeedsDisplay in the startAnimation method. But I mistakenly
    left that part out.

    The layer is nil, (at least when I do a po [self layer] in the console I get
    Cannot access memory at address 0x0) at the end of the initWithFrame method.

    In startAmination it shows up, so i guess thats not a issue, but I dont
    understand why it doesn't show earlier.

    All the drawing methods get called but nothing shows on the screen, its an
    empty view.

    For the sake of completeness I'll include the entire class

    #import "FishView.h"
    #import <QuartzCore/QuartzCore.h>
    #import "NSImage-Utils.h"
    #import "FIshLayer.h"

    @interface FishView () // private

    - (void)_fireMovementTimer:(NSTimer *)timer;
    - (void)_scheduleMovementTimerOnFish:(CALayer *)fishLayer;
    - (void)_moveFish:(CALayer *)fishLayer toPosition:(CGPoint)finalPosition;
    @end

    @implementation FishView

    #define FISH_COUNT (10)

    - (id)initWithFrame:(NSRect)frame isPreview:(BOOL)isPreview
    {
        self = [super initWithFrame:frame isPreview:isPreview];
        if (self) {

      self.layer = [CALayer layer];
      self.layer.delegate = self;
      self.layer.needsDisplayOnBoundsChange = YES;
      self.layer.frame = NSRectToCGRect(self.bounds);

      self.wantsLayer = YES;

      NSLog(@"fish init");
        }
        return self;
    }

    - (void)startAnimation
    {
    NSLog(@"fish start");
    NSLog(@"layer=", self.layer);

    [self.layer setNeedsDisplay];
        NSUInteger fishIndex;
    for (fishIndex = 0; fishIndex < FISH_COUNT; fishIndex++) {
      CALayer *fishLayer = [FishLayer layer];

      fishLayer.frame = CGRectMake(random() % (NSUInteger)NSWidth(self.bounds),
    random() % (NSUInteger)NSHeight(self.bounds), 50, 100);
      fishLayer.transform = CATransform3DMakeRotation((CGFloat)(random() % 314) /
    100.0, 0.0, 0.0, 1.0);
      [fishLayer setNeedsDisplay]; // layers need to be marked as needing display
    before drawing the first time
      [self.layer addSublayer:fishLayer];
      [self performSelector:@selector(_scheduleMovementTimerOnFish:)
    withObject:fishLayer afterDelay:((float)(random() % 1000) / 10.0)];
    }
    }

    - (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)context
    {
    NSLog(@"drawlayer");
    [NSGraphicsContext saveGraphicsState];
        [NSGraphicsContext setCurrentContext:[NSGraphicsContext
    graphicsContextWithGraphicsPort:context flipped:NO]];

    [[NSImage imageNamed:@"water"]
    drawInRect:NSRectFromCGRect(CGContextGetClipBoundingBox(context))
    fromRect:NSZeroRect operation:NSCompositeSourceOver fraction:1.0];

        [NSGraphicsContext restoreGraphicsState];
    }

    #pragma mark Private API

    - (void)_scheduleMovementTimerOnFish:(CALayer *)fishLayer;
    {
    NSLog(@"fish _scheduleMovementTimerOnFish");
        // begin random movement timer and move for the first time
        [[NSTimer scheduledTimerWithTimeInterval:7.0 target:self
    selector:@selector(_fireMovementTimer:) userInfo:fishLayer repeats:YES] fire];
    }

    - (void)_fireMovementTimer:(NSTimer *)timer;
    {    NSLog(@"fish _fireMovementTimer");
        // move critter randomly
        [self _moveFish:(CALayer *)timer.userInfo toPosition:CGPointMake(random() %
    (NSUInteger)NSWidth(self.bounds), random() %
    (NSUInteger)NSWidth(self.bounds))];
    }

    - (void)_moveFish:(CALayer *)fishLayer toPosition:(CGPoint)finalPosition;
    {
    NSLog(@"fish move");
        // rotate the critter in the direction of movement, then move it
        CGFloat angleOfLineBetweenPositions = atan2((finalPosition.y -
    fishLayer.position.y), (finalPosition.x - fishLayer.position.x)) - M_PI / 2.0;
        fishLayer.transform =
    CATransform3DMakeRotation(angleOfLineBetweenPositions, 0.0, 0.0, 1.0);
        fishLayer.position = finalPosition;
    }

    //screensaver stuff

    - (void)stopAnimation
    {
        [super stopAnimation];
    }

    - (void)drawRect:(NSRect)rect
    {
        [super drawRect:rect];
    }

    - (void)animateOneFrame
    {
        return;
    }

    - (BOOL)hasConfigureSheet
    {
        return NO;
    }

    - (NSWindow*)configureSheet
    {
        return nil;
    }

    @end
  • I'll anwser my own question for the sake of the archives.

    The problem was here

    > [[NSImage imageNamed:@"water"]
    drawInRect:NSRectFromCGRect(CGContextGetClipBoundingBox(context))
    fromRect:NSZeroRect operation:NSCompositeSourceOver fraction:1.0];

    [NSImage imageNamed:@"water"} returns nil in the screen saver frame work
    apparently. It worked fine when it was in a regular app.

    so I changed it to use:

    NSBundle *programBundle = [NSBundle bundleForClass:[self class]];
    NSString *path = [programBundle pathForResource:@"water" ofType:@"jpg"];
    NSImage  *backgroundImage = [[NSImage alloc] initWithContentsOfFile:path];

    and it works. So no problem with CA, just my debugging skills :)