Question

I have been trying to us CALayers as "sprites" in an iPhone application I'm working on. From what I have been reading, that seems like the correct thing to do here.

When I setup the layer inside a UIView class I am able to get the layer to show up on the screen: NSLog(@"GameView initWithCoder");

NSString* imagePath = [[NSBundle mainBundle] pathForResource:@"earth" ofType:@"png"];
earthImage = [[UIImage alloc] initWithContentsOfFile:imagePath];

earthLayer = [CALayer layer];
[earthLayer setContents:(id)[earthImage CGImage]];
[earthLayer setBounds:CGRectMake(0, 0, 32, 32)];
[earthLayer setPosition:CGPointMake(50, 50)];
[earthLayer setName:@"earth"];

[[self layer] addSublayer:earthLayer];

However, when I attempt to initialize the CALayer outside the UIView, my application crashes:

- (void)loadImage:(NSString*)fileName
{
  NSLog(@"GamePiece loadImage");
  NSArray* fileNameParts = [fileName componentsSeparatedByString:@"."];
  imageName = [[fileNameParts objectAtIndex:0] retain];
  NSString* ext = [fileNameParts objectAtIndex:1];

  NSString* imagePath = [[NSBundle mainBundle] pathForResource:imageName ofType:ext];
  image = [[UIImage alloc] initWithContentsOfFile:imagePath];

  layer = [CALayer layer];
  [layer setContents:(id)[image CGImage]];

  [layer setBounds:CGRectMake(0.0f, 0.0f, 32.0f, 32.0f)];
  [layer setPosition:CGPointMake(50.0f, 50.0f)];
  [layer setName:imageName];

  [fileNameParts release], fileNameParts = nil;
  [ext release], ext = nil;
}

NSLog(@"GameView initWithCoder");
earth = [[GamePiece alloc] init];
[earth loadImage:@"earth.png"];

[[self layer] addSublayer:[earth layer]];

Clearly I'm doing something wrong here, but I just don't know what that would be. I have read the Core Animation Programming Guide and also the iPhone Application Programming Guide, but they really don't seem to talk about how to initialize a CALayer other than in passing.

I just need a gentle nudge in the correct direction. I have been getting a pretty good understanding about everything else I have learned when it comes to programming the iPhone. I'm just having some issues with Graphics, Layers and Animation.

Thanks in advance!

Was it helpful?

Solution

You don't own the layer that you get from the layer method, so you have to take ownership. Sometime after your method returns, the layer is being deallocated and that is causing your crash. To cure this:

layer = [[CALayer layer] retain]; // now we explicitly take ownership of this layer

Also, don't forget to release the layer since you own it now:

// in your dealloc method
[layer release];

You should review the Memory Management Programming Guide for Cocoa for more details on when you own objects and what your responsibilities are regarding memory management.

OTHER TIPS

Jason, thank you so much for responding! However your answer doesn't seem to have any effect on the outcome of my problem.

Actually, the answer "is" something related to memory management. Not the Layer, but the name I got from the NSArray after calling componentsSeparatedByString:. If I don't release the NSArray, the code works fine. So I need to make a copy of the NSString inside the array instead of using the object in the array.

I have to say that I haven't read the Memory Management Programming Guide for Cocoa as you suggest. However, I have read several other book that talked about the standard used in Cocoa for objects created and their retain counts. I have a fair understanding, but apparently, not as good as needed yet.

I really had a suspicion that I should copy the string out of the array, but when it worked in the UIView, I just assumed that it was correct. Problem was, I wasn't keeping it like I am inside my GamePiece object.

Thank you for at least getting me to look in the right direction! I was really at wits end with this code.

To do what you wanted, here is how you could change your code. In loadImage: use this line to add the UIImage you loaded into the earth layer:

self.contents = image.CGImage;

CALayers have a contents property which you can set to a CGImage rather than going through the trouble of using Core Graphics calls to draw the image into the layer's context.

Then in the parent UIView subclass you can add the earth to the view hierarchy like this:

[self.layer addSublayer:earth];

Note that earth is itself a CALayer with an image as its contents so I add it directly as a sublayer. The other thing I always forget to do is set the layer's frame property. Otherwise its width and height will be zero and so it won't show.

In your code you created a layer but never added your layer to the view hierarchy. There is a tree if views (class UIView) and their subviews. The root of the tree is [UIWindow mainwindow] (notice that UIWindow is a subclass of UIView. Each UIView has a layer property of type CALayer which actually holds its visible content. UIView is just a wrapper around CALayer to add things like touch handling ability.

The important idea you are missing here is that for anything to be visible on the screen it has to be part of this tree of views with a UIWindow as its root and all of the views must be visible (i.e. have their hidden property set to NO) between the toplevel window and the layer in question. Only then will a layer be visible on the screen. So somewhere in your code you need to add your layer as a sublayer of something already visible by calling addSublayer:.

In your case, I'm assuming earth is an instance of a CALayer and what you have done inside the loadImage: method is create a new CALayer (with a call to [CALayer layer]) within the earth layer but then you never added that new layer to anything so it never became visible. What you did instead is attempt add the earth layer, which still doesn't have any content, to what I assume is a UIView's layer

[[self layer] addSublayer:[earth layer]];

One problem with this line is you seem to be thinking layer is a property of earth. Actually layer is a class method of CALayer which is a factory method to create new instances of CALayer. This is confusing because [self layer] actually is accessing the layer property of a UIView instance. (I'm assuming self refers to an instance of a UIView subclass.)

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top