容易忽略的那些小點(diǎn)總結(jié) (二) —— CALayer相關(guān)(一)

版本記錄

版本號(hào) 時(shí)間
V1.0 2018.01.21

前言

在蘋(píng)果的API文檔中惧财,有很多屬性和方法我們用的不是很多泌射,所以很容易忽略和出錯(cuò)国裳,下面我就用這一個(gè)專(zhuān)題專(zhuān)門(mén)說(shuō)一些不常用的API接口,下面開(kāi)始盗温。感興趣的可以參考前面幾篇文章。
1. 容易忽略的那些小點(diǎn)總結(jié) (一) —— UIView UIViewTintAdjustmentMode相關(guān)(一)

doubleSided

這是一個(gè)屬性成肘,看一下API

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

@property(getter=isDoubleSided) BOOL doubleSided;

它的作用就是:來(lái)控制圖層的背面是否要被繪制卖局。這是一個(gè)BOOL類(lèi)型,默認(rèn)為YES双霍,如果設(shè)置為NO砚偶,那么當(dāng)圖層正面從相機(jī)視角消失的時(shí)候,它將不會(huì)被繪制洒闸。

圖層有雙面染坯,是否都顯示,設(shè)置NO意思背面看不到丘逸。下圖是兩個(gè)圖層分別設(shè)置doubleSided為NO和YES翻轉(zhuǎn)180°的效果单鹿。


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

先看一下這個(gè)靜態(tài)方法

/** 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為所有類(lèi)及其子類(lèi)定義的Objective C屬性實(shí)現(xiàn)了標(biāo)準(zhǔn)的NSKeyValueCoding協(xié)議。 
  它動(dòng)態(tài)地為子類(lèi)聲明的屬性實(shí)現(xiàn)缺少的訪(fǎng)問(wèn)器方法深纲。

  當(dāng)通過(guò)KVC訪(fǎng)問(wèn)不是對(duì)象的屬性時(shí)仲锄,使用標(biāo)準(zhǔn)的KVC包裝約定,擴(kuò)展名為支持以下幾種類(lèi)型:
       C Type                  Class
       ------                  -----
       CGPoint                 NSValue
       CGSize                  NSValue
       CGRect                  NSValue
       CGAffineTransform       NSValue
       CATransform3D           NSValue  

  返回指定屬性的默認(rèn)值湃鹊,如果沒(méi)有默認(rèn)值儒喊,則返回nil。 重寫(xiě)此方法以定義其屬性的默認(rèn)值
  的子類(lèi)應(yīng)該為未知屬性調(diào)用“super”
*/

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

例如:我們新建一個(gè)SubLayer類(lèi)繼承自CALayer币呵,則在SubLayer.m中重寫(xiě)此方法怀愧。如下:

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

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

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

             return @20.0;
         }

         return [super defaultValueForKey:key];
 }

然后,我們?cè)赩C里面的view上添加一個(gè)SubLayer類(lèi)型的layer。代碼如下:

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

運(yùn)行起來(lái)芯义,就會(huì)發(fā)現(xiàn)

正好是一個(gè)黑色的圓形哈垢。

我們也可以在SubLayer中重寫(xiě)- (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];

這樣我們做出來(lái)的SubLayer就會(huì)旋轉(zhuǎn)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.) */

/*
  子類(lèi)重寫(xiě)的方法温赔。 對(duì)給定的屬性,當(dāng)屬性發(fā)生更改(包括由附加到圖層的動(dòng)畫(huà)更改)時(shí)鬼癣,
  將導(dǎo)致圖層的內(nèi)容重繪陶贼,這時(shí)返回true。默認(rèn)實(shí)現(xiàn)返回NO待秃。 子類(lèi)應(yīng)該為超類(lèi)定義的屬性
  調(diào)用super拜秧。 (例如,對(duì)于CALayer實(shí)現(xiàn)的屬性不要試圖返回YES是由章郁,這樣做會(huì)有未知的結(jié)果枉氮。)
*/

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

首先了解下layer自己的屬性如何實(shí)現(xiàn)動(dòng)畫(huà)。

  • layer首次加載時(shí)會(huì)調(diào)用 +(BOOL)needsDisplayForKey:(NSString *)key方法來(lái)判斷當(dāng)前指定的屬性key改變是否需要重新繪制暖庄。

  • 當(dāng)Core Animartion中的key或者keypath等于+(BOOL)needsDisplayForKey:(NSString *)key方法中指定的key聊替,便會(huì)自動(dòng)調(diào)用setNeedsDisplay方法,這樣就會(huì)觸發(fā)重繪培廓,達(dá)到我們想要的效果惹悄。

