08 Sep 2013
Core Animation (三)
布局
UIView 有三个跟布局有关的属性,frame bounds center。 CALayer 也有三个类似的属性,frame bounds position 。view 使用 center ,layer 使用position。实际上他们是一样的。

frame 跟bounds的区别就是参照系不一样。 frame是以superView 做参照系。 而bounds 是以自己做参照系。上面这幅图就是个很好的例子。layer 跟view是一样的。

frame 并不是个确定的值。它会根据 bounds center 以及做动画的时候的transform 动态的改变。例如上面这个图片旋转了45度 frame就变大了。这时。frame的size就跟bounds的size 不一样了。

旋转之后的frame 变大了。
锚点(anchorPoint)
图层的 anchorPoint 属性指定了一个基于图层 bounds 的符合位置坐标系的位置。锚点指定了 bounds 相对于 position 的值,同时也作为变换时候的支点。锚点使用单元空间坐标系表示,(0.0,0.0)点接近图层 的原点,而(1.0,1.0)是原点的对角点。改变图层的父图层的变换属性将会影响到 anchorPoint 的方向,具体变化取决于父图层坐标系的 Y 轴。
当你设置图层的 frame 属性的时候,position 会根据锚点相应的改变,而当你设置图层的 position 属性的时候,bounds会根据锚点做相应的改变。锚点的默认值是(0.5,0.5).
下面几幅图片展示了锚点的位置对于 position的影响。

图层的坐标系
我们可以通过 CALayer的方法 在进行坐标转换。
- (CGPoint)convertPoint:(CGPoint)point fromLayer:(CALayer *)layer;
- (CGPoint)convertPoint:(CGPoint)point toLayer:(CALayer *)layer;
- (CGRect)convertRect:(CGRect)rect fromLayer:(CALayer *)layer;
- (CGRect)convertRect:(CGRect)rect toLayer:(CALayer *)layer;
23 Aug 2013
Core Animation (二)
The contents Image
CALayer 有个属性叫做 contents 是个id类型mac平台 core animation 的产物,用这个属性我们可以非常简单的使一个CALayer 显示一张image。
CALayer *layer = [CALayer layer];
layer.frame = CGRectMake(50, 50, 220, 220);
[self.view.layer addSublayer:layer];
UIImage *image = [UIImage imageNamed:@"testImage.png"];
layer.contents = (__bridge id)image.CGImage;

contentsGravity
额 宝马车被砸扁了。这有点不正常,怎么办呢,我的图片是个长方形,但是我的layer是个正方形。
当我们使用UIImageView显示图片的时候遇到这种情况下,我们可以使用
view.contentMode = UIViewContentModeScaleAspectFit;
layer 也有类似的属性,它叫做 contentsGravity 跟UIView的contentMode enum 定义不一样,contentsGravity是NSString类型。
NSString * const kCAGravityCenter
NSString * const kCAGravityTop
NSString * const kCAGravityBottom
NSString * const kCAGravityLeft
NSString * const kCAGravityRight
NSString * const kCAGravityTopLeft
NSString * const kCAGravityTopRight
NSString * const kCAGravityBottomLeft
NSString * const kCAGravityBottomRight
NSString * const kCAGravityResize
NSString * const kCAGravityResizeAspect
NSString * const kCAGravityResizeAspectFill
我们可以使用 kCAGravityResizeAspect 等同于
UIViewContentModeScaleAspectFit 我们可以是图片适应 layer的大小 而不会生变形。汽车就不会被拍瘪了。
layer.contentsGravity = kCAGravityResizeAspect;

contentsScale
contentsScale定义了两个像素点之间的距离的比例。是个float类型 默认值为1.0;
contentScale的用在我的汽车图片的时候并没有效果,因为 contents的图片已经被 contentsGravity 进行过缩放处理了,已适应,layer的bounds。
如果我们想简单的缩放layer 的 contents 图片,我们可以使用 transform或者 affinetransform .
contentsScale 到底是用来干嘛的呢?他其实是用来支持高分屏的。它用来查看当image绘制的时候应该绘制的大小。前提是图片没有被 contentsGravity压缩过,UIView 有个类似的很少用的属性 contentScaleFactor。
当contentsScale设为 1.0 时,在屏幕上绘制的每个点一个像素,2.0 就是每个点 2个像素。这就是Apple 对Retina的解决方案。
kCAGravityResizeAspect会压缩我们的图片。当我们换成 kCAGravityCenter 看看会发生什么

