[아이폰 앱 개발] Cocoa for Scientists (Part XXV): Core Animation Layer Trees

Cocoa for Scientists (Part XXV): Core Animation Layer Trees

Author: Drew McCormack
Web Site: www.maccoremac.com

In the first part of this foray into Core Animation, we saw how you can animate the properties of a plain square layer, translating, scaling, and rotating in time. In this part, you’ll learn how to build up hierarchies of layers — the proverbial fleas on the fleas. To do this, we’ll finish off the Flea on Flea application, adding sublayers that creep and crawl on the backs of their superlayers, which in turn creep and crawl on the backs of theirs.

Flea on Flea

The Flea on Flea application (requires Mac OS X 10.5) is nothing more than an animated scene, but it demonstrates how you build up a layer tree: a hierarchy of child layers and their parents. Each flea layer in the application has other, smaller flea layers randomly walking its surface. You can’t interact with the scene, but you can resize the window to give the fleas more room to move.


The Flea on Flea application.

Flea on Flea is a fun way to be introduced to the layer tree, but you don’t have to think too hard to come up with scientific applications which utilize the same basic approach. Multi-scale is a trendy catch phrase in many fields, and with a bit of creativity, it should be possible to come up with some revolutionary new ways of navigating hierarchical data sets with Core Animation layer trees.

The Layer Tree

Much like NSViewCALayers can be organized into a tree structure, with sublayers in superlayers, in super-superlayers, and so on. Just as for NSView, each layer moves in the coordinate system of its superlayer, which is given by the bounds property. The size and location of a layer relative to its superlayer is determined by itsbounds rectangle, and its position, which is a point giving the location of the anchor point in the superlayer’s coordinate system. The anchor point is the point which represents the center of the layer’s world, which remains fixed during transformations like rotation and scaling. By default, the anchor point is at the center of the layer, but it can be moved by setting the anchorPoint property.



The geometry of a CALayer (image from Core Animation Programming Guide).

There are many similarities between NSView and CALayer, but also some subtle differences. For example, CALayerdoes not crop its sublayers drawing by default. If you want this behavior, you need to set the layer’s propertymasksToBounds to YES. Layers also have a few tricks that views don’t, such as the ability to set a background color (backgroundColor property); round corners (cornerRadius property); draw a drop shadow (shadowOpacity,shadowOffsetshadowRadius, and shadowColor); and include a border (borderWidth and borderColor). You can even set Core Image filters to change the appearance of the layer’s content, or whatever lies directly behind it (eg. you could blur the background).



Flea on Flea with random background colors and rounded corners.

Growing the Tree

The Flea on Flea source code is not much more advanced than it ended up last time. The awakeFromNib method downloads an image of a flea, and stores it in an instance variable.

