Core Animation CAShapeLayer

CAShapeLayer

我们在上面曾经用过CGPath代替图片来绘制阴影,同样的我们可以使用CGPath来绘制各种我们想要的图形,用来代替图片使用。

CAShapeLayerCALayer的子类,在绘制的时候它使用的是矢量图形,而不是 Bitmap Image,因此效率非常高。在使用的时候 只要我们定义好颜色 线条的宽度,然后用CGPath来定义出形状,剩下的 CAShapeLayer会自动渲染。当然我们也可以使用 Core GraphicsCALayer中绘制我们想要的内容,但是有使用 CAShapeLayer有它的好处,

  • 首先它速度非常快,CAShaplayer使用的是硬件加速,比起用 Core Graphics画一个图来会快很多
  • 节省内存 它不象CALayer那样会创建背景图片,所以无论它有多大,都不会太消耗内存
  • 不会剪切超出自己bounds的部分, 但是使用CALayer的时候我们就会被CGPath剪切掉超出的部分
  • 没有像素画,在做缩放变幻的时候,不会出现像素化的情况。

我们看个小栗子 怎么使用 CASapeLayer

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)];

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;
[self.containerView.layer addSublayer:shapeLayer];

image lineWidth 就是线的宽度

lineCap 线头的效果

lineJoin 不同的线相交的地方的效果

圆角

我们曾经使用过 CALayercornerRadius属性来绘制圆角矩形,我们使用 CAShaplayer也可以实现这个效果,但是CAShaplayer更好的地方在于它可以定义每个角实现不同的效果。

CGRect rect = CGRectMake(50, 50, 150, 150);
UIView *testView = [[UIView alloc] initWithFrame:rect];
testView.backgroundColor = [UIColor redColor];
[self.view addSubview:testView];

CGSize radii = CGSizeMake(20, 20);
UIRectCorner corners = UIRectCornerTopRight |
UIRectCornerBottomRight | UIRectCornerBottomLeft;
//create path
UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:testView.bounds
                                            byRoundingCorners:corners 
                                                  cornerRadii:radii];
CAShapeLayer *shapeLayer = [CAShapeLayer layer];
shapeLayer.strokeColor = [UIColor greenColor].CGColor;
shapeLayer.fillColor=[UIColor redColor].CGColor;
shapeLayer.lineWidth = 1;
shapeLayer.lineJoin = kCALineJoinRound;
shapeLayer.lineCap = kCALineCapRound;
shapeLayer.path = path.CGPath;
testView.layer.mask =shapeLayer;

image

Core Animation (七)3DTransform

3D Transform

3D Transform

CGAffineTransform 属于 Core Graphics Framework ,Core Graphic是一个绘制2D图形的库。CGAffineTransform 只提供 2D 的旋转缩放移动等。我们要想实现 3D矩阵下的旋转缩放就要用到CATransform3D。在2D动画中,point属性只有 X Y 值,在3D坐标中,多了一个 Z 值。它代表的是当前的Layer距离用户视角的远近,正是这个Z值 使我们可以在3D坐标系中旋转缩放移动。

CATransform3DCGAffineTransform 一样也是个矩阵,是一个 4x4 的矩阵结构定义如下。

struct CATransform3D
{
  CGFloat m11, m12, m13, m14;
  CGFloat m21, m22, m23, m24;
  CGFloat m31, m32, m33, m34;
  CGFloat m41, m42, m43, m44;
};
typedef struct CATransform3D CATransform3D;

3D转换的计算如下。

image

下面是几种常用的3D变换矩阵 跟 CGAffineTransform 矩阵非常相似

image

Core Graphic 提供了一些列的函数使我们可以方便简单的实现 3D变换。

CATransform3DMakeRotation(angle,x,y,z);
CATransform3DMakeScale(sx,sy,sz);
CATransform3DMakeTranslation(tx,ty,tz);

我们看下面这个例子

