容易忽略的那些小点总结 (二) —— CALayer相关(一)

版本记录

版本号 时间
V1.0 2018.01.21

前言

在苹果的API文档中,有很多属性和方法我们用的不是很多,所以很容易忽略和出错,下面我就用这一个专题专门说一些不常用的API接口,下面开始。感兴趣的可以参考前面几篇文章。
1. 容易忽略的那些小点总结 (一) —— UIView UIViewTintAdjustmentMode相关(一)

doubleSided

这是一个属性,看一下API

/* When false layers facing away from the viewer are hidden from view.
 * Defaults to YES. Animatable. */

@property(getter=isDoubleSided) BOOL doubleSided;

它的作用就是:来控制图层的背面是否要被绘制。这是一个BOOL类型,默认为YES,如果设置为NO,那么当图层正面从相机视角消失的时候,它将不会被绘制。

图层有双面,是否都显示,设置NO意思背面看不到。下图是两个图层分别设置doubleSided为NO和YES翻转180°的效果。


+ (nullable id)defaultValueForKey:(NSString *)key

先看一下这个静态方法

/** Property methods. **/

/* CALayer implements the standard NSKeyValueCoding protocol for all
 * Objective C properties defined by the class and its subclasses. It
 * dynamically implements missing accessor methods for properties
 * declared by subclasses.
 *
 * When accessing properties via KVC whose values are not objects, the
 * standard KVC wrapping conventions are used, with extensions to
 * support the following types:
 *
 *      C Type                  Class
 *      ------                  -----
 *      CGPoint                 NSValue
 *      CGSize                  NSValue
 *      CGRect                  NSValue
 *      CGAffineTransform       NSValue
 *      CATransform3D           NSValue  */

/* Returns the default value of the named property, or nil if no
 * default value is known. Subclasses that override this method to
 * define default values for their own properties should call `super'
 * for unknown properties. */

/*
  CALayer为所有类及其子类定义的Objective C属性实现了标准的NSKeyValueCoding协议。 
  它动态地为子类声明的属性实现缺少的访问器方法。

  当通过KVC访问不是对象的属性时,使用标准的KVC包装约定,扩展名为支持以下几种类型:
       C Type                  Class
       ------                  -----
       CGPoint                 NSValue
       CGSize                  NSValue
       CGRect                  NSValue
       CGAffineTransform       NSValue
       CATransform3D           NSValue  

  返回指定属性的默认值,如果没有默认值,则返回nil。 重写此方法以定义其属性的默认值
  的子类应该为未知属性调用“super”
*/

+ (nullable id)defaultValueForKey:(NSString *)key;

例如:我们新建一个SubLayer类继承自CALayer,则在SubLayer.m中重写此方法。如下:

 + (id)defaultValueForKey:(NSString *)key
{
         if ([key isEqualToString:@"backgroundColor"]) {

             return (id)[UIColor blackColor].CGColor;
         }

         if ([key isEqualToString:@"cornerRadius"]) {

             return @20.0;
         }

         return [super defaultValueForKey:key];
 }

然后,我们在VC里面的view上添加一个SubLayer类型的layer。代码如下:

SubLayer *subLayer = [SubLayer layer];
subLayer.frame = CGRectMake(0,0,40,40);
subLayer.position = CGPointMake(100, 30);
[self.view.layer addSublayer:subLayer];

运行起来,就会发现

正好是一个黑色的圆形。

我们也可以在SubLayer中重写- (instancetype)init
代码如下:

- (instanceType)init
{
    self = [super init];
    if(self){
     self.transform=CATransform3DMakeRotation(M_PI_2, 1, 1, 1);
    }
}  
SubLayer *subLayer = [SubLayer layer];
subLayer.frame = CGRectMake(0,0,40,40);
subLayer.position = CGPointMake(100, 30);
[self.view.layer addSublayer:subLayer];

这样我们做出来的SubLayer就会旋转90度,具体效果如下所示。


+ (BOOL)needsDisplayForKey:(NSString *)key;

还是先看一下API

/* Method for subclasses to override. Returning true for a given
 * property causes the layer's contents to be redrawn when the property
 * is changed (including when changed by an animation attached to the
 * layer). The default implementation returns NO. Subclasses should
 * call super for properties defined by the superclass. (For example,
 * do not try to return YES for properties implemented by CALayer,
 * doing will have undefined results.) */

/*
  子类重写的方法。 对给定的属性,当属性发生更改(包括由附加到图层的动画更改)时,
  将导致图层的内容重绘,这时返回true。默认实现返回NO。 子类应该为超类定义的属性
  调用super。 (例如,对于CALayer实现的属性不要试图返回YES是由,这样做会有未知的结果。)
*/

+ (BOOL)needsDisplayForKey:(NSString *)key;

首先了解下layer自己的属性如何实现动画。

  • layer首次加载时会调用 +(BOOL)needsDisplayForKey:(NSString *)key方法来判断当前指定的属性key改变是否需要重新绘制。

  • Core Animartion中的key或者keypath等于+(BOOL)needsDisplayForKey:(NSString *)key方法中指定的key,便会自动调用setNeedsDisplay方法,这样就会触发重绘,达到我们想要的效果。

