CoreAnimation Sample Code: ArtGallery

ArtGallery is a simple Cocoa application which uses CoreAnimation layers for layout and display of images. The design is a single custom view which is backed by a single root layer. Additional layers are added to the root layer for navigation, images, text, and so on (Update: 1.0a).

ArtGallery 1
Inset image from Wikipedia


The additional layers are not layer-backed views, they're raw CALayers — all hosted inside a single view. This is a Leopard project and includes examples of:

- Basic layout with CoreAnimation layers
- Subclassing CALayer
- CALayer delegate methods
- Making a view layer-backed
- NSGradient
- Conversion between AppKit (NS) and Quartz (CG) geometry types
- Objective-C 2.0 properties and garbage collection
- Loading data from property lists inside the application bundle

ArtGallery 2
Inset image from Wikipedia


The user experience is a gallery wall metaphor which focuses on one image at a time. The next and previous images are visible from the edges of the view, but initially darkened. As the user mouses over the right and left sides of the view, navigation appears inline and the edges are illuminated. All of the animations are stock effects provided by CoreAnimation.

Download: ArtGallery Xcode Project (52k)
Minor update: ArtGallery 1.0a uses pre-defined constants for contentsGravity and better default content size.

Enjoy.

(Credit to Lucas Newman of Delicious Monster for help with some of the trickier parts of CoreAnimation.)
Design Element
CoreAnimation Sample Code: ArtGallery
Posted Nov 29, 2007 — 19 comments below




 

AlexClarke — Nov 29, 07 5141

Very nice Scott.I was just looking for something like this.

Tony Arnold — Nov 29, 07 5143

Sweet! The documentation and examples included with Xcode for CoreAnimation are quite difficult to pick up without help. This project is a much simpler exemplar.

Nice work, Scott :)

Jarl Robert Kristiansen — Nov 29, 07 5145

"// I'm open to better ideas. :)", NSImage-Extras.m, line 300.

How about:
return [[[self.image representations] firstObject] CGImage];

Jamie — Nov 29, 07 5146

[noob]

how do I run the app when its not in a bundle?

[/noob]

Jose Vazquez — Nov 29, 07 5148

Please correct me if I'm wrong. The Core Data stuff in this project is not being used at all right? I could see how it could be useful if you actually wanted to make into more than just a coding example.
Excellent work! I definetly want to delve into it and understand it fully. Might be a good topic of conversation for NSCoder night

Lucas Newman — Nov 29, 07 5149

@Jarl - NSImage doesn't guarantee that the first image rep will be an NSBitmapImageRep (what if it is an NSCachedImageRep?), so you are going to get an exception in certain cases with that code.

Scott Stevenson — Nov 29, 07 5150 Scotty the Leopard

@Jamie: how do I run the app when its not in a bundle?

An application actually is a bundle. If you want to run it outside of Xcode, it's in the project folder under "build/Release/ArtGallery". You can copy that file out and run it standalone.

@Jose Vazquez: The Core Data stuff in this project is not being used at all right?

Not right now, no. I usually use the Core Data template when creating a project because it's easier to add persistence in the future.

Alexander Rauchfuss — Nov 30, 07 5154

I am kind of surprised that you did not use constraints nor CAScrollLayer.

scott anguish — Nov 30, 07 5155

I'm not sure that constraints would be a big win in this situation. You'd need to create layers for everything at once.

a custom layout manager would be one option.

scroll layer would be interesting.

Scott Stevenson — Nov 30, 07 5156 Scotty the Leopard

@Alexander Rauchfuss: I am kind of surprised that you did not use constraints nor CAScrollLayer

The constraint layout manager is very nice, but this sort of layout might be a better starting point because it's what people are used to with NSView. Another sample might use the constraint-based layout manager.

I haven't looked at CAScrollLayer closely, but I'm not sure where it would fit in this case. I guess I could load all the image layers in at once (rather than loading and unloading them on demand) and scroll through them. Is that what you had in mind?

Alexander Rauchfuss — Dec 01, 07 5157

@Scott Stevenson
I haven't looked at CAScrollLayer closely, but I'm not sure where it would fit in this case. I guess I could load all the image layers in at once (rather than loading and unloading them on demand) and scroll through them. Is that what you had in mind?

Yes all of the layers are populated but their contents are not loaded.
I had in mind something like an NSTableview where the CALayer content was not set until it was visible.

Example Code

Here is a quick example I threw together. Buggy as heck but it was kinda fun trying to make it work.

Scott Stevenson — Dec 01, 07 5158 Scotty the Leopard