UIImage *image = [UIImage imageNamed:@"DragonMedium"];
UIImageView *imageView = [[UIImageView alloc] initWithImage:image];
imageView.backgroundColor = [UIColor greenColor];
imageView.frame = CGRectMake(0, 0 , CGRectGetWidth(imageView.frame),
                             CGRectGetHeight(imageView.frame));
imageView.center = self.view.center;

CATransform3D transform = CATransform3DMakeRotation(M_PI_4, 0, 1, 0);
imageView.layer.transform = transform;
[self.view addSubview:imageView];

image

好像只是简单的压缩了宽度。并没有出现我们预期的3D 沿 Y轴旋转的效果,这是为什么呢。我们知道,要想在2D平面表现3D效果,无论是拍照,还是绘画都要用到一个叫做透视法的东西,当我们距离一个物体越远的时候,我们看它就越小,在CATransform3D有一个值是控制透视效果的,就是 m34 .默认的情况下 m34的值是零,意味着我们是在元素的角度看到UI,我们把上面的代码稍加改造。

CATransform3D transform =   CATransform3DIdentity;
transform.m34 = -1.0 / 500.0;
transform = CATransform3DRotate(transform,M_PI_4, 0, 1, 0);
imageView.layer.transform = transform;

image

这次看上去就对了。我们将m34 的值设为 -1.0/d .d 表示的就是虚拟的摄像头跟屏幕的距离,因为摄像头并不是真的存在,这个值通常是靠感觉来调整的,一般来说 500 到 1000 都是不错的值。 可以多尝试几次。

消失点

艺术家或者工程师在纸上表现立体图时,常用一种透视法,这种方法源于人们的视觉经验:大小相同的物体,离你较近的看起来比离你较远的大。如当你沿着铁路线去看两条铁轨,沿着公路线去看两边排列整齐的树木时,两条平行的铁轨或两排树木连线交与很远很远的某一点,这点在透视图中叫做消失点。 凡是平行的直线都消失于无穷远处的同一个点,消失于视平线上的点的直线都是水平直线。(百度百科

image

在2D的 transform 中我们知道做变换的Layer都有一个锚点。在3D变换中,消失点就相当于一个锚点。 当我们在3D坐标系中,每次更改Layer的位置,同时也就更改消失点的位置。如果我们打算通过调整一个LayerM34 来显示3D效果,那么 我们应该把先消失点定位在屏幕的中心,然后移动它到相应的位置,使得屏幕上所有的Layer可以共享同一个消失点。已达到一个整体的3D效果。

sublayerTransform

如果我们有多个View或者Layer的时候,并且它们每一个都有单独的3D变换,这时候我们有必要使用统一的M34的值,来保证它们在屏幕上显示的时候有个共同的消失点。

CALayer有个属性叫做 sublayerTransform 它也是一个 CATransform3D ,它只是在变换的时候作用于 subLayer 。这意味着我们可以在superLayer上设置变换,subLayer都将自动继承。消失点会设置在superLayer上,也就不必在单独给每一个subLayer设置消失点了。 我们看下面这个栗子。

UIView *superImageView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 320, 200)];
superImageView.backgroundColor = [UIColor redColor];
superImageView.center = self.view.center;

UIImage *image = [UIImage imageNamed:@"DragonMedium"];
UIImageView *leftimageView = [[UIImageView alloc] initWithImage:image];
UIImageView *rightImageView = [[UIImageView alloc] initWithImage:image];
leftimageView.backgroundColor = [UIColor greenColor];
rightImageView.backgroundColor = [UIColor greenColor];
leftimageView.frame = CGRectMake(0, 25 , 150,150);
rightImageView.frame = CGRectMake(170, 25 ,150,150);

[superImageView addSubview:leftimageView];
[superImageView addSubview:rightImageView];


CATransform3D superTransform = CATransform3DIdentity;
superTransform.m34 = -1.0/500.0;
superImageView.layer.sublayerTransform = superTransform;