-(void)awakeFromNib { // Download flea image NSURL *url = [NSURL URLWithString:@"http://medent.usyd.edu.au/fact/flea.gif"]; CGImageSourceRef imageSource = CGImageSourceCreateWithURL((CFURLRef)url, NULL); fleaImage = CGImageSourceCreateImageAtIndex(imageSource, 0, NULL); CFRelease(imageSource); // Setup layers [self setupHostView]; [self createFleas]; // Start Timer [NSTimer scheduledTimerWithTimeInterval:3.0 target:self selector:@selector(changeDestinations) userInfo:nil repeats:YES]; }

As usual, Core Graphics types and functions are used; in this case, a CGImageSource is used to download an image from a URL, and create a CGImage, which is the image type used in Core Animation.

The awakeFromNib method continues by calling a method to setup the host view, which is unchanged from last time, and to initialize the flea layers and build the layer tree.

The createFleas method creates an array to store references to all of the flea layers, and then calls another routine to do the hard work.

-(void)createFleas { allFleaLayers = [NSMutableArray array]; [self createFleasInLayer:hostView.layer atDepth:0]; }

createFleasInLayer:atDepth: is a function that is designed to be called recursively. It adds a new sublayer to the layer passed in, and then recursively calls itself to add its own sublayers, up to a maximum layer depth.

-(void)createFleasInLayer:(CALayer *)layer atDepth:(NSUInteger)depth { static const NSUInteger MAX_DEPTH = 2; static const NSUInteger NUMBER_FLEAS_PER_LAYER = 4; if ( depth > MAX_DEPTH ) return; NSUInteger fleaIndex; for (fleaIndex = 0; fleaIndex < NUMBER_FLEAS_PER_LAYER; ++fleaIndex) { CALayer *newLayer = [self createFleaLayerInLayer:layer]; [allFleaLayers addObject:newLayer]; [self createFleasInLayer:newLayer atDepth:depth+1]; } }

Each flea layer is initialized in the createFleaLayerInLayer: method.

-(CALayer *)createFleaLayerInLayer:(CALayer *)parentLayer { CALayer *layer = [CALayer layer]; // Choose random location in parent layer float rand1 = rand()/(float)RAND_MAX; float rand2 = rand()/(float)RAND_MAX; float parentWidth = CGRectGetWidth(parentLayer.bounds); float parentHeight = CGRectGetHeight(parentLayer.bounds); layer.position = CGPointMake(rand1*parentWidth, rand2*parentHeight); layer.bounds = CGRectMake(0.0, 0.0, parentWidth*0.4, parentHeight*0.4); layer.opacity = 0.9; // Set image layer.contents = (id)fleaImage; // Add to parent [parentLayer addSublayer:layer]; return layer; }

The layer is given a random position in the bounds of its superlayer. Its contents property is set to theCGImageRef created in awakeFromNib, and the layer opacity set to 0.9 (to give it that icky translucence popular with small insects).

That’s almost all there is to it. The only aspect remaining is the animation itself. A timer, which is created inawakeFromNib, fires every 3 seconds, and invokes the changeDestinations method. This method loops over all flea layers, assigning a random position and rotation around the z-axis.

-(void)changeDestinations { [CATransaction begin]; [CATransaction setValue:[NSNumber numberWithFloat:3.0f] forKey:kCATransactionAnimationDuration]; for ( CALayer *layer in allFleaLayers ) { // Choose a random position in the superlayer layer.position = CGPointMake(layer.superlayer.bounds.size.width * rand()/(CGFloat)RAND_MAX, layer.superlayer.bounds.size.height * rand()/(CGFloat)RAND_MAX); // Choose a random rotation around the z axis CATransform3D transform = CATransform3DIdentity; transform = CATransform3DRotate(transform, acos(-1.0)*rand()/(CGFloat)RAND_MAX, 0.0, 0.0, 1.0); layer.transform = transform; } [CATransaction commit]; }

Being Core Animation, these changes don’t take effect immediately, but provide a target for the animation. The code to setup and commit the CATransaction is exactly the same as last time.

Things to Try

Flea on Flea is a fun application to play with. You can quite easily modify aspects of it to change the appearance and behavior of the flea layers. For example, if you want to add a colored drop shadow to the fleas, you could change the createFleasInLayer:atDepth: method as follows:

-(void)createFleasInLayer:(CALayer *)layer atDepth:(NSUInteger)depth { static const NSUInteger MAX_DEPTH = 2; static const NSUInteger NUMBER_FLEAS_PER_LAYER = 4; if ( depth > MAX_DEPTH ) return; NSUInteger fleaIndex; for (fleaIndex = 0; fleaIndex < NUMBER_FLEAS_PER_LAYER; ++fleaIndex) { CALayer *newLayer = [self createFleaLayerInLayer:layer]; // Set the shadow color based on depth CGColorSpaceRef colorSpace = CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB); CGFloat components[4] = {0.0f, 0.0f, 0.0f, 1.0f}; switch (depth) { case 1: components[1] = 1.0f; break; case 2: components[2] = 1.0f; break; case 3: components[0] = 1.0f; components[1] = 1.0f; components[2] = 1.0f; break; } CGColorRef color = CGColorCreate(colorSpace, components); newLayer.shadowColor = color; newLayer.shadowOpacity = 0.5; newLayer.shadowRadius = 2.0; newLayer.shadowOffset = CGSizeMake(0.0, 0.0); CGColorRelease(color); CGColorSpaceRelease(colorSpace); [allFleaLayers addObject:newLayer]; [self createFleasInLayer:newLayer atDepth:depth+1]; } }

It’s also worth observing the effect of setting the masksToBounds property of new layers to YES, as this is a one line change.



Flea on Flea with a Core Image Bloom filter applied to each layer.

Lastly, there are a multitude of Core Image filters that can be applied. Here’s how you can modifycreateFleaLayerInLayer: to apply a bloom effect to the fleas.

-(CALayer *)createFleaLayerInLayer:(CALayer *)parentLayer { CALayer *layer = [CALayer layer]; // Choose random location in parent layer float rand1 = rand()/(float)RAND_MAX; float rand2 = rand()/(float)RAND_MAX; float parentWidth = CGRectGetWidth(parentLayer.bounds); float parentHeight = CGRectGetHeight(parentLayer.bounds); layer.position = CGPointMake(rand1*parentWidth, rand2*parentHeight); layer.bounds = CGRectMake(0.0, 0.0, parentWidth*0.4, parentHeight*0.4); layer.opacity = 0.9; // Set image layer.contents = (id)fleaImage; // Set Core Image filter CIFilter *bloomFilter = [CIFilter filterWithName:@"bloom"]; if ( nil == bloomFilter ) { bloomFilter = [CIFilter filterWithName:@"CIBloom" keysAndValues: kCIInputRadiusKey, [NSNumber numberWithFloat:1.0], kCIInputIntensityKey, [NSNumber numberWithFloat:10.0], nil]; bloomFilter.name = @"bloom"; } layer.filters = [NSArray arrayWithObject:bloomFilter]; // Add to parent [parentLayer addSublayer:layer]; return layer; }

Be warned: filters and drop shadows seem to be demanding on the graphics hardware, and you might want to reduce the MAX_DEPTH and/or NUMBER_FLEAS_PER_LAYER constants before using them.

http://www.macresearch.org/cocoa-scientists-part-xxv-core-animation-layer-trees

Posted by 오늘마감