额 只能看见车头的一点点,并且整张图片被像素化了。当我们给contents 设置CGImage 对象的时候,缩放机制就失去作用,我们现在可以使用 contentsScale来修正我们的image 显示了
self.layerView.layer.contentsScale = image.scale;

当我们在程序中使用 layer显示图片的时候。我们要记着,设置layer的contentsScale符合屏幕的scale;否则我们的图片会像素画在高分屏上。
layer.contentsScale = [UIScreen mainScreen].scale;
masksToBounds
我们的汽车图片现在显示的正确的大小了。但是我们不想显示超出layer 大小的部分。 UIView 有个叫属性叫做 clipsToBounds 用来决定是否显示超出自身大小的部分。CALayer 也有个类似的属性叫做 masksToBounds 当我们设置成 YES的时候,就是这样。

contentsRect
contentsRect是个CALayer的属性 可以指定layer显示image的某个区域。和 bounds frame 不一样的是 contentsRect 的度量单位并不是 points,而是坐标。坐标的范围是 (0 ~ 1.0)而且取得是相对值。
默认的contentsRect的值 是 {0,0,1,1}如果我们设置contentsRect为{0,0,0.5,0,5}
则layer显示的image如下

contentsCenter
最后一个和内容相关的属性就是 contentsCenter。看名字大家觉得这个东西可能是个中心点之类的。但是实际上他是一个 CGRect ,它指定图片的拉伸区域。默认情况下, contentsCenter 是{0,0,1,1},当layer 改变大小的时候,image将会均匀的拉伸。当我们增加起始的位置,减小size的时候,我们就围着图片产生了一个边界。下面这个图显示了 contentsCenter的值为 {0.25,0.25,0.5,0.5}的时候缩放的行为 是什么样儿的。