CATransform3D transformLeft = CATransform3DMakeRotation(M_PI_4, 0, 1, 0);
CATransform3D transformRight = CATransform3DMakeRotation(M_PI_4, 0, -1, 0);
leftimageView.layer.transform = transformLeft;
rightImageView.layer.transform = transformRight;

[self.view addSubview:superImageView];

image

镜像

当我们把上面得例子中得rightImageViewtransform 去掉。leftImageView的 弧度参数从MPI4 变成 M_PI 。发生了什么?

image

我们得到了一个Layer的镜像。在3D变幻中有时候这种反面的镜像的计算和绘制是没有必要的,比如在一个不透明的物体做3D变幻的时候。对于背面的东西既然是不可见的也就没必要浪费资源显示。所以 CALayer有个属性叫做 doubleSided 可以关掉。

当一个Layer和它所包含的Layer做相反的变换会发生什么呢?

我们现在试着把superLayer向右旋转 45度,把subLayer向左旋转45度。

UIView *superImageView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 200, 200)];
superImageView.backgroundColor = [UIColor redColor];

UIImage *image = [UIImage imageNamed:@"DragonMedium"];
UIImageView *imageView = [[UIImageView alloc] initWithImage:image];
imageView.backgroundColor = [UIColor greenColor];
imageView.frame = CGRectMake(0,0 ,150,150);
imageView.center = superImageView.center;

[superImageView addSubview:imageView];
superImageView.center = self.view.center;

CATransform3D transformLeft = CATransform3DMakeRotation(M_PI_4, 0, 0, 1);
CATransform3D transformRight = CATransform3DMakeRotation(-M_PI_4, 0, 0, 1);
superImageView.layer.transform = transformLeft;
imageView.layer.transform = transformRight;

[self.view addSubview:superImageView];

image

我们在试试3D显示。将上面栗子中的3D transform 修改成下面这样

CATransform3D transformLeft = CATransform3DIdentity;
transformLeft.m34 = -1.0 / 500.0;
transformLeft = CATransform3DRotate(transformLeft, M_PI_4, 0, 1, 0);

CATransform3D transformRight = CATransform3DIdentity;
transformRight.m34 = -1.0 / 500.0;
transformRight = CATransform3DRotate(transformRight, -M_PI_4, 0, 1, 0);

superImageView.layer.transform = transformLeft;
imageView.layer.transform = transformRight;

image 我们发现绿色部分的变换比以前例子中的效果来变形的非常厉害,这是因为在做3D变换的时候每个Layer的坐标系都是以superLayer为参照的。也就是说 红色Layer跟绿色Layer并不在同一个参照系里。当我们要用 Core Animation创建一些复杂的3D效果的时候,要注意这点。

Core Animation (六)CGAffineTransform

变换

图层一旦创建,你就可以通过矩阵变换来改变一个图层的几何形状。CGAffineTransform ,用于 图层的旋转,缩放,位移,还有 CATransform3D 它可以把二维的平面转换到三维矩阵中。

Affine Transforms

UIView 有个属性是 transform,transform 的类型就是 CGAffineTransform 通过它我们可以对UIView做一些二维的旋转缩放之类的操作。CGAffineTransform 的定义如下

struct CGAffineTransform {
      CGFloat a, b, c, d;
      CGFloat tx, ty;
};

它其实表示的是一个矩阵

image

因为最后一列总是是(0,0,1),所以有用的信息就是前面两列,对一个view进行仿射变化就相当于对view上的每个点做一个乘法,结果就是

image

下面举例说明几个转换运算的数学实现,x y 是原先点的坐标,下面是矩阵转换的计算公式

image

恒等矩阵

恒等矩阵就是输入什么坐标,出来什么坐标,没有转换

image

计算的结果如下,计算之前的x y 跟计算之后的 x‘ y’ 是相等的。

image

平移矩阵

image

通过平移矩阵的计算公式如下

image

缩放矩阵

image

缩放矩阵的计算公式

image

旋转矩阵

image

旋转矩阵的计算公式

image

旋转加平移矩阵

image

下面就是组合起来的计算公式

image

