2
votes

On iOS, if a view has several layers, then can the drawRect method just choose any one layer to display, and 1 second later, choose another layer to display, to achieve an animation effect?

Right now, I have several layers, but I don't think they are the view's layers (they are just individual layers which are not sublayers of parent layer), as I just created them using

CGLayerCreateWithContext(context, self.view.bounds.size, NULL);

and in drawRect, I use

CGContextDrawLayerAtPoint(context, self.bounds.origin, layer1);

to draw the layer onto the view... it works, but isn't this like drawing a layer onto a layer (drawing a layer onto the view's layer)? Isn't there a faster way, which is to tell the the view to use layer1 or layer2, kind of like

self.layer = layer1;  

but it can't because layer is read only. Can this be achieved?

2
Are you trying to achieve an animation by having each frame in a separate layer and swapping the layers?Greg
This question doesn't make much sense, because you're confusing two different things: CGLayers and CALayers. They have similar names but they are entirely different. You cannot mix and match them. CGLayerCreateWithContext makes a CGLayer, but self.layer is a CALayer.Kurt Revis
@PartiallyFinite yes... 2 layers, for example, and animate using these 2 layers...Jeremy L
@Kurt I read that instead of drawing into BitmapContext, it is better to draw into a Layer, since it is cached in the Graphics Card, so this Layer is a CGLayer? Is it possible to draw into a CALayer too? (hm... and then I saw PartiallyFinite's answer... pretty much drawing into a view, and then extract the layer from the view...)Jeremy L
The important part of @PartiallyFinite's answer was NOT the part where he "extracts the layer from the view" -- that was just an easy way to get a layer to show you. In practice you could just as easily create a CALayer and set its backgroundColor property.Kurt Revis

2 Answers

3
votes

You are trying to draw CALayer objects using CGLayer drawing methods. These are different things, and will not work interchangeably. However, if I understood your question correctly, you don't need to use drawRect: at all to switch between layers after a period of time. Here is my basic working code example:

#import <QuartzCore/QuartzCore.h>

@interface TTView {
    NSUInteger index;
    NSArray *layers;
    CALayer *currentLayer;
}

@end

@implementation TTView

- (void)switchView {
    // Remove the currently visible layer
    [currentLayer removeFromSuperlayer];

    // Add in the next layer
    currentLayer = [layers objectAtIndex:index];
    [self.layer addSublayer:currentLayer];

    // Increment the index for next time
    index += 1;
    if (index > [layers count] - 1) {
        // If we have reached the end of the array, go back to the start. You can exit the loop here by calling a different method followed by return;
        index = 0;
    }

    // Call this method again after 1 second
    [self performSelector:@selector(switchView) withObject:nil afterDelay:1];
}

- (id)initWithCoder:(NSCoder *)aDecoder {
    // Basic initialisation. Move this to whatever method your view inits with.
    self = [super initWithCoder:aDecoder];
    if (self) {
        // Create some random objects with layers to display. Place your layers into the array here.
        UIView *a = [[UIView alloc] initWithFrame:self.bounds];
        a.backgroundColor = [UIColor redColor];
        UIView *b = [[UIView alloc] initWithFrame:self.bounds];
        b.backgroundColor = [UIColor greenColor];
        UIView *c = [[UIView alloc] initWithFrame:self.bounds];
        c.backgroundColor = [UIColor blueColor];

        // Add the layers to the array.
        layers = [[NSArray alloc] initWithObjects:a.layer, b.layer, c.layer, nil];
        // Call the method to start the loop.
        [self switchView];
    }
    return self;
}

Obviously you should replace my 3 plain coloured views with whatever layers you are planning to animate in this way, and possibly tidy up the instance variables. This is simply a basic code example that does what you want.

3
votes

Following @PartiallyFinite's example, here's a similar way to get the same sort of effect, by setting your view's layer's contents property to a CGImage of your choosing.

This is more efficient than overriding -drawRect: and drawing there, because it avoids an additional draw operation and upload to the video hardware.

#import <QuartzCore/QuartzCore.h>

@interface TTView {
    NSUInteger index;
    NSMutableArray *images;
}

@end

@implementation TTView

- (id)initWithCoder:(NSCoder *)aDecoder {
    // Basic initialisation. Move this to whatever method your view inits with.
    self = [super initWithCoder:aDecoder];
    if (self) {
        // Create some random images to display. Place your images into the array here.
        CGSize imageSize = self.bounds.size;        
        images = [[NSMutableArray alloc] init];
        [images addObject:[self imageWithSize:imageSize color:[UIColor redColor]]];
        [images addObject:[self imageWithSize:imageSize color:[UIColor greenColor]]];
        [images addObject:[self imageWithSize:imageSize color:[UIColor blueColor]]];

        [self switchView];
    }
    return self;
}

- (UIImage*)imageWithSize:(CGSize)imageSize color:(UIColor*)color {
    UIGraphicsBeginImageContext(imageSize);

    // Draw whatever you like here. 
    // As an example, we just fill the whole image with the color.
    [color set];
    UIRectFill(CGRectMake(0, 0, imageSize.width, imageSize.height));

    UIImage* image = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    return image;
}

- (void)switchView {
    UIImage* image = [images objectAtIndex:index];
    self.layer.contents = (id)(image.CGImage);

    index = (index + 1) % images.count;

    [self performSelector:@selector(switchView) withObject:nil afterDelay:1];
}

// DO NOT override -drawRect:, and DO NOT call -setNeedsDisplay.
// You're already providing the view's contents.

@end