下面我们就看一下layer方法响应链

  • [layer setNeedDisplay] -> [layer displayIfNeed] -> [layer display] -> [layerDelegate displayLayer:]

  • [layer setNeedDisplay] -> [layer displayIfNeed] -> [layer display] -> [layer drawInContext:] -> [layerDelegate drawLayer: inContext:]

说明一下,如果layerDelegate实现了displayLayer:协议,之后layer就不会再调用自身的重绘代码。一会的示例代码中就采用第二种响应链进行绘制。

下面看一下示例代码

1. ViewController.m
#import "ViewController.h"
#import "JJCircleLayer.h"

@interface ViewController ()

@property (nonatomic, strong) JJCircleLayer *layer;

@end

@implementation ViewController

- (void)viewDidLoad
{
    [super viewDidLoad];

    self.layer = [[JJCircleLayer alloc] init];
    self.layer.frame = CGRectMake(100, 200, 200, 200);
    self.layer.backgroundColor = [UIColor lightGrayColor].CGColor;
    [self.view.layer addSublayer:self.layer];
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    [self.layer animateCircle];
}

@end
2. JJCircleLayer.h
#import <QuartzCore/QuartzCore.h>

@interface JJCircleLayer : CALayer

- (void)animateCircle;

@end
3. JJCircleLayer.m
#import "JJCircleLayer.h"
#import <UIKit/UIKit.h>

@interface JJCircleLayer() <CAAnimationDelegate>

@property (nonatomic, assign) CGFloat progress;

@end

@implementation JJCircleLayer

#pragma mark - Override Base Function

+ (BOOL)needsDisplayForKey:(NSString *)key
{
    BOOL result;
    if ([key isEqualToString:@"progress"]) {
        result = YES;
    }
    else {
        result = [super needsDisplayForKey:key];
    }
    return result;
}

- (void)drawInContext:(CGContextRef)ctx
{
    NSLog(@"progress: %f", self.progress);
    CGContextSetLineWidth(ctx, 5.0f);
    CGContextSetStrokeColorWithColor(ctx, [UIColor blueColor].CGColor);
    CGContextAddArc(ctx, CGRectGetWidth(self.bounds) * 0.5, CGRectGetHeight(self.bounds) * 0.5, CGRectGetWidth(self.bounds) * 0.5 - 6, 0, 2 * M_PI * self.progress, 0);
    CGContextStrokePath(ctx);
}

#pragma mark - Object Public Function

- (void)animateCircle
{
    CAKeyframeAnimation *anim = [CAKeyframeAnimation animationWithKeyPath:@"progress"];
    anim.values = [self valuesListWithAnimationDuration: 3];
    anim.duration = 3.0;
    anim.fillMode = kCAFillModeForwards;
    anim.removedOnCompletion = NO;
    anim.delegate = self;
    [self addAnimation:anim forKey:@"circle"];
}

#pragma mark - Object Private Function

- (NSMutableArray *)valuesListWithAnimationDuration:(CGFloat)duration
{
    NSInteger numberOfFrames = duration * 60;
    NSMutableArray *values = [NSMutableArray array];
    // 注意这里的 fromValue和toValue是针对的progress的值的大小。
    CGFloat fromValue = 0.0;
    CGFloat toValue = 1.0;
    CGFloat diff = toValue - fromValue;
    for (NSInteger frame = 1; frame <= numberOfFrames; frame++) {
        CGFloat piece = (CGFloat)frame / (CGFloat)numberOfFrames;
        CGFloat currentValue = fromValue + diff * piece;
        [values addObject:@(currentValue)];
    }
    return values;
}

#pragma mark - CAAnimationDelegate
    
- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag
{
    [self removeAnimationForKey:@"circle"];
    self.progress = 1.0;
    [self setNeedsDisplay];
}

@end

下面看一下效果

这里还有几点需要注意,对于needsDisplayForKey方法:

  • 此方法只会在图层初始化的时候被调用一次。
  • 代码中通过判断图层的属性名称来决定是否需要对对应的Core Animation动画执行UI重绘工作(本例中就是对自定义的progress属性进行处理)。
  • [super needsDisplayForKey:key]; 这个父类方法默认的返回值是NO。
  • 因为在needsDisplayForKey方法中指定了key的值是progress,所以这里的animationWithKeyPath动画操作会在动画执行期间,不停的促发Core Graphics的重绘工作,即不停的调用 - (void)drawInContext:(CGContextRef)ctx方法进行绘制。
  • fillModeremoveOnCompletion 两个属性指定动画在绘制完成后,对应的动画对象不会从内存中移除掉。

后记

本篇已结束,下一篇更精彩~~~

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,843评论 6 502
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,538评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,187评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,264评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,289评论 6 390
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,231评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,116评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,945评论 0 275
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,367评论 1 313
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,581评论 2 333
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,754评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,458评论 5 344
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,068评论 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,692评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,842评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,797评论 2 369
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,654评论 2 354

推荐阅读更多精彩内容