AffineTransform类描述了一种二维仿射变换的功能,它是一种二维坐标到二维坐标之间的线性变换,保持二维图形的“平直性”(即变换后直线还是直线不会打弯,圆弧还是圆弧)和“平行性”(是指保二维图形间的相对位置关系不变,平行线还是平行线,而直线上点的位置顺序不变,另特别注意向量间夹角可能会发生变化。)仿射变换可以通过一系列的原子变换的复合来实现,包括:平移(Translation)、缩放(Scale)、翻转(Flip)、旋转(Rotation)和错切(Shear)

image

创建一个 CGAffineTransform

看了上面的的矩阵和计算有点叫人挠头。 不过 Core Graphics为了方便开发者,提供了好多内建的函数,可以使我们不必学习复杂的数学知识,就可以实现上面的操作。 下面的方法都可以创建 CGAffineTransform

函数| 操作 ------------ | ------------- CGAffineTransformMakeTranslation | 新的平移矩阵要移动到距离远点的位置
CGAffineTransformMakeRotation | 构建一个新的旋转矩阵 参数是旋转的弧度。 CGAffineTransformMakeScale | 构建一个新的缩放矩阵指定多少拉伸或收缩坐标x和y值。

我们看下面这个例子,它把LLVM的logo 旋转了 45°:

UIImage *image = [UIImage imageNamed:@"DragonMedium"];
UIImageView *imageView = [[UIImageView alloc] initWithImage:image];
imageView.frame = CGRectMake(0, 0 , CGRectGetWidth(imageView.frame),
                                     CGRectGetHeight(imageView.frame));
imageView.center = self.view.center;
imageView.transform = CGAffineTransformMakeRotation(M_PI_4);
[self.view addSubview:imageView];

image

我们在旋转ImageView的时候 传给 CGAffineTransformMakeRotation 的参数是一个宏定义 MPI4,并不是 45°。旋转的单位采用弧度(radians),而不是角度(degress)。以下两个函数,你可以在 弧度和角度之间切换。

CGFloat DegreesToRadians(CGFloat degrees) {return degrees * M_PI / 180;};
CGFloat RadiansToDegrees(CGFloat radians) {return radians * 180 / M_PI;};

弧度通常指定使用的数学常数π(圆周率)的倍数。 π弧度等于180度,所以π除以4是相当于45度。 C 的math库,方便地提供常数π的公倍数,MPI4是常数,表示π除以4。如果你想使用弧度时候,你可以直接使用这些宏。

#define M_E         2.71828182845904523536028747135266250   /* e              */
#define M_LOG2E     1.44269504088896340735992468100189214   /* log2(e)        */
#define M_LOG10E    0.434294481903251827651128918916605082  /* log10(e)       */
#define M_LN2       0.693147180559945309417232121458176568  /* loge(2)        */
#define M_LN10      2.30258509299404568401799145468436421   /* loge(10)       */
#define M_PI        3.14159265358979323846264338327950288   /* pi             */
#define M_PI_2      1.57079632679489661923132169163975144   /* pi/2           */
#define M_PI_4      0.785398163397448309615660845819875721  /* pi/4           */
#define M_1_PI      0.318309886183790671537767526745028724  /* 1/pi           */
#define M_2_PI      0.636619772367581343075535053490057448  /* 2/pi           */
#define M_2_SQRTPI  1.12837916709551257389615890312154517   /* 2/sqrt(pi)     */
#define M_SQRT2     1.41421356237309504880168872420969808   /* sqrt(2)        */
#define M_SQRT1_2   0.707106781186547524400844362104849039  /* 1/sqrt(2)      */

组合Transforms

Core Graphics 还提供了另外一系列的函数,使开发者可以方便的组合 Transforms

例如

函数| 操作 ------------ | ------------- GAffineTransformRotate(CGAffineTransform t, CGFloat angle) | 原始的基础上加上偏移 CGAffineTransformScale(CGAffineTransform t, CGFloat sx, CGFloat sy) | 加上缩放 CGAffineTransformTranslate(CGAffineTransform t, CGFloat tx, CGFloat ty) | 加上旋转

