CALayer drawLayer:inContext:

Apprentice
Posts: 12
Joined: 2009.01
Post: #1
Hi all,

I have been trying to wrap my head around this for several days, with lots of Googling and forum searching. Perhaps someone can point me in the right direction.

Basically, I am trying to flash a filled rectangle on the iPhone screen, at coordinates which I will provide. The best way I can come up with to do this is by using a CALayer, modifying its position and bounds, and animating its opacity property. However, I can't seem to wrap my head around it. The Apple docs are not in the least bit helpful, especially when it regards providing custom content to a CALayer instance.

Here is what I have done so far, in a separate test project, to try to completely isolate this problem ("layer" is a class instance variable):
Here's the initWithCoder method:
Code:
- (id)initWithCoder:(NSCoder *)coder {
    
    //we init the class instance the same whether it is loaded from a NIB or not
    if (self = [super initWithCoder:coder] ){
        [self setupLayers];
        [layer setNeedsDisplay];
    }
    
    
    return self;
}
This is run during initWithCoder:
Code:
-(void)setupLayers {
    layer = [CALayer layer];
    [layer retain];
    [[self layer] addSublayer:layer];
    //layer.hidden = TRUE;
    layer.frame = CGRectMake(50.0, 50.0, 50.0, 50.0);
    layer.position = CGPointMake(50.0, 50.0);
    NSLog(@"Layer's delegate is %@", [layer delegate]);
    float components[4] = {0.25, 0, .68, 1.0/3.0};

    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
    CGColorRef blueColor = CGColorCreate( colorSpace, components);
    
    layer.backgroundColor = blueColor;
    
}

Documentation I read says that the delegate needs to implement drawLayer:inContext, so I have an additional method in my view with the layer in it:
Code:
- (void)drawLayer:(CALayer *)theLayer
        inContext:(CGContextRef)theContext{
    CGContextSaveGState(theContext);
    CGContextSetRGBFillColor(theContext, 0.25, 0.35, 0.45, 0.85);
    CGContextFillRect(theContext, layer.bounds);
    CGContextRestoreGState(theContext);
}

Here's my drawRect: method in the view, which should produce a red background:
Code:
- (void)drawRect:(CGRect)rect {
    // Drawing code
    CGContextRef ctx = UIGraphicsGetCurrentContext();
    CGContextClearRect(ctx, rect);
    CGContextSetRGBFillColor(ctx, 1.0, 0.0, 0.0, 1.0);
    CGContextFillRect(ctx, rect);
}

However, the layer only draws in the upper-left corner of the screen (i.e. the origin is 0,0) and the view's drawRect method doesn't get called. I would think this code should draw a 50.0x50.0 rectangle with origin 50, 50, and if I uncomment the layer.hidden=TRUE line it shouldn't display at all. I think I can figure out the animation part once I can simply get the layer to draw itself in the right spot, and for the view to draw itself properly. From what I can tell, I shouldn't need to subclass CALayer to make this work.

Could someone point me in the right direction? Thanks!

[EDIT]: Also, if this is way too complicated for what I'm trying to do, and someone has a better idea, I'm certainly open to suggestions.
Quote this message in a reply
Apprentice
Posts: 12
Joined: 2009.01
Post: #2
So, I found a hint here, and after a while, I was able to make it work. It turns out the culprit was that you are not supposed to have the view that contains the layer also serve as the layer's delegate, which happens by default for the UIView's built-in layer. I created a delegate helper object which gets instantiated in the view's setupLayers method.

Here's the code that works, for future CALayer people.

Code:
-(void)setupLayers {
    layerDelegate = [[layerDelegate alloc] init];
    layer = [CALayer layer];
    [layer retain];
    [[self layer] addSublayer:layer];
    //layer.hidden = TRUE;
    layer.frame = CGRectMake(50.0, 50.0, 50.0, 50.0);
    layer.position = CGPointMake(50.0, 50.0);
    NSLog(@"Layer's delegate is %@", [layer delegate]);
    float components[4] = {0.25, 0, .68, 1.0/3.0};

    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
    CGColorRef blueColor = CGColorCreate( colorSpace, components);
    
    layer.backgroundColor = blueColor;
        /*****IMPORTANT*****/
    [layer setDelegate:layerDelegate];
    
}

and the delegate object contains one method only:

Code:
- (void)drawLayer:(CALayer *)theLayer
        inContext:(CGContextRef)theContext{
    CGContextSaveGState(theContext);
    CGContextSetRGBFillColor(theContext, 0.25, 0.35, 0.45, 0.85);
    CGContextFillRect(theContext, theLayer.bounds);
    CGContextRestoreGState(theContext);
}

It's too bad Apple doesn't have any sample code like this on their site - once you realize it's bad to have the view be the delegate of its own layer, which it automatically is, it's pretty straightforward.
Quote this message in a reply
Apprentice
Posts: 12
Joined: 2009.01
Post: #3
One last code fragment, which I hope will help others as they try to do this. Here's an example of the method that flashes the layer. Whenever the method is called, the animation will happen automagically.

Code:
-(IBAction)flashLayer:(id)sender {
    //add an explicit animation to our layer
    CABasicAnimation* theAnimation = [CABasicAnimation animationWithKeyPath:@"opacity"];
    theAnimation.duration=0.15;
    theAnimation.repeatCount=2;
    theAnimation.autoreverses=YES;
    theAnimation.fromValue=[NSNumber numberWithFloat:0.0];
    theAnimation.toValue=[NSNumber numberWithFloat:1.0];
    [layer addAnimation:theAnimation forKey:@"animateOpacity"];
}
Quote this message in a reply
Post Reply 

Possibly Related Threads...
Thread: Author Replies: Views: Last Post
  CALayer won't display Paul from Boston 1 3,220 Mar 26, 2010 09:08 AM
Last Post: longjumper
  UIView tree vs CALayer tree charshep 1 4,932 May 7, 2009 06:09 PM
Last Post: longjumper