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).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
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.)
CoreAnimation Sample Code: ArtGallery
Posted Nov 29, 2007 — 19 comments below
Posted Nov 29, 2007 — 19 comments below
AlexClarke — Nov 29, 07 5141
Tony Arnold — Nov 29, 07 5143
Nice work, Scott :)
Jarl Robert Kristiansen — Nov 29, 07 5145
How about:
return [[[self.image representations] firstObject] CGImage];
Jamie — Nov 29, 07 5146
how do I run the app when its not in a bundle?
[/noob]
Jose Vazquez — Nov 29, 07 5148
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
Scott Stevenson — Nov 29, 07 5150
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
scott anguish — Nov 30, 07 5155
a custom layout manager would be one option.
scroll layer would be interesting.
Scott Stevenson — Nov 30, 07 5156
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
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
This is an interesting example. Thanks for putting it together.
Jarl Robert Kristiansen — Dec 04, 07 5166
return [[NSBitmapImageRep imageRepWithData: [self TIFFRepresentation]] CGImage];
Won't that be better as it will use the whole tiff?
ssp — May 26, 08 5922
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
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
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
Scott Stevenson — Nov 29, 08 6547
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:
.