当我们操作 Transforms 的时候,我们经常会创建一个初始的什么也不做的Transform,就像 pointzero 或者 nil。在矩阵的世界中,恒等矩阵就是干这个用的。 Core Graphics 为我们提供了

CGAffineTransformIdentity

当我们想组合两个已经存在的 transform的时候,我们可以使用

CGAffineTransformConcat(CGAffineTransform t1, CGAffineTransform t2);

下面的代码使LLVM的logo 缩小了50% 旋转了45°,并且向下移动了100的位置。

UIImage *image = [UIImage imageNamed:@"DragonMedium"];
UIImageView *imageView = [[UIImageView alloc] initWithImage:image];
imageView.frame = CGRectMake(0, 0 , CGRectGetWidth(imageView.frame), 
                                    CGRectGetHeight(imageView.frame));
imageView.center = self.view.center;

CGAffineTransform transform = CGAffineTransformIdentity;
transform = CGAffineTransformScale(transform, 0.5, 0.5);
transform = CGAffineTransformTranslate(transform, 0, 100);

imageView.transform = CGAffineTransformRotate(transform, M_PI_4);
[self.view addSubview:imageView];

image

Shear Transform

Shear Transform 是第四种 Affine Transform (我也不知道该怎么翻译这个 Shear Transform)。和旋转 缩放 移动 不同的是 Core Graphics并没有提供现成的函数来做 Shear Transform,不过我们可以自己来实现他。

CGAffineTransform CGAffineTransformMakeShear(CGFloat x, CGFloat y)
{
    CGAffineTransform transform = CGAffineTransformIdentity;
    transform.c = -x;
    transform.b = y;
    return transform;
}

下面给imageview 添加上 transform

UIImage *image = [UIImage imageNamed:@"DragonMedium"];
UIImageView *imageView = [[UIImageView alloc] initWithImage:image];
imageView.backgroundColor = [UIColor greenColor];
imageView.frame = CGRectMake(0, 0 , CGRectGetWidth(imageView.frame), 
                                    CGRectGetHeight(imageView.frame));
imageView.center = self.view.center;
imageView.transform = CGAffineTransformMakeShear(0.6,0);

[self.view addSubview:imageView];

效果如下

image

Core Animation (五)Layer Masking

Layer Masking (五)

Layer Masking

当使用 masksToBounds 的时候,它会沿着layer的边框进行裁切超出的部分。包括使用 cornerRadius也是这样,裁出一个圆角矩形来。但是有时候我们想绘制的并不是一个矩形,或者一个圆角矩形,比如我现在想以五角星的形状来显示一个image对象,在比如我想在滚动一个显示文本的View的时候可以叫他优雅的在屏幕边缘淡出,而不是笔直的切割显示区域的边缘。 有两种方法可以实现,一种就是用 带 alpha 透明的PNG图片盖在要显示的区域上,当我们在程序中要动态的裁切图片显示区域的时候这个方法可能就不实用了。

CAlayer 有个mask属性, mask本身就是一个CAlayer,它和其他任何的Layer没什么不同。使用mask layer的时候,跟其他的sublayer没什么区别,不同的是它并像一个普通的subLayer那样显示在superLayer上 ,它决定了superLayer的可显示区域。 mask Layer的color 属性就可以忽略了。mask会把superLayer裁切,只显示符合 Mask 大小的部分.

UIView *viewA = [[UIView alloc] initWithFrame:CGRectMake(100, 80, 100, 100)];
UIView *viewB = [[UIView alloc] initWithFrame:CGRectMake(350, 80, 100, 100)];
UIImage *image = [UIImage imageNamed:@"kangaroo.png"];
UIImageView *imageView = [[UIImageView alloc] initWithImage:image];
imageView.frame = CGRectMake(250, 100 , CGRectGetWidth(imageView.frame), 
                                         CGRectGetHeight(imageView.frame));