@Alexander Rauchfuss: Here is a quick example I threw together. Buggy as heck but it was kinda fun trying to make it work.

This is an interesting example. Thanks for putting it together.

Jarl Robert Kristiansen — Dec 04, 07 5166

@Lucas: Thats true, but what about:
return [[NSBitmapImageRep imageRepWithData: [self TIFFRepresentation]] CGImage];
Won't that be better as it will use the whole tiff?

ssp — May 26, 08 5922

Hey Scott,

the comments in your code suggests that you're not really convinced about the -cgImage method in your NSImage Extras category. How far does that go? In particular: What about memory usage/leakage?

According to Google, problems with that seem to be quite common when people want to set the contents of their CALayer with an NSImage. And from what I could tell, the 500MB of memory my app leaked (you can see the memory blocks with ObjectAlloc but the Leaks tool won't list them) for drawing a few NSBezierPaths into a layer all come from the TIFFRepresentiations created when getting the CGImageRef.

Scott Stevenson — May 26, 08 5926 Scotty the Leopard

@ssp: the comments in your code suggests that you're not really convinced about the -cgImage method in your NSImage Extras category

It works fine, it just seemed very brute force-ish to me at first. At this point, I think it's probably the best solution, and it's very simple at two lines of code.

How far does that go? In particular: What about memory usage/leakage?

As long as you CFRelease() or CGImageRelease() the result at some point, it's fine. I'm pretty sure ArtGallery does that correctly.

And from what I could tell, the 500MB of memory my app leaked [...] for drawing a few NSBezierPaths into a layer all come from the TIFFRepresentiations created when getting the CGImageRef.

You definitely need to release the images that you create. If you find a specific case where CFRelease() or CGImageRelease() isn't freeing the reference, then it's a bug that should be filed.

Even if you have garbage collection turned on, CF-style objects are not automatically picked up by the collector. Read more about it in "Using Core Foundation with Garbage Collection" at ADC.

ssp — Jun 02, 08 6001

Thanks for the additional comments Scott.

This problem kept turning me nuts and everything seemed to be fine with the code – as you say it's nice and simple.

After a lot of back and forth I decided to copy the code over to a new project and see what happens and it worked fine there. Further investigation revealed that for some reason all that happened was that the garbage collection setting for the compiler was set to not use garbage collection. No idea how that happened (I really don't touch those settings at all as I don't know my way around them and the defaults work just fine for me), perhaps some unfortunate glitch. Anyway that solved the problem!

Now that I have that sorted, I'm still not entirely happy with memory usage. I am displaying animated content in a CALayer (that is I'm updating the image in its contents, not just moving sublayers around) and doing this requires almost 100MB of RAM. I assume it happens because it takes a while for my old images to be garbage collected but with these amounts of memory, it'd seem preferable to just release them 'old school' style.

As I'm enjoying garbage collection otherwise I'm not keen on reverting to retain/release, but it's not clear to me how to release things manually exactly when I stop needing them (the NSGarbageCollector methods don't seem to improve my situation)

So if you can get someone to speak on the topic in you CocoaHeads show and make a video of it, that could be useful ;)

Tony Arnold — Nov 26, 08 6545

I'm seeing very similar behaviour to to ssp in terms of RAM usage - and I'd like to second the request for a "primer" on memory management (although I'd love more on manually managing memory than GC) while dealing with Core Animation/CGImageRefs/etc.

Scott Stevenson — Nov 29, 08 6547 Scotty the Leopard

@Tony Arnold: the request for a "primer" on memory management (although I'd love more on manually managing memory than GC) while dealing with Core Animation/CGImageRefs/etc

I'm not sure offhand how to address what ssp mentions because I don't have a specific test case to work with. If you're using manual memory management, though, the rule is pretty simple: release anything you create.

This stuff is explained in a bit more detail in the Objective-C Tutorial I posted at Cocoa Dev Central. Sections 4 and 7 of that tutorial cover manual memory management.

The rules for CoreFoundation-style objects like CGImageRef are conceptually the same, though you use the CFRelease() function instead of sending the -release message to the object.

Randy Becker — Feb 17, 10 7554

I built and ran this project, but it crashed when I clicked on an image. The private properties in THImageBrowserContentLayer are assigned, but the image layers need to be retained so they don't get deallocated when they get replaced in -setShouldDisplayMonochromeImage:.





 

Comments Temporarily Disabled

I had to temporarily disable comments due to spam. I'll re-enable them soon.





Copyright © Scott Stevenson 2004-2015