b2cloud

    5th July 2011

    Introduction to CALayer, CoreGraphics and CABasicAnimation

    Guides | Tutorial By 5 years ago

    Today we’re going to look at CALayers and CoreGraphics. These are great for custom drawing, they are lightweight, powerful and most importantly, FAST.

    I will take you through a basic introduction to CALayers, custom drawing using CoreGraphics and a CABasicAnimation. This tutorial is targeted for people who are very confident with regular UIView subclasses and Objective-C, so you may need to research some coding concepts that won’t be explicitly explained here.

    Begin a View-based project and import the QuartzCore Framework. Go to your view controller and make sure you include it at the top.

    #import 

    Add the viewDidAppear: method into your view controllers implementation file, this is where most of our code will go.

    First we’ll make a standard CALayer (if you’re one of those lazy people that just copy pastes the code from a tutorial scroll down to the bottom for the full code)

    CALayer* layer = [CALayer layer];

    By default the layer will have a CGRectZero frame, so set that so we can actually see it, also set a background colour so we can confirm it’s being displayed properly at this stage.

    [layer setFrame:CGRectMake(100, 100, 100, 100)];
    [layer setBackgroundColor:[UIColor yellowColor].CGColor];

    CALayers work similar to UIViews in the way they have a superlayer and sublayers. Currently no layers contain our new layer, so let’s add it to the ViewController’s view.

    [self.view.layer addSublayer:layer];

    Run the project, we should (hopefully) get a yellow square in the middle of our screen. We will now set up our layer for custom drawing by setting it’s delegate.

    [layer setDelegate:self];

    Because the delegate is a ViewController this will work fine, as the selector used is drawLayer:inContext:. Make sure the delegate isn’t a subclass of UIView or CALayer, as these implement the drawLayer: selector themselves, and the delegate will interfere with it.

    Add the following method for custom drawing, stick an NSLog() in there to check if we’re getting called

    - (void) drawLayer:(CALayer*) layer inContext:(CGContextRef) ctx
    {
    	NSLog(@"draw");
    }

    Run the project. Hmmm that’s interesting, we are setting up our delegate properly but our method isn’t being run? This is because we need to invoke the drawing method on our layer, which is a piece of cake, you have probably seen this used on UIViews too. Under the line where we set the delegate, use the setNeedsDisplay method on our layer.

    [layer setNeedsDisplay];

    Run the project again and we get ‘draw’ logged to our console. Paste the following into the drawLayer:inContext: method. CoreGraphics is a bucketload just by itself, so I won’t go into much detail about these functions.

    NSLog(@"draw");
    
    CGContextSetStrokeColorWithColor(ctx, [UIColor blackColor].CGColor);
    CGContextSetLineWidth(ctx, 5);
    
    CGContextMoveToPoint(ctx, 5, 5);
    CGContextAddLineToPoint(ctx, 95, 95);
    
    CGContextStrokePath(ctx);

    Run the project, we get a nice black line going through our square.

    This square looks a little sharp for me, so we will round of it’s corners, very easily might I add. In the viewDidAppear: method add this after initialising the layer.

    [layer setCornerRadius:15];
    [layer setBorderColor:[UIColor blackColor].CGColor];
    [layer setBorderWidth:5];

    These methods are available to any CALayer, useful for colouring UIButtons, a quick example (you will need to do a bit of work to get it to tint on it’s highlighted state):

    UIButton* button = [UIButton buttonWithType:UIButtonTypeCustom];
    [button setFrame:CGRectMake(10, 10, 300, 44)];
    [button setBackgroundColor:[UIColor orangeColor]];
    [button.layer setCornerRadius:15];
    [button.layer setBorderColor:[UIColor blackColor].CGColor];
    [button.layer setBorderWidth:2];
    [self.view addSubview:button];

    Now let’s animate that rounded corner yellow square. In our viewDidAppear: after the layer is created, add:

    CABasicAnimation* animation = [CABasicAnimation animationWithKeyPath:@"opacity"];
    [animation setDuration:1.0];
    [animation setRepeatCount:INT_MAX];
    [animation setFromValue:[NSNumber numberWithFloat:0.0]];
    [animation setToValue:[NSNumber numberWithFloat:1.0]];
    [layer addAnimation:animation forKey:nil];

    This CABasicAnimation will fade the layer forever (nearly). The keyPath specifies which property of the path will be changed gradually over the duration of the animation. As we have set the keyPath to @”opacity”, it is like using the layer.opacity = 0.5; method. This get’s a bit tricky with structures, like transforms, so refer to Apple’s website for a list of these

    For example, rotation:

    CABasicAnimation* animation = [CABasicAnimation animationWithKeyPath:@"transform.rotation"];
    [animation setDuration:2.0];
    [animation setRepeatCount:INT_MAX];
    [animation setFromValue:[NSNumber numberWithFloat:0.0]];
    [animation setToValue:[NSNumber numberWithFloat:M_PI * 2.0]];
    [layer addAnimation:animation forKey:nil];

    And for the lazy people, all the code we wrote:

    - (void) viewDidAppear:(BOOL)animated
    {
    	CALayer* layer = [CALayer layer];
    
    	[layer setCornerRadius:15];
    	[layer setBorderColor:[UIColor blackColor].CGColor];
    	[layer setBorderWidth:5];
    
    	[layer setFrame:CGRectMake(100, 100, 100, 100)];
    	[layer setBackgroundColor:[UIColor yellowColor].CGColor];
    
    	[self.view.layer addSublayer:layer];
    
    	[layer setDelegate:self];
    	[layer setNeedsDisplay];
    
    	CABasicAnimation* animation = [CABasicAnimation animationWithKeyPath:@"transform.rotation"]
    	[animation setDuration:2.0];
    	[animation setRepeatCount:INT_MAX];
    	[animation setFromValue:[NSNumber numberWithFloat:0] ];
    	[animation setToValue:[NSNumber numberWithFloat:M_PI * 2.0]];
    	[layer addAnimation:animation forKey:nil];
    }
    
    - (void) drawLayer:(CALayer*) layer inContext:(CGContextRef) ctx
    {
    	CGContextSetStrokeColorWithColor(ctx, [UIColor blackColor].CGColor);
    	CGContextSetLineWidth(ctx, 5);
    
    	CGContextMoveToPoint(ctx, 5, 5);
    	CGContextAddLineToPoint(ctx, 95, 95);
    
    	CGContextStrokePath(ctx);
    }
    • Cam

      Followed this almost word for word (except I added the layer to a UIView) and drawLayer is never getting called…any ideas?

      • tom

        Hi Cam,
        Is your layer initialisation code happening within a UIView subclass? If this is the case then setting the CALayer’s delegate to self (self being a subclass of UIView) will not work as UIView has it’s own drawLayer implementation. Instead create a subclass of NSObject to handle the drawLayer code, and set the CALayer’s delegate to that instead. Also make sure your method follows the signature “- (void) drawLayer:(CALayer*) layer inContext:(CGContextRef) ctx”

        You also need to make sure you invoke the draw code the first time by calling “[layer setNeedsDisplay];” on your layer

    • What a great little tutorial. I managed to get my rotation animation working very quickly by reading this. Thanks!

    • bsdshell

      Simple and effective

    Recommended Posts

    Yammer integrations in ReactJS

    Post by 5 years ago

    I am writing this blog while I am working on a project for our client’s intranet website. The client requires the website has the ability to share, like and write comments in the website through

    Got an idea?

    We help entrepreneurs, organizations and established brands from around
    the country bring ideas to life. We would love to hear from you!