下面我們就看一下layer方法響應(yīng)鏈

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

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

說(shuō)明一下,如果layerDelegate實(shí)現(xiàn)了displayLayer:協(xié)議肩钠,之后layer就不會(huì)再調(diào)用自身的重繪代碼泣港。一會(huì)的示例代碼中就采用第二種響應(yīng)鏈進(jìn)行繪制。

下面看一下示例代碼

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是針對(duì)的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

下面看一下效果

這里還有幾點(diǎn)需要注意当纱,對(duì)于needsDisplayForKey方法:

  • 此方法只會(huì)在圖層初始化的時(shí)候被調(diào)用一次。
  • 代碼中通過(guò)判斷圖層的屬性名稱(chēng)來(lái)決定是否需要對(duì)對(duì)應(yīng)的Core Animation動(dòng)畫(huà)執(zhí)行UI重繪工作(本例中就是對(duì)自定義的progress屬性進(jìn)行處理)踩窖。
  • [super needsDisplayForKey:key]; 這個(gè)父類(lèi)方法默認(rèn)的返回值是NO坡氯。
  • 因?yàn)樵?code>needsDisplayForKey方法中指定了key的值是progress,所以這里的animationWithKeyPath動(dòng)畫(huà)操作會(huì)在動(dòng)畫(huà)執(zhí)行期間洋腮,不停的促發(fā)Core Graphics的重繪工作廉沮,即不停的調(diào)用 - (void)drawInContext:(CGContextRef)ctx方法進(jìn)行繪制。
  • fillModeremoveOnCompletion 兩個(gè)屬性指定動(dòng)畫(huà)在繪制完成后徐矩,對(duì)應(yīng)的動(dòng)畫(huà)對(duì)象不會(huì)從內(nèi)存中移除掉滞时。

后記

本篇已結(jié)束,下一篇更精彩~~~

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末滤灯,一起剝皮案震驚了整個(gè)濱河市坪稽,隨后出現(xiàn)的幾起案子曼玩,更是在濱河造成了極大的恐慌,老刑警劉巖窒百,帶你破解...
    沈念sama閱讀 218,204評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件黍判,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡篙梢,警方通過(guò)查閱死者的電腦和手機(jī)顷帖,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,091評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)渤滞,“玉大人贬墩,你說(shuō)我怎么就攤上這事⊥唬” “怎么了陶舞?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,548評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)绪励。 經(jīng)常有香客問(wèn)我肿孵,道長(zhǎng),這世上最難降的妖魔是什么疏魏? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,657評(píng)論 1 293
  • 正文 為了忘掉前任停做,我火速辦了婚禮,結(jié)果婚禮上大莫,老公的妹妹穿的比我還像新娘雅宾。我一直安慰自己,他們只是感情好葵硕,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,689評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著贯吓,像睡著了一般懈凹。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上悄谐,一...
    開(kāi)封第一講書(shū)人閱讀 51,554評(píng)論 1 305
  • 那天介评,我揣著相機(jī)與錄音,去河邊找鬼爬舰。 笑死们陆,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的情屹。 我是一名探鬼主播坪仇,決...
    沈念sama閱讀 40,302評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼垃你!你這毒婦竟也來(lái)了椅文?” 一聲冷哼從身側(cè)響起喂很,我...
    開(kāi)封第一講書(shū)人閱讀 39,216評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎皆刺,沒(méi)想到半個(gè)月后少辣,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,661評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡羡蛾,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,851評(píng)論 3 336
  • 正文 我和宋清朗相戀三年漓帅,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片痴怨。...
    茶點(diǎn)故事閱讀 39,977評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡忙干,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出腿箩,到底是詐尸還是另有隱情豪直,我是刑警寧澤,帶...
    沈念sama閱讀 35,697評(píng)論 5 347
  • 正文 年R本政府宣布珠移,位于F島的核電站弓乙,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏钧惧。R本人自食惡果不足惜暇韧,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,306評(píng)論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望浓瞪。 院中可真熱鬧懈玻,春花似錦、人聲如沸乾颁。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,898評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)英岭。三九已至湾盒,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間诅妹,已是汗流浹背罚勾。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,019評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留吭狡,地道東北人尖殃。 一個(gè)月前我還...
    沈念sama閱讀 48,138評(píng)論 3 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像划煮,于是被迫代替她去往敵國(guó)和親送丰。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,927評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容