6?Specialzed?layers 特殊层? 第一部分 ?读书笔记
?
Specialization is a feature of every complex organization.
专注是每个复杂系统的特性
Catharine R. Stimpson
?
Up to this point, we have been working with the?CALayer?class, and we have seen that it has some useful image drawing and transformation capabilities. But Core Animation layers can be used for more than just images and colors. This chapter explores some of the other layer classes that you can use to extend Core Animation‘s drawing capabilities.
这章我们将研究能用来扩展core animation 绘制能力的的其他
的类。
CAShapeLayer
In Chapter 4, "Visual Effects," you learned how to use?CGPath?to create arbitrarily shaped shadows without using images. It would be neat if we could create arbitrarily shaped layers in the same way.
?
CAShapeLayer?is a layer subclass that draws itself using vector graphics instead of a bitmap image. You specify attributes such as color and line thickness, define the desired shape using a?CGPath, and?CAShapeLayer?renders it automatically.
你指明属性例如 color and line thickness ,用CGPath定义需要的图片,CAShapeLayer自动的把他们显示出来。?
Of course, you could use Core Graphics to draw a path directly into the contents of an ordinary?CALayer?(as in Chapter 2, "The Backing Image"), but there are several advantages to using?CAShapeLayer?instead:
你可以使用普通的CALayer,但是使用CAShaperLayer有下列好处:
?
??It‘s fast—CAShapeLayer?uses hardware-accelerated drawing and is much faster than using Core Graphics to draw an image.
更快
?
??It‘s memory efficient—A?CAShapeLayer?does not have to create a backing image like an ordinary?CALayer?does, so no matter how large it gets, it won‘t consume much memory.
内存更有效
?
??It doesn‘t ?get clipped to the layer?bounds—A?CAShapeLayer?can happily draw outside of its?bounds. Your path will not get clipped like it does when you draw into a regular?CALayer?using Core Graphics (as you saw in Chapter 2).
不会被layer bounds 剪切
?
??There‘s no pixelation ?— When you transform a?CAShapeLayer?by scaling it up or moving it closer to the camera with a 3D perspective transform, it does not become pixelated like an ordinary layer‘s backing image would.
不会像素失真
?
Creating a?CGPath
?
CAShapeLayer?can be used to draw any shape that can be represented by a?CGPath.
CAShapeLayer 能够绘制任何能用CGPath绘制的形状?
The shape doesn‘t have to be closed, and the path doesn‘t have to be unbroken, so you can actually draw several distinct shapes in a single layer. 形状不一定是闭合的,也不一定是不可分割的。
You can control the?strokeColor?and?fillColor?of the path, along with other properties such as?lineWidth?(line thickness, in points),?lineCap?(how the ends of lines look), and?lineJoin?(how the joints between lines look); but you can only set these properties once, at the layer level. If you want to draw multiple shapes with different colors or styles, you have to use a separate layer for each shape.
Listing 6.1 shows the code for a simple stick figure drawing, rendered using a single?CAShapeLayer. The?CAShapeLayer path?property is defined as a?CGPathRef, but we‘ve created the path using the?UIBezierPath?helper class, which saves us from having to worry about manually releasing the?CGPath. Figure 6.1 shows the result. It‘s not exactly a Rembrandt, but you get the idea!
Listing 6.1?Drawing a Stick Figure Using?CAShapeLayer?#import?"DrawingView.h"
?
#import?<QuartzCore/QuartzCore.h>
@interface?ViewController ()
@property?(nonatomic,?weak)?IBOutlet?UIView?*containerView;
@end
@implementation?ViewController
- (void)viewDidLoad {
[super?viewDidLoad];
//create path
?
UIBezierPath?*path = [[UIBezierPath?alloc]?init];
[path?moveToPoint:CGPointMake(175,?100)];
[path?addArcWithCenter:CGPointMake(150,?100)?radius:25
startAngle:0?endAngle:2*M_PI
clockwise:YES];
[path?moveToPoint:CGPointMake(150,?125)];
?
[path?addLineToPoint:CGPointMake(150,?175)];
[path?addLineToPoint:CGPointMake(125,?225)];
[path?moveToPoint:CGPointMake(150,?175)];
[path?addLineToPoint:CGPointMake(175,?225)];
[path?moveToPoint:CGPointMake(100,?150)];
[path?addLineToPoint:CGPointMake(200,?150)];
//create shape layer
?
CAShapeLayer?*shapeLayer = [CAShapeLayer?layer];
shapeLayer.strokeColor?= [UIColor?redColor].CGColor;
shapeLayer.fillColor?= [UIColor?clearColor].CGColor;
shapeLayer.lineWidth?=?5;
?
shapeLayer.lineJoin?=?kCALineJoinRound;
shapeLayer.lineCap?=?kCALineCapRound;
shapeLayer.path?= path.CGPath;
//add it to our view
[self.containerView.layer?addSublayer:shapeLayer]; }
?
@end
Figure 6.1?A simple stick figure displayed using?CAShapeLayer
Rounded Corners, Redux
?
Chapter 2 mentioned that?CAShapeLayer?provides an alternative way to create a view with rounded corners, as opposed to using the?CALayer cornerRadius?property. Although using a?CAShapeLayer?is a bit more work, it has the advantage that it allows us to specify the radius of each corner independently.
尽管使用CAShapeLayer有点麻烦,但是可以为每个角独立的指定弧度。
We could create a rounded rectangle path manually using individual straight lines and arcs, but?UIBezierPath?actually has some convenience constructors for creating rounded rectangles automatically. The following code snippet produces a path with three rounded corners and one sharp:
//define path parameters
?
CGRect?rect =?CGRectMake(50,?50,?100,?100);
CGSize?radii =?CGSizeMake(20,?20);
UIRectCorner?corners =?UIRectCornerTopRight?|?UIRectCornerBottomRight?|?UIRectCornerBottomLeft;
//create path
?
UIBezierPath?*path = [UIBezierPath?bezierPathWithRoundedRect:rect?byRoundingCorners:corners?cornerRadii:radii];
?
?
We can use a?CAShapeLayer?with this path to create a view with mixed sharp and rounded corners.
我们可以利用CAShapeLayer 有这个path来创建一个固定的角度和圆角的view?
If we want to clip the view‘s contents to this shape, we can use our?CAShapeLayer?as the?mask?property of the view‘s backing layer instead of adding it as a sublayer. (See Chapter 4, "Visual Effects," for a full explanation of layer masks.)
CATextLayer ?
?
A user interface cannot be constructed from images alone. A well-designed icon can do a great job of conveying the purpose of a button or control, but sooner or later you‘re going to need a good old-fashioned text label.
?
?
If you want to display text in a layer, there is nothing stopping you from using the layer delegate to draw a string directly into the layer contents with Core Graphics (which is essentially how?UILabel?works).
使用layer 代理去绘制一个String 直接到layer 的content是
用Core Graphics .?
This is a cumbersome approach if you are working directly with layers, though, instead of layer-backed views.
如果你想直接与layers工作,而不是在layer-backed views时,会是笨重的方法。
You would need to create a class that can act as the layer delegate for each layer that displays text, and then write logic to determine which layer should display which string, not to mention keeping track of the different fonts, colors, and so on.
?
Fortunately, this is unnecessary. Core Animation provides a subclass of?CALayer?called?CATextLayer?that encapsulates most of the string drawing features of?UILabel?in layer form and adds a few extra features for good measure.
CATextLayer囊括了UILabel string 绘制的特性并添加了一些更好的特色。
CATextLayer?also renders much faster than?UILabel. It‘s a little-known fact that on iOS6 and earlier,?UILabel?actually uses WebKit to do its text drawing, which carries a significant performance overhead when you are drawing a lot of text.?CATextLayer?uses Core Text and is significantly faster.
Let‘s try displaying some text using a?CATextLayer. Listing 6.2 shows the code to set up and display a?CATextLayer, and Figure 6.2 shows the result.
?
Listing 6.2?Implementing a Text Label Using?CATextLayer
@interface?ViewController ()
@property?(nonatomic,?weak)?IBOutlet?UIView?*labelView;
@end
@implementation?ViewController
- (void)viewDidLoad {
[super?viewDidLoad];
//create a text layer
?
CATextLayer?*textLayer = [CATextLayer?layer];
textLayer.frame?=?self.labelView.bounds;
[self.labelView.layer?addSublayer:textLayer];
//set text attributes
?
textLayer.foregroundColor?= [UIColor?blackColor].CGColor;
textLayer.alignmentMode?=?kCAAlignmentJustified;
textLayer.wrapped?=?YES;
//choose a font
UIFont?*font = [UIFont?systemFontOfSize:15];
//set layer font
?
CFStringRef?fontName = (__bridge?CFStringRef)font.fontName;
CGFontRef?fontRef =?CGFontCreateWithFontName(fontName);
textLayer.font?= fontRef;
textLayer.fontSize?= font.pointSize;
CGFontRelease(fontRef);
//choose some text
NSString?*text =?@"Lorem ipsum dolor sit amet, consectetur adipiscing \ elit. Quisque massa arcu, eleifend vel varius in, facilisis pulvinar \ leo. Nunc quis nunc at mauris pharetra condimentum ut ac neque. Nunc \
elementum, libero ut porttitor dictum, diam odio congue lacus, vel \ fringilla sapien diam at purus. Etiam suscipit pretium nunc sit amet \ lobortis";
//set layer text
textLayer.string?= text; }
?
@end
?
Figure 6.2?A plain text label implemented using?CATextLayer
?
If you look at this text closely, you‘ll see that something is a bit odd; the text is pixelated. That‘s because it‘s not being rendered at Retina resolution. Chapter 2 mentioned the?contentsScale?property, which is used to determine the resolution at which the layer contents are rendered. The?contentsScale?property always defaults to 1.0 instead of the screen scale factor. If we want Retina-quality text, we have to set the?contentsScale?of our?CATextLayer?to match the screen scale using the following line of code:需要设置contentsScale属性
来设为screen scale 因子。
textLayer.contentsScale?= [UIScreen?mainScreen].scale;
?
This solves the pixelation problem (see Figure 6.3).
The?CATextLayer font?property is not a?UIFont, it‘s a?CFTypeRef.
CATextLayer ?font 属性不是一个UIFont,是CFTypeRef。
This allows you to specify the font using either a?CGFontRef?or a?CTFontRef?(a Core Text font), depending on your requirements. The font size is also set independently using the?fontSize?property, because?CTFontRef?and?CGFontRef?do not encapsulate the point size like?UIFont?does. The example shows how to convert from a?UIFont?to a?CGFontRef.
?
Also, the?CATextLayer string?property is not an?NSString?as you might expect, but is typed as?id.
CATextLayer string属性不是NSString,而是id类型的。
This is to allow you the option of using an?NSAttributedString?instead of an?NSString?to specify the text (NSAttributedString?is not a subclass of?NSString). Attributed strings are the mechanism that iOS uses for rendering styled text. They specify?style runs, which are specific ranges of the string to which metadata such as font, color, bold, italic, and so forth are attached.
Rich Text
?
In iOS 6, Apple added direct support for attributed strings to?UILabel?and to other UIKit text views.
在iOS6上,苹果提供了一个attributed strings 给UILabel 和其他UIKit text views .
This is a handy feature that makes attributed strings much easier to work with, but?CATextLayer?has supported attributed strings since its introduction in iOS 3.2; so if you still need to support earlier iOS versions with your app,?CATextLayer?is a great way to add simple rich text labels to your interface without having to deal with the complexity of Core Text or the hassle of using a?UIWebView.
Let‘s modify the example to use an?NSAttributedString?(see Listing 6.3). On iOS 6 and above we could use the new?NSTextAttributeName?constants to set up our string
attributes, but because the point of the exercise is to demonstrate that this feature also works on iOS 5 and below, we‘ve used the Core Text equivalents instead. This means that you‘ll need to add the Core Text framework to your project; otherwise, the compiler won‘t recognize the attribute constants.
Figure 6.4 shows the result. (Note the red, underlined text.)
Listing 6.3?Implementing a Rich Text Label Using?NSAttributedString
?
#import?"DrawingView.h"
#import?<QuartzCore/QuartzCore.h>
#import?<CoreText/CoreText.h>
@interface?ViewController ()
@property?(nonatomic,?weak)?IBOutlet?UIView?*labelView;?@end
@implementation?ViewController
- (void)viewDidLoad {
[super?viewDidLoad];
//create a text layer
?
CATextLayer?*textLayer = [CATextLayer?layer];
textLayer.frame?=?self.labelView.bounds;
textLayer.contentsScale?= [UIScreen?mainScreen].scale;
[self.labelView.layer?addSublayer:textLayer];
//set text attributes
textLayer.alignmentMode?=?kCAAlignmentJustified; textLayer.wrapped?=?YES;
//choose a font
UIFont?*font = [UIFont?systemFontOfSize:15];
//choose some text
NSString?*text =?@"Lorem ipsum dolor sit amet, consectetur adipiscing \ elit. Quisque massa arcu, eleifend vel varius in, facilisis pulvinar \ leo. Nunc quis nunc at mauris pharetra condimentum ut ac neque. Nunc \ elementum, libero ut porttitor dictum, diam odio congue lacus, vel \ fringilla sapien diam at purus. Etiam suscipit pretium nunc sit amet \ lobortis";
//create attributed string
NSMutableAttributedString?*string =?nil;
string = [[NSMutableAttributedString?alloc]?initWithString:text];
//convert UIFont to a CTFont
?
CFStringRef?fontName = (__bridge?CFStringRef)font.fontName;
CGFloat?fontSize = font.pointSize;
CTFontRef?fontRef =?CTFontCreateWithName(fontName, fontSize,?NULL);
//set text attributes
NSDictionary?*attribs =?@{
(__bridge id)kCTForegroundColorAttributeName:
(__bridge id)[UIColor?blackColor].CGColor, (__bridge id)kCTFontAttributeName: (__bridge id)fontRef
?
};
[string?setAttributes:attribs?range:NSMakeRange(0, [text?length])];
attribs =?@{
(__bridge id)kCTForegroundColorAttributeName: (__bridge id)[UIColor?redColor].CGColor, (__bridge id)kCTUnderlineStyleAttributeName:
@(kCTUnderlineStyleSingle),
(__bridge id)kCTFontAttributeName: (__bridge id)fontRef
};
[string?setAttributes:attribs?range:NSMakeRange(6,?5)];
//release the CTFont we created earlier
CFRelease(fontRef);
//set layer text
textLayer.string?= string; }
?
@end
?
?
Figure 6.4?A rich text label implemented using?CATextLayer
Leading and Kerning
?
It‘s worth mentioning that the?leading?(line spacing) and?kerning?(spacing between letters) for text rendered using?CATextLayer?is not completely identical to that of the string rendering used by?UILabel?due to the different drawing implementations (Core Text and WebKit, respectively).
leading 行间距
和kerning 两个字符的间距?
?
The extent of the discrepancy varies (depending on the specific font and characters used) and is generally fairly minor, but you should keep this mind if you are trying to exactly match appearance between regular labels and a?CATextLayer.
一般差距很小,但是一般情况下很少,但是如果你要注意这事在使用普通的labels和CATextLayer的时候。
A?UILabel?Replacement
We‘ve established that?CATextLayer?has performance benefits over?UILabel, as well as some additional layout options and support for rich text on iOS 5. But it‘s fairly cumbersome to use by comparison to a regular label. If we want to make a truly usable replacement for?UILabel, we should be able to create our labels in Interface Builder, and they should behave as much as possible like regular views.
?
We could subclass?UILabel?and override its methods to display the text in a?CATextLayer?that we‘ve added as a sublayer, but we‘d still have the redundant empty backing image created by the presence of?UILabel -drawRect:?method. And because?CALayer?doesn‘t support autoresizing or autolayout, a sublayer wouldn‘t track the size of the view?bounds?automatically, so we would need to manually update the sublayer bounds every time the view is resized.
我们可以创建UILabel 的子类并重写它的方法来展示我们添加到为一个sublayer的CATextLayer.
What we really want is a?UILabel?subclass that actually uses a?CATextLayer?as its backing layer, then it would automatically resize with the view and there would be no redundant backing image to worry about.
?
As we discussed in Chapter 1, "The Layer Tree," every?UIView?is backed by an instance of?CALayer. That layer is automatically created and managed by the view, so how can we substitute a different layer type? We can‘t replace the layer once it has been created, but if we subclass?UIView, we can override the +layerClass?method to return a different layer subclass at creation time.?UIView?calls the +layerClass?method during its initialization, and uses the class it returns to create its backing layer.
但是如果我们继承UIView,我们可以重载+layerClass 方法
返回一个不同的layer 子类在创建的时候。UIView调用layerClass 方法在初始化的时候,使用这个类返回创建的backing layer.
?
Listing 6.4 shows the code for a?UILabel?subclass called?LayerLabel?that draws its text using a?CATextLayer?instead of the using the slower?–drawRect:?approach that an ordinary?UILabel?uses.?LayerLabel?instances can either be created program-matically or via Interface Builder by adding an ordinary label to the view and setting its class to?LayerLabel.
?
Listing 6.4?LayerLabel, a?UILabel?Subclass That Uses?CATextLayer
#import?"LayerLabel.h"
?
#import?<QuartzCore/QuartzCore.h>
@implementation?LayerLabel
+ (Class)layerClass {
?
//this makes our label create a CATextLayer
//instead of a regular CALayer for its backing layer
return?[CATextLayer?class];
?
}
- (CATextLayer?*)textLayer {
?
return?(CATextLayer?*)self.layer;
}
- (void)setUp {
//set defaults from UILabel settings
?
self.text?=?self.text;
self.textColor?=?self.textColor;
self.font?=?self.font;
?
//we should really derive these from the UILabel settings too
//but that‘s complicated, so for now we‘ll just hard-code them
[self?textLayer].alignmentMode?=?kCAAlignmentJustified;
?
[self?textLayer].wrapped?=?YES;
[self.layer?display];
?
}
- - (id)initWithFrame:(CGRect)frame {
//called when creating label programmatically
if?(self?= [super?initWithFrame:frame]) {
[self?setUp];
- }
return self; }
- - (void)awakeFromNib {
//called when creating label using Interface Builder
[self?setUp];
- }
- - (void)setText:(NSString?*)text {
super.text?= text;
//set layer text
[self?textLayer].string?= text; }
- - (void)setTextColor:(UIColor?*)textColor {
super.textColor?= textColor;
//set layer text color
[self?textLayer].foregroundColor?= textColor.CGColor; }
- - (void)setFont:(UIFont?*)font {
super.font?= font;
//set layer font
CFStringRef?fontName = (__bridge?CFStringRef)font.fontName;?CGFontRef?fontRef =?CGFontCreateWithFontName(fontName); [self?textLayer].font?= fontRef;
[self?textLayer].fontSize?= font.pointSize;
?
CGFontRelease(fontRef);
}
@end
If you run the sample code, you‘ll notice that the text isn‘t pixelated even though we aren‘t setting the?contentsScale?anywhere. Another benefit of implementing?CATextLayer?as a backing layer is that its?contentsScale?is automatically set by the view.
In this simple example, we‘ve only implemented a few of the styling and layout properties of?UILabel, but with a bit more work we could create a?LayerLabel?class that supports the full functionality of?UILabel?and more (you will find several such classes already available as open source projects online).
If you only intend to support iOS 6 and above, a?CATextLayer-based label may be of limited use. But in general, using +layerClass?to create views backed by different layer types is a clean and reusable way to utilize?CALayer?subclasses in your apps.
CATransformLayer
When constructing complex objects in 3D, it is convenient to be able to organize the individual elements hierarchically. For example, suppose you were making an arm: You would want the hand to be a child of the wrist, which would be a child of the forearm, which would be a child of the elbow, which would be a child of the upper arm, which would be a child of the shoulder, and so on.
The reason for this is that it allows you to move each section independently. Pivoting the elbow would move the lower arm and hand but not the shoulder. Core Animation layers easily allow for this kind of hierarchical arrangement in?2D,?but in 3D it‘s not possible because each layer flattens its children into a single plane (as explained in Chapter 5, "Transforms").
CATransformLayer?solves this problem. A?CATransformLayer?is unlike a regular?CALayer?in that it cannot display any content of its own; it exists only to host a transform that can be applied to its sublayers.?CATransformLayer?does not flatten its sublayers, so it can be used to construct a hierarchical 3D structure, such as our arm example.
Creating an arm programmatically would require rather a lot of code, so we‘ll demonstrate this with something a bit simpler: In the cube example in Chapter 5, we worked around the layer-flattening problem by rotating the?camera?instead of the cube by using the?sublayerTransform?of the containing layer. This is a neat trick, but only works for a single object. If our scene contained two cubes, we would not be able to rotate them independently using this technique.
So, let‘s try it using a?CATransformLayer?instead. The first problem to address is that we constructed our cube in Chapter 5 using views rather than standalone layers. We cannot place a view-backing layer inside another layer that is not itself a view-backing layer without messing up the view hierarchy. We could create a new?UIView?subclass backed by a?CATransformLayer?(using the +layerClass?method), but to keep things simple for our example, let‘s just re-create the cube using standalone layers instead of views. This means we can‘t display buttons and labels on our cube faces like we did in Chapter 5, but we don‘t need to do that right now.
Listing 6.5 contains the code. We position each cube face using the same basic logic we used in Chapter 5. But instead of adding the cube faces directly to the container view‘s backing layer as we did before, we place them inside a?CATransformLayer?to create a standalone cube object, and then place two such cubes into our container. We‘ve colored the cube faces randomly so as to make it easier to distinguish them without labels or lighting. Figure 6.5 shows the result.
?
Listing 6.5?Assembling a 3D Layer Hierarchy Using?CATransformLayer
@interface?ViewController ()
@property?(nonatomic,?weak)?IBOutlet?UIView?*containerView;?@end
@implementation?ViewController
- (CALayer?*)faceWithTransform:(CATransform3D)transform {
//create cube face layer
CALayer?*face = [CALayer?layer];
face.frame?=?CGRectMake(-50, -50,?100,?100);
//apply a random color
?
CGFloat?red = (rand() / (double)INT_MAX);
CGFloat?green = (rand() / (double)INT_MAX);
CGFloat?blue = (rand() / (double)INT_MAX);
face.backgroundColor?= [UIColor?colorWithRed:red?
green:green?blue:blue
?
alpha:1.0].CGColor;
?
//apply the transform and return
face.transform?= transform;
?
return?face;
}
?
?
- (CALayer?*)cubeWithTransform:(CATransform3D)transform {
//create cube layer
CATransformLayer?*cube = [CATransformLayer?layer];
//add cube face 1
?
CATransform3D?ct =?CATransform3DMakeTranslation(0,?0,?50);
[cube?addSublayer:[self?faceWithTransform:ct]];
//add cube face 2
?
ct =?CATransform3DMakeTranslation(50,?0,?0);
ct =?CATransform3DRotate(ct,?M_PI_2,?0,?1,?0);
[cube?addSublayer:[self?faceWithTransform:ct]];
//add cube face 3
?
ct =?CATransform3DMakeTranslation(0, -50,?0);
ct =?CATransform3DRotate(ct,?M_PI_2,?1,?0,?0);
[cube?addSublayer:[self?faceWithTransform:ct]];
//add cube face 4
?
ct =?CATransform3DMakeTranslation(0,?50,?0);
ct =?CATransform3DRotate(ct, -M_PI_2,?1,?0,?0);
[cube?addSublayer:[self?faceWithTransform:ct]];
//add cube face 5
?
ct =?CATransform3DMakeTranslation(-50,?0,?0);
ct =?CATransform3DRotate(ct, -M_PI_2,?0,?1,?0);
[cube?addSublayer:[self?faceWithTransform:ct]];
//add cube face 6
?
ct =?CATransform3DMakeTranslation(0,?0, -50);
ct =?CATransform3DRotate(ct,?M_PI,?0,?1,?0);
[cube?addSublayer:[self?faceWithTransform:ct]];
//center the cube layer within the container
?
CGSize?containerSize =?self.containerView.bounds.size;
cube.position?=?CGPointMake(containerSize.width?/?2.0,
containerSize.height?/?2.0);
//apply the transform and return
cube.transform?= transform;
return?cube; }
- - (void)viewDidLoad {
[super?viewDidLoad];
//set up the perspective transform
CATransform3D?pt =?CATransform3DIdentity; pt.m34?= -1.0?/?500.0;?self.containerView.layer.sublayerTransform?= pt;
//set up the transform for cube 1 and add it
?
CATransform3D?c1t =?CATransform3DIdentity;
c1t =?CATransform3DTranslate(c1t, -100,?0,?0);
CALayer?*cube1 = [self?cubeWithTransform:c1t];
[self.containerView.layer?addSublayer:cube1];
//set up the transform for cube 2 and add it
?
CATransform3D?c2t =?CATransform3DIdentity;
c2t =?CATransform3DTranslate(c2t,?100,?0,?0);
c2t =?CATransform3DRotate(c2t, -M_PI_4,?1,?0,?0);
c2t =?CATransform3DRotate(c2t, -M_PI_4,?0,?1,?0);
CALayer?*cube2 = [self?cubeWithTransform:c2t];
[self.containerView.layer?addSublayer:cube2];
}
?
@end
?
Figure 6.5?Two cubes with shared perspective but different transforms applied