viewB.backgroundColor = [UIColor greenColor];
viewA.backgroundColor = [UIColor greenColor];

CALayer *layer = [CALayer layer];

layer.frame = CGRectMake(10, 20, image.size.width, image.size.height);
layer.contents = (id)image.CGImage;
viewB.layer.mask = layer;

[self.view addSubview:imageView];
[self.view addSubview:viewA];
[self.view addSubview:viewB];

image

Core Animation (四) Visual Effects

Visual Effects (四)

圆角

IOS UI 的一个显著特点就是无处不在的圆角矩形,从主屏的图标,alert,文本输入框。我们也可以在程序中创建圆角矩形,而不需要Photoshop。

CAlayer 有个 cornerRadius 属性可以控制Layer 直角的弯曲度。它是一个默认值为零的浮点值。默认的情况下,圆角的效果只会影Layer的背景颜色,并不会影响你设置的背景图片,或者加在Layer上面的SubLayermasksToBounds设置为 YES 可以实现裁切SubLayer的功能。代码如下

UIView *greenView = [[UIView alloc] initWithFrame:CGRectMake(100,100 , 160, 160)];
greenView.backgroundColor = [UIColor greenColor];
UIView *redView = [[UIView alloc] initWithFrame:CGRectMake(-20,-20, 60, 60)];
redView.backgroundColor = [UIColor redColor];
[greenView addSubview:redView];
greenView.layer.cornerRadius = 20;
[self.view addSubview:greenView];

image

当设置了

    greenView.layer.masksToBounds = YES;

效果就是这样的,红色的layer的右上角也被剪切了。 image

Layer Borders

CALayer另外一个非常有用的属性就是 borderWidthborderColor 这两个属性组合起来控制Layer的边框。 borderColor 的类型不是UIColorCGColorRef。 边框是是绘制在Layerbounds里面并且覆盖在其他所有的 SubLayer 的上面。我们还用这个greenView 来做实验。

        greenView.layer.borderWidth = 5.0f;

image

Drop Shadows

对于IOS的设计来说,另外一个特征就是阴影。当我们给LayershadowOpacity 属性设置了一个大于零的值,阴影就会出现在 Layer 的后面。 shadowOpacity 是一个 大于0.0(透明) 小于1.0(全黑) 的浮点数。此外,我们还可以通过 shadowColor, shadowOffset, shadowRadius来调整 阴影的效果。

UIView *viewA = [[UIView alloc] initWithFrame:CGRectMake(100, 80, 150, 150)];
UIView *viewB = [[UIView alloc] initWithFrame:CGRectMake(300, 80, 150, 150)];
viewA.backgroundColor = [UIColor greenColor];
viewB.backgroundColor = [UIColor redColor];

viewA.layer.shadowOpacity = 1.0;
viewB.layer.shadowOpacity = 0.5;

viewA.layer.shadowOffset = CGSizeMake(0, -15);
viewB.layer.shadowOffset = CGSizeMake(5, 5);

viewA.layer.shadowRadius = 30;
viewB.layer.shadowRadius = 1;

viewA.layer.shadowColor = [UIColor redColor].CGColor;
viewB.layer.shadowColor = [UIColor greenColor].CGColor;


[self.view addSubview:viewA];
[self.view addSubview:viewB];

image

Shadow Clipping

Layer的边框不一样,阴影会依照Layer包含的内容的确切的形状来显示。还有一个问题就是当使用 masksToBounds 的时候,阴影的显示也会有问题。

UIView *redViewA = [[UIView alloc] initWithFrame:CGRectMake(-20,-20, 70, 70)];
redViewA.backgroundColor = [UIColor redColor];
UIView *redViewB = [[UIView alloc] initWithFrame:CGRectMake(-20,-20, 70, 70)];
redViewB.backgroundColor = [UIColor redColor];


UIView *viewA = [[UIView alloc] initWithFrame:CGRectMake(100, 80, 150, 150)];
UIView *viewB = [[UIView alloc] initWithFrame:CGRectMake(300, 80, 150, 150)];
viewB.layer.masksToBounds = YES;