这个跟UIImage的resizableImageWithCapInsets是一样的效果
Custom Drawing
设置 layer 的contents 并不是唯一的显示Image 的方法, UIView的子类实现了 -drawRect: 方法 可以自定义UI。drawRect 方法会给View创建一个新的 背景image 对象, ,所以如果我们不需要这个image的话,就不要实现空的drawRect 方法。会浪费内存和CPU资源。
CAlayer 还有个 optional delegate 实现了 CALayerDelegate的协议。当CALayer 需要特殊信息的时候可以通过delegate 获得。
- (void)displayLayer:(CALayer *)layer;
- (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx;
- (void)layoutSublayersOfLayer:(CALayer *)layer;
- (id<CAAction>)actionForLayer:(CALayer *)layer forKey:(NSString *)event;
当layer 重绘之前会依次调用这几个方法。
- (void)viewDidLoad
{
[super viewDidLoad];
CALayer *layer = [[CALayer alloc] init];
layer.frame = CGRectMake(50, 50, 100, 100);
layer.masksToBounds = YES;
layer.delegate = self;
layer.backgroundColor = [UIColor blueColor].CGColor;
[self.view.layer addSublayer:layer];
[layer display];
}
-(void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx{
CGContextSetLineWidth(ctx, 10.0f);
CGContextSetStrokeColorWithColor(ctx, [UIColor redColor].CGColor);
CGContextStrokeEllipseInRect(ctx, layer.bounds);
}

我们主动的调用了 layer的 display方法,来强制它刷新,跟UIView不一样的是 CALayer 当它显示在屏幕上之后,它不会自动的重绘。需要程序员自己小心的决定他的重绘时机。
我们画的圆圈儿超出layer 大小的部分呗剪切掉了没有显示,尽管我们没有使用masksToBounds属性。这是因为我们绘制的时候调用的 CALayerDelegate CALayer 创建的绘制区域 conext的大小是由layer的大小决定的。所以没有可能在超出的区域绘制。
除非我们创建了单独的layer 否则尽量不要使用delegate,尽量不要实现delegate的协议方法,这是因为UIView 会创建一个他自己的背景层,自动的设置自己是layer的delegate,并且实现了 -displayLayer:方法。这就是问题的所在,当我们使用 View的背景层的时候,不需要实现 -displayLayer:方法 或者drawLayer:inContext:方法来绘制层的图片,我们只需要实现 drawRect:方法, UIView 会帮我们处理一切,包括layer需要重绘的时候 自动调用 display。
23 Aug 2013
Core Animation (一)
一直很羡慕 github 上很多人写的炫酷的动画跟UI的特效。有时候自己想搞一个动画的时候就去 github 搜一下。作为一个iOS 开发人员 我决定从头学习 Core animation。
Layer & View
Core animation 是一个误导人的名字,大家都觉得看这个名字,这个库应该是主要用来实现动画效果的。实际上,动画只是很小的一部分,更贴切的名字应该叫做 Layer kit。大部分都是关于图层的。屏幕上显示的不同图形图像 其实就是不同的Layer。 Layer 有自己的父图层 也有自己的子图层,它们构成了一个树形的层次结构。这个树形结构构成了UIKit的底层,和屏幕上我们所见的一切。
我们都用过 UIView ,view 就是屏幕上显示文本 图片或者视频的一个矩形区域,并且它可以相应我们的各种手势操作,每个View 可以包含多个view从而构成一种层级结构。父View 管理子View的位置。

在iOS中 所有的View 都继承自UIView,UIView 处理触摸事件,并且支持基于 Core Graphics的显示,仿射变换,例如旋转跟缩放,以及简单的滑动或者翻转动画。但是UIView 并不是处理所有的这些细节,渲染 布局和动画其实是 CALayer 来做的。
CALayer
CALayer其实跟UIView 非常相似,他是一个矩形的方块,在一个层级结构中,包含图片文字或者背景颜色,也是由父类来管理显示的位置。CALayer 有专门的方法和属性来实现动画和几何变换。和UIView 唯一不同的一点就是 Layer 不处理和用户的交互逻辑。
CALayer并不是响应连的一部分,所以无法响应用户的交互,但是它提供了方法来检查用户点击的位置是否在Layer中。
平行的层级结构
每个UIView 都有一个CALayer 对象,UIView可以创建或者删除这个CALayer。CALayer 的层级结构就像是UIView结构的一个镜像。事实上是图层负责显示UI跟动画以及其他你在屏幕上所见到的东西。UIView 只是简单地封装了一下,处理IOS特有的touch事件,并把底层的Core Animation 接口封装成高级接口。
iOS 之所以有UIView 和 CALayer 两套平行的层级结构,是为了更好的解耦代码,将UI和用户的交互逻辑分离开来。因为iOS的多点触控的处理比起Mac OS的键盘鼠标来要复杂的多。事实上如果你的UI布局使用 Core Animation 的框架来写,是可以在iOS和Mac OS 上通用的
图层的作用
CALayer 提供了 UIView没有提供的一些属性,例如
- 阴影,矩形的圆角,边框的颜色
- 3D矩阵变换
- 非矩形的边框
- 透明的遮罩
- 多步,非线性的动画。
14 Apr 2013
什么是单例
单例模式是一种常用的软件设计模式。在它的核心结构中只包含一个被称为单例类的特殊类。通过单例模式可以保证系统中一个类只有一个实例而且该实例易于外界访问,从而方便对实例个数的控制并节约系统资源。如果希望在系统中某个类的对象只能存在一个,单例模式是最好的解决方案。
iOS开发中如何使用单例
传统的单例构造方法
+ (id)sharedInstance
{
static id sharedInstance;
if(sharedInstance == nil){
sharedInstance = [[]self alloc] init]
}
return sharedInstance;
}
多线程下的隐患
在多线程的情况下,如果两个线程几乎同时调用sharedInstance()方法会发生什么呢?有可能会创建出两个该类的实例。 为了防止这种情况 我们通常会加上锁
+ (id)sharedInstance
{
static id sharedInstance;
@synchronized(self)
if(sharedInstance == nil){
sharedInstance = [[]self alloc] init]
}
}
return sharedInstance;
}
dispatch_once
iOS 4.0 引进了 GCD ,其中的 dispatch_once,它即使是在多线程环境中也能安全地工作,非常安全。dispatchonce是用来确保指定的任务将在应用的生命周期期间,仅执行一次。以下是一个典型的源代码以初始化的东西。它可以优雅通过使用dispatchonce来创建一个单例。
+ (id)sharedInstance
{
static dispatch_once_t once;
static id sharedInstance;
dispatch_once(&once, ^{
sharedInstance = [[self alloc] init];
});
return sharedInstance;
}