版本记录
版本号 | 时间 |
---|---|
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
方法进行绘制。 -
fillMode
和removeOnCompletion
两个属性指定动画在绘制完成后,对应的动画对象不会从内存中移除掉。
后记
本篇已结束,下一篇更精彩~~~