[viewA addSubview:redViewA];
[viewB addSubview:redViewB];

viewB.backgroundColor = [UIColor greenColor];
viewA.backgroundColor = [UIColor greenColor];
viewA.layer.shadowOffset = CGSizeMake(0, 10);
viewB.layer.shadowOffset = CGSizeMake(0, 10);
viewA.layer.shadowOpacity = 0.7;
viewB.layer.shadowOpacity = 0.7;

image

viewB因为使用了 layer.masksToBounds = YES; 导致阴影消失了。我们怎么解决这个问题呢? 给viewB一个同样大小的superView 叫这个superView 添加阴影。下面的代码是这个问题的解决方法。

UIView *redViewA = [[UIView alloc] initWithFrame:CGRectMake(-20,-20, 70, 70)];
UIView *redViewB = [[UIView alloc] initWithFrame:CGRectMake(-20,-20, 70, 70)];
redViewA.backgroundColor = [UIColor redColor];
redViewB.backgroundColor = [UIColor redColor];


UIView *viewA = [[UIView alloc] initWithFrame:CGRectMake(100, 80, 150, 150)];
UIView *viewB = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 150, 150)];
viewB.layer.masksToBounds = YES;
[viewA addSubview:redViewA];
[viewB addSubview:redViewB];

viewB.backgroundColor = [UIColor greenColor];
viewA.backgroundColor = [UIColor greenColor];
viewA.layer.shadowOffset = CGSizeMake(0, 10);
viewB.layer.shadowOffset = CGSizeMake(0, 10);
viewA.layer.shadowOpacity = 0.7;
viewB.layer.shadowOpacity = 0.7;


UIView *shadowViewB = [[UIView alloc] initWithFrame:CGRectMake(300, 80, 150, 150)];
shadowViewB.layer.shadowOffset = CGSizeMake(0, 10);
shadowViewB.layer.shadowOpacity = 0.7f;
[shadowViewB addSubview:viewB];

[self.view addSubview:viewA];
[self.view addSubview:shadowViewB];

image 这样一来 ViewB也有阴影啦。

ShadowPath

有时候我们给 Layer 添加的阴影并不总是正方形,如果我们想要其他形状的阴影怎么办呢。这时候 shadowPath 就派上用场了。阴影的实时计算是非常消耗手机性能的,尤其是当 Layer 包含大量的 带 alpha 遮罩 sublayer 的时候。 ShadowPath 是一个 CGPathRef (CGPath 的指针)。CGPath 是一个Core Graphics 的用来表示矢量形状的对象。我们可以用它来定义shadow的形状。

UIView *viewA = [[UIView alloc] initWithFrame:CGRectMake(100, 80, 100, 100)];
UIView *viewB = [[UIView alloc] initWithFrame:CGRectMake(300, 80, 100, 100)];

viewB.backgroundColor = [UIColor greenColor];
viewA.backgroundColor = [UIColor greenColor];
viewA.layer.shadowOpacity = 0.5;
viewB.layer.shadowOpacity = 0.5;


CGMutablePathRef squarePath = CGPathCreateMutable();
CGPathAddRect(squarePath, NULL, CGRectMake(-25, -25, 150, 150));
viewA.layer.shadowPath = squarePath;
CGPathRelease(squarePath);


CGMutablePathRef circlePath = CGPathCreateMutable();
CGPathAddEllipseInRect(circlePath, NULL,CGRectMake(-25, -25, 150, 150));
viewB.layer.shadowPath = circlePath;
CGPathRelease(circlePath);

[self.view addSubview:viewA];
[self.view addSubview:viewB];

image

当我们绘制一些简单的矩形或者圆形时可以直接使用CGPath。但是当需要一些复杂的形状的时候,比如圆角矩形 或者 五角星之类的 ,我们可以使用 UIBezierPath ,它是 UIKit 提供的 objc 封装的CGPath 要简单的多。