6 min read

Cocoa and Objective-C Cookbook

Understanding the CALayer class

In this recipe, we will use multiple layers to draw our custom view. Using a delegate method, we will draw the background of our view. A second layer will be used to include an image centered in the view. When complete, the sample application will resemble the following screenshot:

Getting ready

In Xcode, create a new Cocoa Application and name it CALayer.

How to do it…

In the Xcode project, right-click on the Classes folder and choose Add…, then choose New File… Under the MacOS X section, select Cocoa Class, then select Objective-C class. Finally, choose NSView in the Subclass of popup. Name the new file MyView.m. In the Xcode project, expand the Frameworks group and then expand Other Frameworks. Right-click on Other Frameworks and choose Add…, then choose Existing Frameworks… Find QuartzCore.framework in the list of frameworks and choose Add. Click on MyView.m to open it and add the following import: #import Remove the initWithFrame: and drawRect: methods. Add the following awakeFromNib method: – (void) awakeFromNib {

CALayer *largeLayer = [CALayer layer];

[largeLayer setName:@”large”];

[largeLayer setDelegate:self];

[largeLayer setBounds:[self bounds]];

[largeLayer setBorderWidth:4.0];

[largeLayer setLayoutManager:[CAConstraintLayoutManager

layoutManager]]; CALayer *smallLayer = [CALayer layer];

[smallLayer setName:@”small”];

CGImageRef image = [self convertImage:[NSImage

imageNamed:@”LearningJQuery”]];

[smallLayer setBounds:CGRectMake(0, 0, CGImageGetWidth(image),

CGImageGetHeight(image))];

[smallLayer setContents:(id)image];

[smallLayer addConstraint:[CAConstraint

constraintWithAttribute:kCAConstraintMidY

relativeTo:@”superlayer”

attribute:kCAConstraintMidY]]; [smallLayer addConstraint:[CAConstraint

constraintWithAttribute:kCAConstraintMidX

relativeTo:@”superlayer”

attribute:kCAConstraintMidX]]; CFRelease(image); [largeLayer addSublayer:smallLayer];

[largeLayer setNeedsDisplay]; [self setLayer:largeLayer];

[self setWantsLayer:YES];

} Add the following two methods to the MyView class as well: – (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)context

{ CGContextSetRGBFillColor(context, .5, .5, .5, 1);

CGContextFillRect(context, [layer bounds]);

} – (CGImageRef) convertImage:(NSImage *)image {

CGImageSourceRef source = CGImageSourceCreateWithData((CFDataRef

)[image TIFFRepresentation],

NULL);

CGImageRef imageRef = CGImageSourceCreateImageAtIndex(source, 0,

NULL);

CFRelease(source); return imageRef;

} Open the MyView.h file and add the following method declaration: – (CGImageRef) convertImage:(NSImage *)image; Right-click on the CALayer project in Xcode’s project view and choose Add…, then choose Existing Files…. Choose the LearningJQuery.jpg image and click on Add. Double-click on the MainMenu.xib file in the Xcode project. From Interface Builder’s Library palette, drag a Custom View into the application window. From Interface Builder’s Inspectors palette, select the Identity tab and set the Class popup to MyView. Back in Xcode, choose Build and Run from the toolbar to run the application.

How it works…

We are creating two layers for our view. The first layer is a large layer, which will be the same size as our MyView view. We set the large layers delegate to self so that we can draw the layer in the drawLayer: delegate method. The drawLayer: delegate method simply fills the layer with a mid-gray color. Next, we set the bounds property and a border width property on the larger layer.

Next, we create a smaller layer whose contents will be the image that we included in the project. We also add a layout manager to this layer and configure the constraints of the layout manager to keep the smaller layer centered both horizontally and vertically relative to the larger view using the superlayer keyword.

Lastly, we set the small layer as a sub-layer of the large layer and force a redraw of the large layer by calling setNeedsDisplay. Next, we set the large layer as the MyView’s layer. We also need to call the setWantsLayer:YES on the MyView to enable the use of layers in our view.

There’s more…

Since we used a layout manager to center the image in the view, the layout manager will also handle the centering of the image when the user resizes the view or window. To see this in action, modify the Size properties in Interface Builder for the custom view as shown in the screenshot below:

Animation by changing properties

Cocoa provides a way to animate views by changing properties using implied animations. In this recipe, we will resize our custom view when the resize button is clicked, by changing the views frame size.

Getting ready

In Xcode, create a new Cocoa Application and name it ChangingProperties.

How to do it…

In the Xcode project, right-click on the Classes folder and choose Add…, then choose New File… Under the MacOS X section, select Cocoa Class, then select Objective-C class. Finally, choose NSView from the Subclass of popup. Name the new file MyView.m. Click on the MyView.m file to open it and add the following in the drawRect: method: NSBezierPath *path = [NSBezierPath bezierPathWithRoundedRect:[

self bounds] xRadius:8.0 yRadius:8.0];

[path setClip]; [[NSColor whiteColor] setFill];

[NSBezierPath fillRect:[self bounds]]; [path setLineWidth:3.0]; [[NSColor grayColor] setStroke];

[path stroke]; Click on the ChangingPropertiesAppDelegate.h to open it. Next, insert an import for the MyView.h header file: #import “MyView.h” Add the following variables to the class interface: NSButton *button;

MyView *myView; Add the following properties to the class interface: @property (assign) IBOutlet NSButton *button;

@property (assign) IBOutlet MyView *myView; Add the method declaration for when the Resize button is clicked: – (IBAction) resizeButtonHit:(id)sender; Click on the ChangingPropertiesAppDelegate.m file to open it. Add our synthesized variables below the synthesized window variable: @synthesize button;

@synthesize myView; Create a global static boolean for tracking the size of the view: static BOOL isSmall = YES; Add the resizeButtonHit: method to the class implementation: – (IBAction) resizeButtonHit:(id)sender {

NSRect small = NSMakeRect(20, 250, 150, 90);

NSRect large = NSMakeRect(20, 100, 440, 240); if (isSmall == YES) {

[[myView animator] setFrame:large];

isSmall = NO;

} else {

[[myView animator] setFrame:small];

isSmall = YES;

}

} Double-click on the MainMenu.xib file in the Xcode project. From Interface Builders Library palette, drag a Custom View into the application window. From Interface Builder’s Inspector’s palette, select the Identity tab and set the Class popup to MyView. From the Library palette, drag a Push Button into the application window. Adjust the layout of the Custom View and button so that it resembles the screenshot below: From Interface Builder’s Inspector’s palette, select the Identity tab and set the Class popup to MyView. Right-click on the Changing Properties App Delegate so that you can connect the outlets to the MyView Custom View, the Resize Push Button, and the resizeButtonHit action: Back in Xcode, choose Build and Run from the toolbar to run the application.

How it works…

We define two sizes for our view, one small size that is the same as the initial size of the view, and one large size. Depending on the state of the isSmall global variable, we set the view’s frame size to one of our predefined sizes. Note that we set the view’s frame via the views animator property. By using this property, we make use of the implicit animations available in the view.

There’s more…

Using the same technique, we can animate several other properties of the view such as its position or opacity. For more information on which properties can be implicitly animated, see Apple’s Core Animation Programming Guide.