mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-02 12:48:45 +00:00
Removed old rendered
This commit is contained in:
parent
502b18ff46
commit
e858c6afd1
2042
Example/Pods/Pods.xcodeproj/project.pbxproj
generated
2042
Example/Pods/Pods.xcodeproj/project.pbxproj
generated
File diff suppressed because it is too large
Load Diff
@ -5,6 +5,7 @@
|
||||
#import "LOTAnimationTransitionController.h"
|
||||
#import "LOTAnimationView.h"
|
||||
#import "LOTAnimationView_Compat.h"
|
||||
#import "LOTComposition.h"
|
||||
#import "Lottie.h"
|
||||
|
||||
FOUNDATION_EXPORT double LottieVersionNumber;
|
||||
|
||||
@ -5,6 +5,7 @@
|
||||
#import "LOTAnimationTransitionController.h"
|
||||
#import "LOTAnimationView.h"
|
||||
#import "LOTAnimationView_Compat.h"
|
||||
#import "LOTComposition.h"
|
||||
#import "Lottie.h"
|
||||
|
||||
FOUNDATION_EXPORT double LottieVersionNumber;
|
||||
|
||||
@ -1,17 +0,0 @@
|
||||
//
|
||||
// LOTAnimatableLayer.h
|
||||
// LottieAnimator
|
||||
//
|
||||
// Created by brandon_withrow on 7/21/16.
|
||||
// Copyright © 2016 Brandon Withrow. All rights reserved.
|
||||
//
|
||||
|
||||
#import <QuartzCore/QuartzCore.h>
|
||||
|
||||
@interface LOTAnimatableLayer : CALayer
|
||||
|
||||
- (instancetype)initWithLayerDuration:(NSTimeInterval)duration NS_DESIGNATED_INITIALIZER;
|
||||
|
||||
@property (nonatomic, readonly) NSTimeInterval layerDuration;
|
||||
|
||||
@end
|
||||
@ -1,21 +0,0 @@
|
||||
//
|
||||
// LOTAnimatableLayer.m
|
||||
// LottieAnimator
|
||||
//
|
||||
// Created by brandon_withrow on 7/21/16.
|
||||
// Copyright © 2016 Brandon Withrow. All rights reserved.
|
||||
//
|
||||
|
||||
#import "LOTAnimatableLayer.h"
|
||||
|
||||
@implementation LOTAnimatableLayer
|
||||
|
||||
- (instancetype)initWithLayerDuration:(NSTimeInterval)duration {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_layerDuration = duration;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
@end
|
||||
@ -1,30 +0,0 @@
|
||||
//
|
||||
// LOTCompositionLayer.h
|
||||
// Pods
|
||||
//
|
||||
// Created by Brandon Withrow on 2/17/17.
|
||||
//
|
||||
//
|
||||
|
||||
#import <QuartzCore/QuartzCore.h>
|
||||
#import "LOTAnimationView_Compat.h"
|
||||
|
||||
@class LOTLayerGroup;
|
||||
@class LOTAssetGroup;
|
||||
|
||||
@interface LOTCompositionLayer : CALayer
|
||||
|
||||
/// DEPRECATED
|
||||
- (instancetype)initWithLayerGroup:(LOTLayerGroup *)layerGroup
|
||||
withAssetGroup:(LOTAssetGroup *)assetGroup
|
||||
withBounds:(CGRect)bounds
|
||||
inBundle:(NSBundle *)bundle;
|
||||
|
||||
- (void)addSublayer:(LOTView *)view
|
||||
toLayerNamed:(NSString *)layer;
|
||||
|
||||
- (void)layoutCustomChildLayers;
|
||||
|
||||
@property (nonatomic) NSNumber *currentFrame;
|
||||
|
||||
@end
|
||||
@ -1,162 +0,0 @@
|
||||
//
|
||||
// LOTCompositionLayer.m
|
||||
// Pods
|
||||
//
|
||||
// Created by Brandon Withrow on 2/17/17.
|
||||
//
|
||||
//
|
||||
|
||||
#import "LOTCompositionLayer.h"
|
||||
#import "LOTPlatformCompat.h"
|
||||
#import "LOTLayerView.h"
|
||||
#import "LOTAnimationView_Internal.h"
|
||||
#import "LOTAsset.h"
|
||||
#import "LOTAssetGroup.h"
|
||||
#import "LOTHelpers.h"
|
||||
#import "LOTLayerContainer.h"
|
||||
|
||||
@interface LOTCustomChild : NSObject
|
||||
|
||||
@property (nonatomic, strong) LOTView *childView;
|
||||
@property (nonatomic, weak) LOTLayerView *layer;
|
||||
@property (nonatomic, assign) LOTConstraintType constraint;
|
||||
|
||||
@end
|
||||
|
||||
@implementation LOTCustomChild
|
||||
|
||||
@end
|
||||
|
||||
@implementation LOTCompositionLayer {
|
||||
NSDictionary *_layerMap;
|
||||
NSDictionary *_layerNameMap;
|
||||
NSMutableArray *_customLayers;
|
||||
}
|
||||
|
||||
- (instancetype)initWithLayerGroup:(LOTLayerGroup *)layerGroup
|
||||
withAssetGroup:(LOTAssetGroup *)assetGroup
|
||||
withBounds:(CGRect)bounds
|
||||
inBundle:(NSBundle *)bundle {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
self.masksToBounds = YES;
|
||||
[self _setupWithLayerGroup:layerGroup withAssetGroup:assetGroup withBounds:bounds inBundle:bundle];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)_setupWithLayerGroup:(LOTLayerGroup *)layerGroup
|
||||
withAssetGroup:(LOTAssetGroup *)assetGroup
|
||||
withBounds:(CGRect)bounds
|
||||
inBundle:(NSBundle *)bundle {
|
||||
if (_customLayers) {
|
||||
for (LOTCustomChild *child in _customLayers) {
|
||||
[child.childView.layer removeFromSuperlayer];
|
||||
}
|
||||
_customLayers = nil;
|
||||
}
|
||||
|
||||
if (_layerMap) {
|
||||
_layerMap = nil;
|
||||
[self removeAllAnimations];
|
||||
[self.sublayers makeObjectsPerformSelector:@selector(removeFromSuperlayer)];
|
||||
}
|
||||
|
||||
if (_layerNameMap) {
|
||||
_layerNameMap = nil;
|
||||
}
|
||||
|
||||
self.bounds = bounds;
|
||||
|
||||
NSMutableDictionary *layerMap = [NSMutableDictionary dictionary];
|
||||
NSMutableDictionary *layerNameMap = [NSMutableDictionary dictionary];
|
||||
|
||||
NSArray *reversedItems = [[layerGroup.layers reverseObjectEnumerator] allObjects];
|
||||
|
||||
CALayer *maskedLayer = nil;
|
||||
for (LOTLayer *layer in reversedItems) {
|
||||
LOTAsset *asset;
|
||||
|
||||
if (layer.referenceID) {
|
||||
asset = [assetGroup assetModelForID:layer.referenceID];
|
||||
}
|
||||
|
||||
LOTLayerView *layerView = [[LOTLayerView alloc] initWithModel:layer inLayerGroup:layerGroup inBundle:bundle];
|
||||
|
||||
if (asset.layerGroup) {
|
||||
LOTCompositionLayer *precompLayer = [[LOTCompositionLayer alloc] initWithLayerGroup:asset.layerGroup
|
||||
withAssetGroup:assetGroup
|
||||
withBounds:layer.layerBounds
|
||||
inBundle:bundle];
|
||||
precompLayer.frame = layer.layerBounds;
|
||||
[layerView LOT_addChildLayer:precompLayer];
|
||||
}
|
||||
|
||||
layerMap[layer.layerID] = layerView;
|
||||
layerNameMap[layer.layerName] = layerView;
|
||||
if (maskedLayer) {
|
||||
maskedLayer.mask = layerView;
|
||||
maskedLayer = nil;
|
||||
} else {
|
||||
if (layer.matteType == LOTMatteTypeAdd) {
|
||||
maskedLayer = layerView;
|
||||
}
|
||||
[self addSublayer:layerView];
|
||||
}
|
||||
}
|
||||
_layerMap = layerMap;
|
||||
_layerNameMap = layerNameMap;
|
||||
}
|
||||
|
||||
- (void)layoutCustomChildLayers {
|
||||
if (!_customLayers.count) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (LOTCustomChild *child in _customLayers) {
|
||||
switch (child.constraint) {
|
||||
case LOTConstraintTypeAlignToLayer:
|
||||
child.childView.frame = child.layer.bounds;
|
||||
break;
|
||||
case LOTConstraintTypeAlignToBounds: {
|
||||
CGRect selfBounds = self.bounds;
|
||||
CGRect convertedBounds = [child.childView.layer.superlayer convertRect:selfBounds fromLayer:self];
|
||||
child.childView.layer.frame = convertedBounds;
|
||||
} break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)addSublayer:(LOTView *)view
|
||||
toLayerNamed:(NSString *)layer {
|
||||
LOTConstraintType constraint = LOTConstraintTypeAlignToBounds;
|
||||
LOTLayerView *layerObject = _layerNameMap[layer];
|
||||
LOTCustomChild *newChild = [[LOTCustomChild alloc] init];
|
||||
newChild.constraint = constraint;
|
||||
newChild.childView = view;
|
||||
|
||||
if (!layer) {
|
||||
NSException* layerNotFoundExpection = [NSException exceptionWithName:@"LayerNotFoundException"
|
||||
reason:@"The required layer was not specified."
|
||||
userInfo:nil];
|
||||
@throw layerNotFoundExpection;
|
||||
} else {
|
||||
newChild.layer = layerObject;
|
||||
[layerObject.superlayer insertSublayer:view.layer above:layerObject];
|
||||
|
||||
[layerObject removeFromSuperlayer];
|
||||
view.layer.mask = layerObject;
|
||||
}
|
||||
|
||||
if (!_customLayers) {
|
||||
_customLayers = [NSMutableArray array];
|
||||
}
|
||||
[_customLayers addObject:newChild];
|
||||
[self layoutCustomChildLayers];
|
||||
}
|
||||
|
||||
|
||||
|
||||
@end
|
||||
@ -1,21 +0,0 @@
|
||||
//
|
||||
// LOTEllipseShapeLayer.h
|
||||
// LottieAnimator
|
||||
//
|
||||
// Created by brandon_withrow on 7/26/16.
|
||||
// Copyright © 2016 Brandon Withrow. All rights reserved.
|
||||
//
|
||||
|
||||
#import "LOTAnimatableLayer.h"
|
||||
#import "LOTModels.h"
|
||||
|
||||
@interface LOTEllipseShapeLayer : LOTAnimatableLayer
|
||||
|
||||
- (instancetype)initWithEllipseShape:(LOTShapeCircle *)circleShape
|
||||
fill:(LOTShapeFill *)fill
|
||||
stroke:(LOTShapeStroke *)stroke
|
||||
trim:(LOTShapeTrimPath *)trim
|
||||
transform:(LOTShapeTransform *)transform
|
||||
withLayerDuration:(NSTimeInterval)duration;
|
||||
|
||||
@end
|
||||
@ -1,227 +0,0 @@
|
||||
//
|
||||
// LOTEllipseShapeLayer.m
|
||||
// LottieAnimator
|
||||
//
|
||||
// Created by brandon_withrow on 7/26/16.
|
||||
// Copyright © 2016 Brandon Withrow. All rights reserved.
|
||||
//
|
||||
|
||||
#import "LOTEllipseShapeLayer.h"
|
||||
#import "CAAnimationGroup+LOTAnimatableGroup.h"
|
||||
#import "LOTStrokeShapeLayer.h"
|
||||
|
||||
const CGFloat kEllipseControlPointPercentage = 0.55228;
|
||||
|
||||
@interface LOTCircleShapeLayer : LOTStrokeShapeLayer
|
||||
|
||||
@property (nonatomic) CGPoint circlePosition;
|
||||
@property (nonatomic) CGPoint circleSize;
|
||||
|
||||
@end
|
||||
|
||||
@implementation LOTCircleShapeLayer
|
||||
|
||||
@dynamic circleSize;
|
||||
@dynamic circlePosition;
|
||||
|
||||
-(id)initWithLayer:(id)layer {
|
||||
if( ( self = [super initWithLayer:layer] ) ) {
|
||||
if ([layer isKindOfClass:[LOTCircleShapeLayer class]]) {
|
||||
self.circleSize = ((LOTCircleShapeLayer *)layer).circleSize;
|
||||
self.circlePosition = ((LOTCircleShapeLayer *)layer).circlePosition;
|
||||
}
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
+ (BOOL)needsDisplayForKey:(NSString *)key {
|
||||
BOOL needsDisplay = [super needsDisplayForKey:key];
|
||||
|
||||
if ([key isEqualToString:@"circlePosition"] || [key isEqualToString:@"circleSize"]) {
|
||||
needsDisplay = YES;
|
||||
}
|
||||
|
||||
return needsDisplay;
|
||||
}
|
||||
|
||||
-(id<CAAction>)actionForKey:(NSString *)event {
|
||||
if( [event isEqualToString:@"circlePosition"] || [event isEqualToString:@"circleSize"]) {
|
||||
CABasicAnimation *theAnimation = [CABasicAnimation
|
||||
animationWithKeyPath:event];
|
||||
theAnimation.fromValue = [[self presentationLayer] valueForKey:event];
|
||||
return theAnimation;
|
||||
}
|
||||
return [super actionForKey:event];
|
||||
}
|
||||
|
||||
- (void)_setPath {
|
||||
LOTCircleShapeLayer *presentationCircle = (LOTCircleShapeLayer *)self.presentationLayer;
|
||||
if (presentationCircle == nil) {
|
||||
presentationCircle = self;
|
||||
}
|
||||
CGFloat halfWidth = presentationCircle.circleSize.x / 2;
|
||||
CGFloat halfHeight = presentationCircle.circleSize.y / 2;
|
||||
|
||||
CGPoint circleQ1 = CGPointMake(0, -halfHeight);
|
||||
CGPoint circleQ2 = CGPointMake(halfWidth, 0);
|
||||
CGPoint circleQ3 = CGPointMake(0, halfHeight);
|
||||
CGPoint circleQ4 = CGPointMake(-halfWidth, 0);
|
||||
|
||||
CGFloat cpW = halfWidth * kEllipseControlPointPercentage;
|
||||
CGFloat cpH = halfHeight * kEllipseControlPointPercentage;
|
||||
|
||||
UIBezierPath *path = [UIBezierPath bezierPath];
|
||||
[path moveToPoint:circleQ1];
|
||||
[path addCurveToPoint:circleQ2 controlPoint1:CGPointMake(circleQ1.x + cpW, circleQ1.y) controlPoint2:CGPointMake(circleQ2.x, circleQ2.y - cpH)];
|
||||
|
||||
[path addCurveToPoint:circleQ3 controlPoint1:CGPointMake(circleQ2.x, circleQ2.y + cpH) controlPoint2:CGPointMake(circleQ3.x + cpW, circleQ3.y)];
|
||||
|
||||
[path addCurveToPoint:circleQ4 controlPoint1:CGPointMake(circleQ3.x - cpW, circleQ3.y) controlPoint2:CGPointMake(circleQ4.x, circleQ4.y + cpH)];
|
||||
|
||||
[path addCurveToPoint:circleQ1 controlPoint1:CGPointMake(circleQ4.x, circleQ4.y - cpH) controlPoint2:CGPointMake(circleQ1.x - cpW, circleQ1.y)];
|
||||
|
||||
// Double path for trim offset.
|
||||
[path moveToPoint:circleQ1];
|
||||
[path addCurveToPoint:circleQ2 controlPoint1:CGPointMake(circleQ1.x + cpW, circleQ1.y) controlPoint2:CGPointMake(circleQ2.x, circleQ2.y - cpH)];
|
||||
|
||||
[path addCurveToPoint:circleQ3 controlPoint1:CGPointMake(circleQ2.x, circleQ2.y + cpH) controlPoint2:CGPointMake(circleQ3.x + cpW, circleQ3.y)];
|
||||
|
||||
[path addCurveToPoint:circleQ4 controlPoint1:CGPointMake(circleQ3.x - cpW, circleQ3.y) controlPoint2:CGPointMake(circleQ4.x, circleQ4.y + cpH)];
|
||||
|
||||
[path addCurveToPoint:circleQ1 controlPoint1:CGPointMake(circleQ4.x, circleQ4.y - cpH) controlPoint2:CGPointMake(circleQ1.x - cpW, circleQ1.y)];
|
||||
|
||||
//Move path(s)
|
||||
[path applyTransform:CGAffineTransformMakeTranslation(presentationCircle.circlePosition.x, presentationCircle.circlePosition.y)];
|
||||
|
||||
self.path = path.CGPath;
|
||||
}
|
||||
|
||||
- (void)display {
|
||||
[self _setPath];
|
||||
[super display];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation LOTEllipseShapeLayer {
|
||||
LOTShapeTransform *_transform;
|
||||
LOTShapeStroke *_stroke;
|
||||
LOTShapeFill *_fill;
|
||||
LOTShapeCircle *_circle;
|
||||
LOTShapeTrimPath *_trim;
|
||||
|
||||
LOTCircleShapeLayer *_fillLayer;
|
||||
LOTCircleShapeLayer *_strokeLayer;
|
||||
|
||||
CAAnimationGroup *_animation;
|
||||
CAAnimationGroup *_strokeAnimation;
|
||||
CAAnimationGroup *_fillAnimation;
|
||||
}
|
||||
|
||||
- (instancetype)initWithEllipseShape:(LOTShapeCircle *)circleShape
|
||||
fill:(LOTShapeFill *)fill
|
||||
stroke:(LOTShapeStroke *)stroke
|
||||
trim:(LOTShapeTrimPath *)trim
|
||||
transform:(LOTShapeTransform *)transform
|
||||
withLayerDuration:(NSTimeInterval)duration {
|
||||
self = [super initWithLayerDuration:duration];
|
||||
if (self) {
|
||||
_circle = circleShape;
|
||||
_stroke = stroke;
|
||||
_fill = fill;
|
||||
_transform = transform;
|
||||
_trim = trim;
|
||||
|
||||
self.allowsEdgeAntialiasing = YES;
|
||||
self.frame = _transform.compBounds;
|
||||
self.anchorPoint = _transform.anchor.initialPoint;
|
||||
self.opacity = _transform.opacity.initialValue.floatValue;
|
||||
self.position = _transform.position.initialPoint;
|
||||
self.transform = _transform.scale.initialScale;
|
||||
self.sublayerTransform = CATransform3DMakeRotation(_transform.rotation.initialValue.floatValue, 0, 0, 1);
|
||||
|
||||
if (fill) {
|
||||
_fillLayer = [LOTCircleShapeLayer new];
|
||||
_fillLayer.allowsEdgeAntialiasing = YES;
|
||||
_fillLayer.fillColor = _fill.color.initialColor.CGColor;
|
||||
_fillLayer.opacity = _fill.opacity.initialValue.floatValue;
|
||||
_fillLayer.circlePosition = circleShape.position.initialPoint;
|
||||
_fillLayer.circleSize = circleShape.size.initialPoint;
|
||||
[self addSublayer:_fillLayer];
|
||||
}
|
||||
|
||||
if (stroke) {
|
||||
_strokeLayer = [LOTCircleShapeLayer new];
|
||||
_strokeLayer.allowsEdgeAntialiasing = YES;
|
||||
_strokeLayer.strokeColor = _stroke.color.initialColor.CGColor;
|
||||
_strokeLayer.opacity = _stroke.opacity.initialValue.floatValue;
|
||||
_strokeLayer.lineWidth = _stroke.width.initialValue.floatValue;
|
||||
_strokeLayer.fillColor = nil;
|
||||
_strokeLayer.backgroundColor = nil;
|
||||
_strokeLayer.lineDashPattern = _stroke.lineDashPattern;
|
||||
_strokeLayer.lineCap = _stroke.capType == LOTLineCapTypeRound ? kCALineCapRound : kCALineCapButt;
|
||||
_strokeLayer.circlePosition = circleShape.position.initialPoint;
|
||||
_strokeLayer.circleSize = circleShape.size.initialPoint;
|
||||
switch (_stroke.joinType) {
|
||||
case LOTLineJoinTypeBevel:
|
||||
_strokeLayer.lineJoin = kCALineJoinBevel;
|
||||
break;
|
||||
case LOTLineJoinTypeMiter:
|
||||
_strokeLayer.lineJoin = kCALineJoinMiter;
|
||||
break;
|
||||
case LOTLineJoinTypeRound:
|
||||
_strokeLayer.lineJoin = kCALineJoinRound;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (trim) {
|
||||
_strokeLayer.trimStart = _trim.start.initialValue.floatValue;
|
||||
_strokeLayer.trimEnd = _trim.end.initialValue.floatValue;
|
||||
_strokeLayer.trimOffset = _trim.offset.initialValue.floatValue;
|
||||
}
|
||||
[self addSublayer:_strokeLayer];
|
||||
}
|
||||
|
||||
[self _buildAnimation];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)_buildAnimation {
|
||||
if (_transform) {
|
||||
_animation = [CAAnimationGroup LOT_animationGroupForAnimatablePropertiesWithKeyPaths:@{@"opacity" : _transform.opacity,
|
||||
@"position" : _transform.position,
|
||||
@"anchorPoint" : _transform.anchor,
|
||||
@"transform" : _transform.scale,
|
||||
@"sublayerTransform.rotation" : _transform.rotation}];
|
||||
[self addAnimation:_animation forKey:@"LottieAnimation"];
|
||||
}
|
||||
|
||||
if (_stroke) {
|
||||
NSMutableDictionary *properties = [NSMutableDictionary dictionaryWithDictionary:@{@"strokeColor" : _stroke.color,
|
||||
@"opacity" : _stroke.opacity,
|
||||
@"lineWidth" : _stroke.width,
|
||||
@"circlePosition" : _circle.position,
|
||||
@"circleSize" : _circle.size}];
|
||||
if (_trim) {
|
||||
properties[@"trimStart"] = _trim.start;
|
||||
properties[@"trimEnd"] = _trim.end;
|
||||
properties[@"trimOffset"] = _trim.offset;
|
||||
}
|
||||
_strokeAnimation = [CAAnimationGroup LOT_animationGroupForAnimatablePropertiesWithKeyPaths:properties];
|
||||
[_strokeLayer addAnimation:_strokeAnimation forKey:@""];
|
||||
|
||||
}
|
||||
|
||||
if (_fill) {
|
||||
_fillAnimation = [CAAnimationGroup LOT_animationGroupForAnimatablePropertiesWithKeyPaths:@{@"fillColor" : _fill.color,
|
||||
@"opacity" : _fill.opacity,
|
||||
@"circlePosition" : _circle.position,
|
||||
@"circleSize" : _circle.size}];
|
||||
[_fillLayer addAnimation:_fillAnimation forKey:@""];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
@ -1,31 +0,0 @@
|
||||
//
|
||||
// LOTGroupLayerView.h
|
||||
// LottieAnimator
|
||||
//
|
||||
// Created by brandon_withrow on 7/14/16.
|
||||
// Copyright © 2016 Brandon Withrow. All rights reserved.
|
||||
//
|
||||
|
||||
#import <QuartzCore/QuartzCore.h>
|
||||
#import "LOTAnimatableLayer.h"
|
||||
|
||||
@class LOTShapeGroup;
|
||||
@class LOTShapeTransform;
|
||||
@class LOTShapeFill;
|
||||
@class LOTShapeStroke;
|
||||
@class LOTShapeTrimPath;
|
||||
|
||||
@interface LOTGroupLayerView : LOTAnimatableLayer
|
||||
|
||||
- (instancetype)initWithShapeGroup:(LOTShapeGroup *)shapeGroup
|
||||
transform:(LOTShapeTransform *)previousTransform
|
||||
fill:(LOTShapeFill *)previousFill
|
||||
stroke:(LOTShapeStroke *)previousStroke
|
||||
trimPath:(LOTShapeTrimPath *)previousTrimPath
|
||||
withLayerDuration:(NSTimeInterval)duration;
|
||||
|
||||
@property (nonatomic, readonly) LOTShapeGroup *shapeGroup;
|
||||
@property (nonatomic, readonly) LOTShapeTransform *shapeTransform;
|
||||
@property (nonatomic, assign) BOOL debugModeOn;
|
||||
|
||||
@end
|
||||
@ -1,135 +0,0 @@
|
||||
//
|
||||
// LOTGroupLayerView.m
|
||||
// LottieAnimator
|
||||
//
|
||||
// Created by brandon_withrow on 7/14/16.
|
||||
// Copyright © 2016 Brandon Withrow. All rights reserved.
|
||||
//
|
||||
|
||||
#import "LOTGroupLayerView.h"
|
||||
#import "LOTShapeLayerView.h"
|
||||
#import "LOTRectShapeLayer.h"
|
||||
#import "LOTEllipseShapeLayer.h"
|
||||
|
||||
#import "CAAnimationGroup+LOTAnimatableGroup.h"
|
||||
|
||||
@implementation LOTGroupLayerView {
|
||||
NSArray<LOTGroupLayerView *> *_groupLayers;
|
||||
NSArray<LOTShapeLayerView *> *_shapeLayers;
|
||||
CAAnimationGroup *_animation;
|
||||
}
|
||||
|
||||
- (instancetype)initWithShapeGroup:(LOTShapeGroup *)shapeGroup
|
||||
transform:(LOTShapeTransform *)previousTransform
|
||||
fill:(LOTShapeFill *)previousFill
|
||||
stroke:(LOTShapeStroke *)previousStroke
|
||||
trimPath:(LOTShapeTrimPath *)previousTrimPath
|
||||
withLayerDuration:(NSTimeInterval)duration {
|
||||
self = [super initWithLayerDuration:duration];
|
||||
if (self) {
|
||||
_shapeGroup = shapeGroup;
|
||||
_shapeTransform = previousTransform;
|
||||
[self _setupShapeGroupWithFill:previousFill stroke:previousStroke trimPath:previousTrimPath];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)_setupShapeGroupWithFill:(LOTShapeFill *)previousFill
|
||||
stroke:(LOTShapeStroke *)previousStroke
|
||||
trimPath:(LOTShapeTrimPath *)previousTrimPath {
|
||||
if (_shapeTransform) {
|
||||
self.frame = _shapeTransform.compBounds;
|
||||
self.anchorPoint = _shapeTransform.anchor.initialPoint;
|
||||
self.position = _shapeTransform.position.initialPoint;
|
||||
self.opacity = _shapeTransform.opacity.initialValue.floatValue;
|
||||
self.transform = _shapeTransform.scale.initialScale;
|
||||
self.sublayerTransform = CATransform3DMakeRotation(_shapeTransform.rotation.initialValue.floatValue, 0, 0, 1);
|
||||
}
|
||||
|
||||
NSArray *groupItems = _shapeGroup.items;
|
||||
NSArray *reversedItems = [[groupItems reverseObjectEnumerator] allObjects];
|
||||
|
||||
LOTShapeFill *currentFill = previousFill;
|
||||
LOTShapeStroke *currentStroke = previousStroke;
|
||||
LOTShapeTransform *currentTransform;
|
||||
LOTShapeTrimPath *currentTrim = previousTrimPath;
|
||||
|
||||
NSMutableArray *shapeLayers = [NSMutableArray array];
|
||||
NSMutableArray *groupLayers = [NSMutableArray array];
|
||||
|
||||
for (id item in reversedItems) {
|
||||
if ([item isKindOfClass:[LOTShapeTransform class]]) {
|
||||
currentTransform = item;
|
||||
} else if ([item isKindOfClass:[LOTShapeStroke class]]) {
|
||||
currentStroke = item;
|
||||
} else if ([item isKindOfClass:[LOTShapeFill class]]) {
|
||||
currentFill = item;
|
||||
} else if ([item isKindOfClass:[LOTShapeTrimPath class]]) {
|
||||
currentTrim = item;
|
||||
} else if ([item isKindOfClass:[LOTShapePath class]]) {
|
||||
LOTShapePath *shapePath = (LOTShapePath *)item;
|
||||
LOTShapeLayerView *shapeLayer = [[LOTShapeLayerView alloc] initWithShape:shapePath
|
||||
fill:currentFill
|
||||
stroke:currentStroke
|
||||
trim:currentTrim
|
||||
transform:currentTransform
|
||||
withLayerDuration:self.layerDuration];
|
||||
[shapeLayers addObject:shapeLayer];
|
||||
[self addSublayer:shapeLayer];
|
||||
} else if ([item isKindOfClass:[LOTShapeRectangle class]]) {
|
||||
LOTShapeRectangle *shapeRect = (LOTShapeRectangle *)item;
|
||||
LOTRectShapeLayer *shapeLayer = [[LOTRectShapeLayer alloc] initWithRectShape:shapeRect
|
||||
fill:currentFill
|
||||
stroke:currentStroke
|
||||
trim:currentTrim
|
||||
transform:currentTransform
|
||||
withLayerDuration:self.layerDuration];
|
||||
[shapeLayers addObject:shapeLayer];
|
||||
[self addSublayer:shapeLayer];
|
||||
} else if ([item isKindOfClass:[LOTShapeCircle class]]) {
|
||||
LOTShapeCircle *shapeCircle = (LOTShapeCircle *)item;
|
||||
LOTEllipseShapeLayer *shapeLayer = [[LOTEllipseShapeLayer alloc] initWithEllipseShape:shapeCircle
|
||||
fill:currentFill
|
||||
stroke:currentStroke
|
||||
trim:currentTrim
|
||||
transform:currentTransform
|
||||
withLayerDuration:self.layerDuration];
|
||||
[shapeLayers addObject:shapeLayer];
|
||||
[self addSublayer:shapeLayer];
|
||||
} else if ([item isKindOfClass:[LOTShapeGroup class]]) {
|
||||
LOTShapeGroup *shapeGroup = (LOTShapeGroup *)item;
|
||||
LOTGroupLayerView *groupLayer = [[LOTGroupLayerView alloc] initWithShapeGroup:shapeGroup
|
||||
transform:currentTransform
|
||||
fill:currentFill
|
||||
stroke:currentStroke
|
||||
trimPath:currentTrim
|
||||
withLayerDuration:self.layerDuration];
|
||||
[groupLayers addObject:groupLayer];
|
||||
[self addSublayer:groupLayer];
|
||||
}
|
||||
}
|
||||
_groupLayers = groupLayers;
|
||||
_shapeLayers = shapeLayers;
|
||||
|
||||
[self _buildAnimation];
|
||||
}
|
||||
|
||||
- (void)_buildAnimation {
|
||||
if (_shapeTransform) {
|
||||
_animation = [CAAnimationGroup LOT_animationGroupForAnimatablePropertiesWithKeyPaths:@{@"opacity" : _shapeTransform.opacity,
|
||||
@"position" : _shapeTransform.position,
|
||||
@"anchorPoint" : _shapeTransform.anchor,
|
||||
@"transform" : _shapeTransform.scale,
|
||||
@"sublayerTransform.rotation" : _shapeTransform.rotation}];
|
||||
[self addAnimation:_animation forKey:@"LottieAnimation"];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setDebugModeOn:(BOOL)debugModeOn {
|
||||
_debugModeOn = debugModeOn;
|
||||
self.borderColor = debugModeOn ? [UIColor blueColor].CGColor : nil;
|
||||
self.borderWidth = debugModeOn ? 2 : 0;
|
||||
self.backgroundColor = debugModeOn ? [[UIColor greenColor] colorWithAlphaComponent:0.2].CGColor : nil;
|
||||
}
|
||||
|
||||
@end
|
||||
@ -1,25 +0,0 @@
|
||||
//
|
||||
// LOTLayerView.h
|
||||
// LottieAnimator
|
||||
//
|
||||
// Created by Brandon Withrow on 12/14/15.
|
||||
// Copyright © 2015 Brandon Withrow. All rights reserved.
|
||||
//
|
||||
|
||||
#import "LOTPlatformCompat.h"
|
||||
#import "LOTAnimatableLayer.h"
|
||||
|
||||
#import "LOTModels.h"
|
||||
|
||||
@interface LOTLayerView : LOTAnimatableLayer
|
||||
|
||||
- (instancetype)initWithModel:(LOTLayer *)model inLayerGroup:(LOTLayerGroup *)layerGroup;
|
||||
- (instancetype)initWithModel:(LOTLayer *)model inLayerGroup:(LOTLayerGroup *)layerGroup inBundle:(NSBundle *)bundle;
|
||||
|
||||
- (void)LOT_addChildLayer:(CALayer *)childLayer;
|
||||
|
||||
@property (nonatomic, strong) NSNumber *currentFrame;
|
||||
@property (nonatomic, readonly) LOTLayer *layerModel;
|
||||
@property (nonatomic, assign) BOOL debugModeOn;
|
||||
|
||||
@end
|
||||
@ -1,398 +0,0 @@
|
||||
//
|
||||
// LOTLayerView.m
|
||||
// LottieAnimator
|
||||
//
|
||||
// Created by Brandon Withrow on 12/14/15.
|
||||
// Copyright © 2015 Brandon Withrow. All rights reserved.
|
||||
//
|
||||
|
||||
#import "LOTLayerView.h"
|
||||
#import "LOTShapeLayerView.h"
|
||||
#import "LOTRectShapeLayer.h"
|
||||
#import "LOTEllipseShapeLayer.h"
|
||||
#import "LOTGroupLayerView.h"
|
||||
#import "CAAnimationGroup+LOTAnimatableGroup.h"
|
||||
#import "LOTMaskLayer.h"
|
||||
#import "CGGeometry+LOTAdditions.h"
|
||||
|
||||
#import "LOTHelpers.h"
|
||||
#import "LOTRenderGroup.h"
|
||||
|
||||
@interface LOTParentLayer : LOTAnimatableLayer
|
||||
|
||||
- (instancetype)initWithParentModel:(LOTLayer *)parent;
|
||||
|
||||
@end
|
||||
|
||||
@implementation LOTParentLayer {
|
||||
LOTLayer *_parentModel;
|
||||
CAAnimationGroup *_animation;
|
||||
}
|
||||
|
||||
- (instancetype)initWithParentModel:(LOTLayer *)parent {
|
||||
self = [super initWithLayerDuration:parent.layerDuration];
|
||||
if (self) {
|
||||
self.bounds = parent.layerBounds;
|
||||
_parentModel = parent;
|
||||
[self _setupLayerFromModel];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)_setupLayerFromModel {
|
||||
if (_parentModel.position) {
|
||||
self.position = _parentModel.position.initialPoint;
|
||||
} else {
|
||||
CGPoint initial = CGPointZero;
|
||||
if (_parentModel.positionX) {
|
||||
initial.x = _parentModel.positionX.initialValue.floatValue;
|
||||
}
|
||||
if (_parentModel.positionY) {
|
||||
initial.y = _parentModel.positionY.initialValue.floatValue;
|
||||
}
|
||||
self.position = initial;
|
||||
}
|
||||
|
||||
self.anchorPoint = _parentModel.anchor.initialPoint;
|
||||
self.transform = _parentModel.scale.initialScale;
|
||||
self.sublayerTransform = CATransform3DMakeRotation(_parentModel.rotation.initialValue.floatValue, 0, 0, 1);
|
||||
[self _buildAnimations];
|
||||
}
|
||||
|
||||
- (void)_buildAnimations {
|
||||
NSMutableDictionary *keypaths = [NSMutableDictionary dictionary];
|
||||
if (_parentModel.position) {
|
||||
[keypaths setValue:_parentModel.position forKey:@"position"];
|
||||
}
|
||||
if (_parentModel.anchor) {
|
||||
[keypaths setValue:_parentModel.anchor forKey:@"anchorPoint"];
|
||||
}
|
||||
if (_parentModel.scale) {
|
||||
[keypaths setValue:_parentModel.scale forKey:@"transform"];
|
||||
}
|
||||
if (_parentModel.rotation) {
|
||||
[keypaths setValue:_parentModel.rotation forKey:@"sublayerTransform.rotation"];
|
||||
}
|
||||
if (_parentModel.positionX) {
|
||||
[keypaths setValue:_parentModel.positionX forKey:@"position.x"];
|
||||
}
|
||||
if (_parentModel.positionY) {
|
||||
[keypaths setValue:_parentModel.positionY forKey:@"position.y"];
|
||||
}
|
||||
|
||||
_animation = [CAAnimationGroup LOT_animationGroupForAnimatablePropertiesWithKeyPaths:keypaths];
|
||||
[self addAnimation:_animation forKey:@"LottieAnimation"];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation LOTLayerView {
|
||||
NSArray<LOTGroupLayerView *> *_shapeLayers;
|
||||
CALayer *_childContainerLayer;
|
||||
CALayer *_rotationLayer;
|
||||
CAAnimationGroup *_animation;
|
||||
CAKeyframeAnimation *_inOutAnimation;
|
||||
NSArray<LOTParentLayer *> *_parentLayers;
|
||||
LOTMaskLayer *_maskLayer;
|
||||
CALayer *_childSolid;
|
||||
NSBundle *_bundle;
|
||||
|
||||
LOTRenderGroup *_childGroup;
|
||||
}
|
||||
|
||||
- (instancetype)initWithModel:(LOTLayer *)model inLayerGroup:(LOTLayerGroup *)layerGroup{
|
||||
return [self initWithModel:model inLayerGroup:layerGroup inBundle:[NSBundle mainBundle]];
|
||||
}
|
||||
|
||||
- (instancetype)initWithModel:(LOTLayer *)model inLayerGroup:(LOTLayerGroup *)layerGroup inBundle:(NSBundle *)bundle{
|
||||
self = [super initWithLayerDuration:model.layerDuration];
|
||||
if (self) {
|
||||
_bundle = bundle;
|
||||
_layerModel = model;
|
||||
[self _setupViewFromModelWithLayerGroup:layerGroup];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)_setupViewFromModelWithLayerGroup:(LOTLayerGroup *)layersGroup {
|
||||
self.backgroundColor = nil;
|
||||
self.bounds = _layerModel.layerBounds;
|
||||
self.anchorPoint = CGPointZero;
|
||||
|
||||
_childContainerLayer = [CALayer new];
|
||||
_childContainerLayer.bounds = _layerModel.layerBounds;
|
||||
_childContainerLayer.backgroundColor = _layerModel.solidColor.CGColor;
|
||||
|
||||
if (_layerModel.layerType <= LOTLayerTypeSolid) {
|
||||
self.bounds = _layerModel.parentCompBounds;
|
||||
_childContainerLayer.backgroundColor = nil;
|
||||
_childContainerLayer.masksToBounds = NO;
|
||||
}
|
||||
|
||||
if (_layerModel.layerType == LOTLayerTypeSolid) {
|
||||
[self _createChildSolid];
|
||||
[self _setSolidLayerBackground];
|
||||
}
|
||||
|
||||
if (_layerModel.layerType == LOTLayerTypeImage) {
|
||||
[self _createChildSolid];
|
||||
[self _setImageForAsset];
|
||||
}
|
||||
|
||||
NSNumber *parentID = _layerModel.parentID;
|
||||
CALayer *currentChild = _childContainerLayer;
|
||||
NSMutableArray *parentLayers = [NSMutableArray array];
|
||||
if (parentID) {
|
||||
while (parentID != nil) {
|
||||
LOTLayer *parentModel = [layersGroup layerModelForID:parentID];
|
||||
LOTParentLayer *parentLayer = [[LOTParentLayer alloc] initWithParentModel:parentModel];
|
||||
[parentLayer addSublayer:currentChild];
|
||||
[parentLayers addObject:parentLayer];
|
||||
currentChild = parentLayer;
|
||||
parentID = parentModel.parentID;
|
||||
}
|
||||
}
|
||||
if (parentLayers.count) {
|
||||
_parentLayers = parentLayers;
|
||||
}
|
||||
[self addSublayer:currentChild];
|
||||
|
||||
_childContainerLayer.opacity = _layerModel.opacity.initialValue.floatValue;
|
||||
|
||||
if (_layerModel.position) {
|
||||
_childContainerLayer.position = _layerModel.position.initialPoint;
|
||||
} else {
|
||||
CGPoint initial = CGPointZero;
|
||||
if (_layerModel.positionX) {
|
||||
initial.x = _layerModel.positionX.initialValue.floatValue;
|
||||
}
|
||||
if (_layerModel.positionY) {
|
||||
initial.y = _layerModel.positionY.initialValue.floatValue;
|
||||
}
|
||||
_childContainerLayer.position = initial;
|
||||
}
|
||||
_childContainerLayer.anchorPoint = _layerModel.anchor.initialPoint;
|
||||
_childContainerLayer.transform = _layerModel.scale.initialScale;
|
||||
_childContainerLayer.sublayerTransform = CATransform3DMakeRotation(_layerModel.rotation.initialValue.floatValue, 0, 0, 1);
|
||||
|
||||
if (DEBUG_USE_NEW_RENDERER) {
|
||||
_childGroup = [[LOTRenderGroup alloc] initWithInputNode:nil contents:_layerModel.shapes];
|
||||
[_childContainerLayer addSublayer:_childGroup.containerLayer];
|
||||
[_childGroup updateWithFrame:@0];
|
||||
return;
|
||||
}
|
||||
|
||||
self.hidden = _layerModel.hasInAnimation;
|
||||
|
||||
NSArray *groupItems = _layerModel.shapes;
|
||||
NSArray *reversedItems = [[groupItems reverseObjectEnumerator] allObjects];
|
||||
LOTShapeTransform *currentTransform = [LOTShapeTransform transformIdentityWithCompBounds:_layerModel.layerBounds];
|
||||
LOTShapeTrimPath *currentTrimPath = nil;
|
||||
LOTShapeFill *currentFill = nil;
|
||||
LOTShapeStroke *currentStroke = nil;
|
||||
|
||||
NSMutableArray *shapeLayers = [NSMutableArray array];
|
||||
|
||||
for (id item in reversedItems) {
|
||||
if ([item isKindOfClass:[LOTShapeGroup class]]) {
|
||||
LOTGroupLayerView *groupLayer = [[LOTGroupLayerView alloc] initWithShapeGroup:(LOTShapeGroup *)item
|
||||
transform:currentTransform
|
||||
fill:currentFill
|
||||
stroke:currentStroke
|
||||
trimPath:currentTrimPath
|
||||
withLayerDuration:self.layerDuration];
|
||||
[_childContainerLayer addSublayer:groupLayer];
|
||||
[shapeLayers addObject:groupLayer];
|
||||
} else if ([item isKindOfClass:[LOTShapePath class]]) {
|
||||
LOTShapePath *shapePath = (LOTShapePath *)item;
|
||||
LOTShapeLayerView *shapeLayer = [[LOTShapeLayerView alloc] initWithShape:shapePath
|
||||
fill:currentFill
|
||||
stroke:currentStroke
|
||||
trim:currentTrimPath
|
||||
transform:currentTransform
|
||||
withLayerDuration:self.layerDuration];
|
||||
[shapeLayers addObject:shapeLayer];
|
||||
[_childContainerLayer addSublayer:shapeLayer];
|
||||
} else if ([item isKindOfClass:[LOTShapeRectangle class]]) {
|
||||
LOTShapeRectangle *shapeRect = (LOTShapeRectangle *)item;
|
||||
LOTRectShapeLayer *shapeLayer = [[LOTRectShapeLayer alloc] initWithRectShape:shapeRect
|
||||
fill:currentFill
|
||||
stroke:currentStroke
|
||||
trim:currentTrimPath
|
||||
transform:currentTransform
|
||||
withLayerDuration:self.layerDuration];
|
||||
[shapeLayers addObject:shapeLayer];
|
||||
[_childContainerLayer addSublayer:shapeLayer];
|
||||
} else if ([item isKindOfClass:[LOTShapeCircle class]]) {
|
||||
LOTShapeCircle *shapeCircle = (LOTShapeCircle *)item;
|
||||
LOTEllipseShapeLayer *shapeLayer = [[LOTEllipseShapeLayer alloc] initWithEllipseShape:shapeCircle
|
||||
fill:currentFill
|
||||
stroke:currentStroke
|
||||
trim:currentTrimPath
|
||||
transform:currentTransform
|
||||
withLayerDuration:self.layerDuration];
|
||||
[shapeLayers addObject:shapeLayer];
|
||||
[_childContainerLayer addSublayer:shapeLayer];
|
||||
} else if ([item isKindOfClass:[LOTShapeTransform class]]) {
|
||||
currentTransform = (LOTShapeTransform *)item;
|
||||
} else if ([item isKindOfClass:[LOTShapeFill class]]) {
|
||||
currentFill = (LOTShapeFill *)item;
|
||||
} else if ([item isKindOfClass:[LOTShapeTrimPath class]]) {
|
||||
currentTrimPath = (LOTShapeTrimPath *)item;
|
||||
} else if ([item isKindOfClass:[LOTShapeStroke class]]) {
|
||||
currentStroke = (LOTShapeStroke *)item;
|
||||
}
|
||||
}
|
||||
|
||||
_shapeLayers = shapeLayers;
|
||||
|
||||
if (_layerModel.masks) {
|
||||
_maskLayer = [[LOTMaskLayer alloc] initWithMasks:_layerModel.masks inLayer:_layerModel];
|
||||
_childContainerLayer.mask = _maskLayer;
|
||||
}
|
||||
|
||||
NSMutableArray *childLayers = [NSMutableArray array];
|
||||
[childLayers addObjectsFromArray:_parentLayers];
|
||||
[childLayers addObjectsFromArray:_shapeLayers];
|
||||
if (_maskLayer) {
|
||||
[childLayers addObject:_maskLayer];
|
||||
}
|
||||
|
||||
[self _buildAnimations];
|
||||
}
|
||||
|
||||
- (void)setCurrentFrame:(NSNumber *)currentFrame {
|
||||
_currentFrame = currentFrame;
|
||||
NSLog(@"--------------------------------- Layer Update %@ ---------------------------------", currentFrame);
|
||||
[_childGroup updateWithFrame:currentFrame];
|
||||
}
|
||||
|
||||
- (void)_buildAnimations {
|
||||
NSMutableDictionary *keypaths = [NSMutableDictionary dictionary];
|
||||
if (_layerModel.opacity) {
|
||||
[keypaths setValue:_layerModel.opacity forKey:@"opacity"];
|
||||
}
|
||||
if (_layerModel.position) {
|
||||
[keypaths setValue:_layerModel.position forKey:@"position"];
|
||||
}
|
||||
if (_layerModel.anchor) {
|
||||
[keypaths setValue:_layerModel.anchor forKey:@"anchorPoint"];
|
||||
}
|
||||
if (_layerModel.scale) {
|
||||
[keypaths setValue:_layerModel.scale forKey:@"transform"];
|
||||
}
|
||||
if (_layerModel.rotation) {
|
||||
[keypaths setValue:_layerModel.rotation forKey:@"sublayerTransform.rotation"];
|
||||
}
|
||||
if (_layerModel.positionX) {
|
||||
[keypaths setValue:_layerModel.positionX forKey:@"position.x"];
|
||||
}
|
||||
if (_layerModel.positionY) {
|
||||
[keypaths setValue:_layerModel.positionY forKey:@"position.y"];
|
||||
}
|
||||
|
||||
|
||||
_animation = [CAAnimationGroup LOT_animationGroupForAnimatablePropertiesWithKeyPaths:keypaths];
|
||||
|
||||
if (_animation) {
|
||||
[_childContainerLayer addAnimation:_animation forKey:@"LottieAnimation"];
|
||||
}
|
||||
|
||||
|
||||
CAKeyframeAnimation *inOutAnimation = [CAKeyframeAnimation animationWithKeyPath:@"hidden"];
|
||||
inOutAnimation.keyTimes = _layerModel.inOutKeyTimes;
|
||||
inOutAnimation.values = _layerModel.inOutKeyframes;
|
||||
inOutAnimation.duration = _layerModel.layerDuration;
|
||||
inOutAnimation.calculationMode = kCAAnimationDiscrete;
|
||||
inOutAnimation.fillMode = kCAFillModeBoth;
|
||||
inOutAnimation.removedOnCompletion = NO;
|
||||
|
||||
_inOutAnimation = inOutAnimation;
|
||||
_inOutAnimation.duration = self.layerDuration;
|
||||
[self addAnimation:_inOutAnimation forKey:@"inout"];
|
||||
self.duration = self.layerDuration;
|
||||
|
||||
}
|
||||
|
||||
- (void)LOT_addChildLayer:(CALayer *)childLayer {
|
||||
[_childContainerLayer addSublayer:childLayer];
|
||||
}
|
||||
|
||||
- (void)setDebugModeOn:(BOOL)debugModeOn {
|
||||
_debugModeOn = debugModeOn;
|
||||
self.borderColor = debugModeOn ? [UIColor redColor].CGColor : nil;
|
||||
self.borderWidth = debugModeOn ? 2 : 0;
|
||||
self.backgroundColor = debugModeOn ? [[UIColor blueColor] colorWithAlphaComponent:0.2].CGColor : [UIColor clearColor].CGColor;
|
||||
|
||||
_childContainerLayer.borderColor = debugModeOn ? [UIColor yellowColor].CGColor : nil;
|
||||
_childContainerLayer.borderWidth = debugModeOn ? 2 : 0;
|
||||
_childContainerLayer.backgroundColor = debugModeOn ? [[UIColor orangeColor] colorWithAlphaComponent:0.2].CGColor : [UIColor clearColor].CGColor;
|
||||
|
||||
for (LOTGroupLayerView *group in _shapeLayers) {
|
||||
group.debugModeOn = debugModeOn;
|
||||
}
|
||||
}
|
||||
|
||||
- (NSString*)description {
|
||||
NSMutableString *text = [[super description] mutableCopy];
|
||||
[text appendFormat:@" model: %@", _layerModel];
|
||||
return text;
|
||||
}
|
||||
|
||||
#if TARGET_OS_IPHONE || TARGET_IPHONE_SIMULATOR
|
||||
|
||||
- (void)_setImageForAsset {
|
||||
if (_layerModel.imageAsset.imageName) {
|
||||
UIImage *image;
|
||||
if (_layerModel.imageAsset.rootDirectory.length > 0) {
|
||||
NSString *rootDirectory = _layerModel.imageAsset.rootDirectory;
|
||||
if (_layerModel.imageAsset.imageDirectory.length > 0) {
|
||||
rootDirectory = [rootDirectory stringByAppendingPathComponent:_layerModel.imageAsset.imageDirectory];
|
||||
}
|
||||
NSString *imagePath = [rootDirectory stringByAppendingPathComponent:_layerModel.imageAsset.imageName];
|
||||
image = [UIImage imageWithContentsOfFile:imagePath];
|
||||
}else{
|
||||
NSArray *components = [_layerModel.imageAsset.imageName componentsSeparatedByString:@"."];
|
||||
image = [UIImage imageNamed:components.firstObject inBundle:_bundle compatibleWithTraitCollection:nil];
|
||||
}
|
||||
|
||||
if (image) {
|
||||
_childSolid.contents = (__bridge id _Nullable)(image.CGImage);
|
||||
} else {
|
||||
NSLog(@"%s: Warn: image not found: %@", __PRETTY_FUNCTION__, _layerModel.imageAsset.imageName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
- (void)_setImageForAsset {
|
||||
if (_layerModel.imageAsset.imageName) {
|
||||
NSArray *components = [_layerModel.imageAsset.imageName componentsSeparatedByString:@"."];
|
||||
NSImage *image = [NSImage imageNamed:components.firstObject];
|
||||
if (image) {
|
||||
NSWindow *window = [NSApp mainWindow];
|
||||
CGFloat desiredScaleFactor = [window backingScaleFactor];
|
||||
CGFloat actualScaleFactor = [image recommendedLayerContentsScale:desiredScaleFactor];
|
||||
id layerContents = [image layerContentsForContentsScale:actualScaleFactor];
|
||||
_childSolid.contents = layerContents;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
- (void)_createChildSolid {
|
||||
_childSolid = [CALayer new];
|
||||
_childSolid.frame = _childContainerLayer.bounds;
|
||||
_childSolid.masksToBounds = YES;
|
||||
[_childContainerLayer addSublayer:_childSolid];
|
||||
}
|
||||
|
||||
- (void)_setSolidLayerBackground {
|
||||
_childSolid.backgroundColor = _layerModel.solidColor.CGColor;
|
||||
}
|
||||
|
||||
@end
|
||||
@ -1,19 +0,0 @@
|
||||
//
|
||||
// LOTMaskLayer.h
|
||||
// LottieAnimator
|
||||
//
|
||||
// Created by brandon_withrow on 7/22/16.
|
||||
// Copyright © 2016 Brandon Withrow. All rights reserved.
|
||||
//
|
||||
|
||||
#import "LOTAnimatableLayer.h"
|
||||
#import "LOTModels.h"
|
||||
|
||||
@interface LOTMaskLayer : LOTAnimatableLayer
|
||||
|
||||
- (instancetype)initWithMasks:(NSArray<LOTMask *> *)masks inLayer:(LOTLayer *)layer;
|
||||
|
||||
@property (nonatomic, readonly) NSArray<LOTMask *> *masks;
|
||||
|
||||
|
||||
@end
|
||||
@ -1,46 +0,0 @@
|
||||
//
|
||||
// LOTMaskLayer.m
|
||||
// LottieAnimator
|
||||
//
|
||||
// Created by brandon_withrow on 7/22/16.
|
||||
// Copyright © 2016 Brandon Withrow. All rights reserved.
|
||||
//
|
||||
|
||||
#import "LOTMaskLayer.h"
|
||||
#import "CAAnimationGroup+LOTAnimatableGroup.h"
|
||||
|
||||
@implementation LOTMaskLayer {
|
||||
LOTLayer *_layer;
|
||||
NSArray *_maskLayers;
|
||||
}
|
||||
|
||||
- (instancetype)initWithMasks:(NSArray<LOTMask *> *)masks inLayer:(LOTLayer *)layer {
|
||||
self = [super initWithLayerDuration:layer.layerDuration];
|
||||
if (self) {
|
||||
_masks = masks;
|
||||
_layer = layer;
|
||||
[self _setupViewFromModel];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)_setupViewFromModel {
|
||||
NSMutableArray *maskLayers = [NSMutableArray array];
|
||||
|
||||
for (LOTMask *mask in _masks) {
|
||||
CAShapeLayer *maskLayer = [CAShapeLayer new];
|
||||
maskLayer.path = mask.maskPath.initialShape.CGPath;
|
||||
maskLayer.fillColor = [UIColor whiteColor].CGColor;
|
||||
maskLayer.opacity = mask.opacity.initialValue.floatValue;
|
||||
[self addSublayer:maskLayer];
|
||||
CAAnimationGroup *animGroup = [CAAnimationGroup LOT_animationGroupForAnimatablePropertiesWithKeyPaths:@{@"opacity" : mask.opacity,
|
||||
@"path" : mask.maskPath}];
|
||||
if (animGroup) {
|
||||
[maskLayer addAnimation:animGroup forKey:@""];
|
||||
}
|
||||
[maskLayers addObject:maskLayer];
|
||||
}
|
||||
_maskLayers = maskLayers;
|
||||
}
|
||||
|
||||
@end
|
||||
@ -1,22 +0,0 @@
|
||||
//
|
||||
// LOTRectShapeLayer.h
|
||||
// LottieAnimator
|
||||
//
|
||||
// Created by brandon_withrow on 7/20/16.
|
||||
// Copyright © 2016 Brandon Withrow. All rights reserved.
|
||||
//
|
||||
|
||||
#import <QuartzCore/QuartzCore.h>
|
||||
#import "LOTModels.h"
|
||||
#import "LOTAnimatableLayer.h"
|
||||
|
||||
@interface LOTRectShapeLayer : LOTAnimatableLayer
|
||||
|
||||
- (instancetype)initWithRectShape:(LOTShapeRectangle *)rectShape
|
||||
fill:(LOTShapeFill *)fill
|
||||
stroke:(LOTShapeStroke *)stroke
|
||||
trim:(LOTShapeTrimPath *)trim
|
||||
transform:(LOTShapeTransform *)transform
|
||||
withLayerDuration:(NSTimeInterval)duration;
|
||||
|
||||
@end
|
||||
@ -1,262 +0,0 @@
|
||||
//
|
||||
// LOTRectShapeLayer.m
|
||||
// LottieAnimator
|
||||
//
|
||||
// Created by brandon_withrow on 7/20/16.
|
||||
// Copyright © 2016 Brandon Withrow. All rights reserved.
|
||||
//
|
||||
|
||||
#import "LOTRectShapeLayer.h"
|
||||
#import "LOTPlatformCompat.h"
|
||||
#import "CAAnimationGroup+LOTAnimatableGroup.h"
|
||||
#import "LOTStrokeShapeLayer.h"
|
||||
#import "LOTHelpers.h"
|
||||
|
||||
@interface LOTRoundRectLayer : LOTStrokeShapeLayer
|
||||
|
||||
@property (nonatomic) CGPoint rectPosition;
|
||||
@property (nonatomic) CGPoint rectSize;
|
||||
@property (nonatomic) CGFloat rectCornerRadius;
|
||||
|
||||
@end
|
||||
|
||||
@implementation LOTRoundRectLayer
|
||||
|
||||
@dynamic rectPosition;
|
||||
@dynamic rectSize;
|
||||
@dynamic rectCornerRadius;
|
||||
|
||||
-(id)initWithLayer:(id)layer {
|
||||
if( ( self = [super initWithLayer:layer] ) ) {
|
||||
if ([layer isKindOfClass:[LOTRoundRectLayer class]]) {
|
||||
self.rectSize = ((LOTRoundRectLayer *)layer).rectSize;
|
||||
self.rectPosition = ((LOTRoundRectLayer *)layer).rectPosition;
|
||||
self.rectCornerRadius = ((LOTRoundRectLayer *)layer).rectCornerRadius;
|
||||
}
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
+ (BOOL)needsDisplayForKey:(NSString *)key {
|
||||
BOOL needsDisplay = [super needsDisplayForKey:key];
|
||||
|
||||
if ([key isEqualToString:@"rectSize"] || [key isEqualToString:@"rectPosition"] || [key isEqualToString:@"rectCornerRadius"]) {
|
||||
needsDisplay = YES;
|
||||
}
|
||||
|
||||
return needsDisplay;
|
||||
}
|
||||
|
||||
-(id<CAAction>)actionForKey:(NSString *)event {
|
||||
if([event isEqualToString:@"rectSize"] || [event isEqualToString:@"rectPosition"] || [event isEqualToString:@"rectCornerRadius"]) {
|
||||
CABasicAnimation *theAnimation = [CABasicAnimation
|
||||
animationWithKeyPath:event];
|
||||
theAnimation.fromValue = [[self presentationLayer] valueForKey:event];
|
||||
return theAnimation;
|
||||
}
|
||||
return [super actionForKey:event];
|
||||
}
|
||||
|
||||
- (void)_setPath {
|
||||
LOTRoundRectLayer *presentationRect = (LOTRoundRectLayer *)self.presentationLayer;
|
||||
if (presentationRect == nil) {
|
||||
presentationRect = self;
|
||||
}
|
||||
CGFloat halfWidth = presentationRect.rectSize.x / 2;
|
||||
CGFloat halfHeight = presentationRect.rectSize.y / 2;
|
||||
|
||||
CGRect rectFrame = CGRectMake(presentationRect.rectPosition.x - halfWidth, presentationRect.rectPosition.y - halfHeight, presentationRect.rectSize.x, presentationRect.rectSize.y);
|
||||
|
||||
// UIBezierPath Draws rects from the top left corner, After Effects draws them from the top right.
|
||||
// Switching to manual drawing.
|
||||
|
||||
CGFloat radius = MIN(MIN(halfWidth, halfHeight), presentationRect.rectCornerRadius);
|
||||
UIBezierPath *path1 = [UIBezierPath new];
|
||||
UIBezierPath *path2 = [UIBezierPath new];
|
||||
|
||||
CGPoint point = CGPointMake(CGRectGetMaxX(rectFrame), CGRectGetMinY(rectFrame) + radius);
|
||||
[path1 moveToPoint:point];
|
||||
[path2 moveToPoint:point];
|
||||
|
||||
point.y = CGRectGetMaxY(rectFrame) - radius;
|
||||
[path1 addLineToPoint:point];
|
||||
[path2 addLineToPoint:point];
|
||||
|
||||
if (radius > 0) {
|
||||
point.x = point.x - radius;
|
||||
[path1 addArcWithCenter:point radius:radius startAngle:LOT_DegreesToRadians(0) endAngle:LOT_DegreesToRadians(90) clockwise:YES];
|
||||
[path2 addArcWithCenter:point radius:radius startAngle:LOT_DegreesToRadians(0) endAngle:LOT_DegreesToRadians(90) clockwise:YES];
|
||||
}
|
||||
|
||||
point.x = CGRectGetMinX(rectFrame) + radius;
|
||||
point.y = CGRectGetMaxY(rectFrame);
|
||||
[path1 addLineToPoint:point];
|
||||
[path2 addLineToPoint:point];
|
||||
|
||||
if (radius > 0) {
|
||||
point.y = point.y - radius;
|
||||
[path1 addArcWithCenter:point radius:radius startAngle:LOT_DegreesToRadians(90) endAngle:LOT_DegreesToRadians(180) clockwise:YES];
|
||||
[path2 addArcWithCenter:point radius:radius startAngle:LOT_DegreesToRadians(90) endAngle:LOT_DegreesToRadians(180) clockwise:YES];
|
||||
}
|
||||
|
||||
point.x = CGRectGetMinX(rectFrame);
|
||||
point.y = CGRectGetMinY(rectFrame) + radius;
|
||||
[path1 addLineToPoint:point];
|
||||
[path2 addLineToPoint:point];
|
||||
|
||||
if (radius > 0) {
|
||||
point.x = point.x + radius;
|
||||
[path1 addArcWithCenter:point radius:radius startAngle:LOT_DegreesToRadians(180) endAngle:LOT_DegreesToRadians(270) clockwise:YES];
|
||||
[path2 addArcWithCenter:point radius:radius startAngle:LOT_DegreesToRadians(180) endAngle:LOT_DegreesToRadians(270) clockwise:YES];
|
||||
}
|
||||
|
||||
point.x = CGRectGetMaxX(rectFrame) - radius;
|
||||
point.y = CGRectGetMinY(rectFrame);
|
||||
[path1 addLineToPoint:point];
|
||||
[path2 addLineToPoint:point];
|
||||
|
||||
if (radius > 0) {
|
||||
point.y = point.y + radius;
|
||||
[path1 addArcWithCenter:point radius:radius startAngle:LOT_DegreesToRadians(270) endAngle:LOT_DegreesToRadians(360) clockwise:YES];
|
||||
[path2 addArcWithCenter:point radius:radius startAngle:LOT_DegreesToRadians(270) endAngle:LOT_DegreesToRadians(360) clockwise:YES];
|
||||
}
|
||||
[path1 closePath];
|
||||
[path2 closePath];
|
||||
|
||||
[path1 appendPath:path2];
|
||||
|
||||
self.path = path1.CGPath;
|
||||
}
|
||||
|
||||
- (void)display {
|
||||
[self _setPath];
|
||||
[super display];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation LOTRectShapeLayer {
|
||||
LOTShapeTransform *_transform;
|
||||
LOTShapeStroke *_stroke;
|
||||
LOTShapeFill *_fill;
|
||||
LOTShapeRectangle *_rectangle;
|
||||
LOTShapeTrimPath *_trim;
|
||||
|
||||
LOTRoundRectLayer *_fillLayer;
|
||||
LOTRoundRectLayer *_strokeLayer;
|
||||
|
||||
CAAnimationGroup *_animation;
|
||||
CAAnimationGroup *_strokeAnimation;
|
||||
CAAnimationGroup *_fillAnimation;
|
||||
}
|
||||
|
||||
- (instancetype)initWithRectShape:(LOTShapeRectangle *)rectShape
|
||||
fill:(LOTShapeFill *)fill
|
||||
stroke:(LOTShapeStroke *)stroke
|
||||
trim:(LOTShapeTrimPath *)trim
|
||||
transform:(LOTShapeTransform *)transform
|
||||
withLayerDuration:(NSTimeInterval)duration {
|
||||
self = [super initWithLayerDuration:duration];
|
||||
if (self) {
|
||||
_rectangle = rectShape;
|
||||
_stroke = stroke;
|
||||
_fill = fill;
|
||||
_transform = transform;
|
||||
_trim = trim;
|
||||
|
||||
self.allowsEdgeAntialiasing = YES;
|
||||
self.frame = _transform.compBounds;
|
||||
self.anchorPoint = _transform.anchor.initialPoint;
|
||||
self.opacity = _transform.opacity.initialValue.floatValue;
|
||||
self.position = _transform.position.initialPoint;
|
||||
self.transform = _transform.scale.initialScale;
|
||||
self.sublayerTransform = CATransform3DMakeRotation(_transform.rotation.initialValue.floatValue, 0, 0, 1);
|
||||
|
||||
if (fill) {
|
||||
_fillLayer = [LOTRoundRectLayer layer];
|
||||
_fillLayer.allowsEdgeAntialiasing = YES;
|
||||
_fillLayer.fillColor = _fill.color.initialColor.CGColor;
|
||||
_fillLayer.opacity = _fill.opacity.initialValue.floatValue;
|
||||
_fillLayer.rectCornerRadius = rectShape.cornerRadius.initialValue.floatValue;
|
||||
_fillLayer.rectSize = rectShape.size.initialPoint;
|
||||
_fillLayer.rectPosition = rectShape.position.initialPoint;
|
||||
[self addSublayer:_fillLayer];
|
||||
}
|
||||
|
||||
if (stroke) {
|
||||
_strokeLayer = [LOTRoundRectLayer layer];
|
||||
_strokeLayer.allowsEdgeAntialiasing = YES;
|
||||
_strokeLayer.strokeColor = _stroke.color.initialColor.CGColor;
|
||||
_strokeLayer.opacity = _stroke.opacity.initialValue.floatValue;
|
||||
_strokeLayer.lineWidth = _stroke.width.initialValue.floatValue;
|
||||
_strokeLayer.fillColor = nil;
|
||||
_strokeLayer.backgroundColor = nil;
|
||||
_strokeLayer.lineDashPattern = _stroke.lineDashPattern;
|
||||
_strokeLayer.lineCap = _stroke.capType == LOTLineCapTypeRound ? kCALineCapRound : kCALineCapButt;
|
||||
_strokeLayer.rectCornerRadius = rectShape.cornerRadius.initialValue.floatValue;
|
||||
_strokeLayer.rectSize = rectShape.size.initialPoint;
|
||||
_strokeLayer.rectPosition = rectShape.position.initialPoint;
|
||||
switch (_stroke.joinType) {
|
||||
case LOTLineJoinTypeBevel:
|
||||
_strokeLayer.lineJoin = kCALineJoinBevel;
|
||||
break;
|
||||
case LOTLineJoinTypeMiter:
|
||||
_strokeLayer.lineJoin = kCALineJoinMiter;
|
||||
break;
|
||||
case LOTLineJoinTypeRound:
|
||||
_strokeLayer.lineJoin = kCALineJoinRound;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (trim) {
|
||||
_strokeLayer.trimStart = _trim.start.initialValue.floatValue;
|
||||
_strokeLayer.trimEnd = _trim.end.initialValue.floatValue;
|
||||
_strokeLayer.trimOffset = _trim.offset.initialValue.floatValue;
|
||||
}
|
||||
[self addSublayer:_strokeLayer];
|
||||
}
|
||||
|
||||
[self _buildAnimation];
|
||||
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)_buildAnimation {
|
||||
if (_transform) {
|
||||
_animation = [CAAnimationGroup LOT_animationGroupForAnimatablePropertiesWithKeyPaths:@{@"opacity" : _transform.opacity,
|
||||
@"position" : _transform.position,
|
||||
@"anchorPoint" : _transform.anchor,
|
||||
@"transform" : _transform.scale,
|
||||
@"sublayerTransform.rotation" : _transform.rotation}];
|
||||
[self addAnimation:_animation forKey:@"LottieAnimation"];
|
||||
}
|
||||
|
||||
if (_stroke) {
|
||||
NSMutableDictionary *properties = [NSMutableDictionary dictionaryWithDictionary:@{@"strokeColor" : _stroke.color,
|
||||
@"opacity" : _stroke.opacity,
|
||||
@"lineWidth" : _stroke.width,
|
||||
@"rectSize" : _rectangle.size,
|
||||
@"rectPosition" : _rectangle.position,
|
||||
@"rectCornerRadius" : _rectangle.cornerRadius}];
|
||||
if (_trim) {
|
||||
properties[@"trimStart"] = _trim.start;
|
||||
properties[@"trimEnd"] = _trim.end;
|
||||
properties[@"trimOffset"] = _trim.offset;
|
||||
}
|
||||
_strokeAnimation = [CAAnimationGroup LOT_animationGroupForAnimatablePropertiesWithKeyPaths:properties];
|
||||
[_strokeLayer addAnimation:_strokeAnimation forKey:@""];
|
||||
}
|
||||
|
||||
if (_fill) {
|
||||
_fillAnimation = [CAAnimationGroup LOT_animationGroupForAnimatablePropertiesWithKeyPaths:@{@"fillColor" : _fill.color,
|
||||
@"opacity" : _fill.opacity,
|
||||
@"rectSize" : _rectangle.size,
|
||||
@"rectPosition" : _rectangle.position,
|
||||
@"rectCornerRadius" : _rectangle.cornerRadius}];
|
||||
[_fillLayer addAnimation:_fillAnimation forKey:@""];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
@ -1,21 +0,0 @@
|
||||
//
|
||||
// LOTShapeLayerView.h
|
||||
// LottieAnimator
|
||||
//
|
||||
// Created by Brandon Withrow on 7/13/16.
|
||||
// Copyright © 2016 Brandon Withrow. All rights reserved.
|
||||
//
|
||||
|
||||
#import "LOTAnimatableLayer.h"
|
||||
#import "LOTModels.h"
|
||||
|
||||
@interface LOTShapeLayerView : LOTAnimatableLayer
|
||||
|
||||
- (instancetype)initWithShape:(LOTShapePath *)shape
|
||||
fill:(LOTShapeFill *)fill
|
||||
stroke:(LOTShapeStroke *)stroke
|
||||
trim:(LOTShapeTrimPath *)trim
|
||||
transform:(LOTShapeTransform *)transform
|
||||
withLayerDuration:(NSTimeInterval)duration;
|
||||
|
||||
@end
|
||||
@ -1,128 +0,0 @@
|
||||
//
|
||||
// LOTShapeLayerView.m
|
||||
// LottieAnimator
|
||||
//
|
||||
// Created by Brandon Withrow on 7/13/16.
|
||||
// Copyright © 2016 Brandon Withrow. All rights reserved.
|
||||
//
|
||||
|
||||
#import "LOTShapeLayerView.h"
|
||||
#import "CAAnimationGroup+LOTAnimatableGroup.h"
|
||||
#import "LOTStrokeShapeLayer.h"
|
||||
|
||||
@implementation LOTShapeLayerView {
|
||||
LOTShapeTransform *_transform;
|
||||
LOTShapeStroke *_stroke;
|
||||
LOTShapeFill *_fill;
|
||||
LOTShapePath *_path;
|
||||
LOTShapeTrimPath *_trim;
|
||||
|
||||
CAShapeLayer *_fillLayer;
|
||||
LOTStrokeShapeLayer *_strokeLayer;
|
||||
|
||||
CAAnimationGroup *_animation;
|
||||
CAAnimationGroup *_strokeAnimation;
|
||||
CAAnimationGroup *_fillAnimation;
|
||||
}
|
||||
|
||||
- (instancetype)initWithShape:(LOTShapePath *)shape
|
||||
fill:(LOTShapeFill *)fill
|
||||
stroke:(LOTShapeStroke *)stroke
|
||||
trim:(LOTShapeTrimPath *)trim
|
||||
transform:(LOTShapeTransform *)transform
|
||||
withLayerDuration:(NSTimeInterval)duration {
|
||||
self = [super initWithLayerDuration:duration];
|
||||
if (self) {
|
||||
_path = shape;
|
||||
_stroke = stroke;
|
||||
_fill = fill;
|
||||
_transform = transform;
|
||||
_trim = trim;
|
||||
|
||||
self.allowsEdgeAntialiasing = YES;
|
||||
self.frame = _transform.compBounds;
|
||||
self.anchorPoint = _transform.anchor.initialPoint;
|
||||
self.opacity = _transform.opacity.initialValue.floatValue;
|
||||
self.position = _transform.position.initialPoint;
|
||||
self.transform = _transform.scale.initialScale;
|
||||
self.sublayerTransform = CATransform3DMakeRotation(_transform.rotation.initialValue.floatValue, 0, 0, 1);
|
||||
|
||||
if (fill) {
|
||||
_fillLayer = [CAShapeLayer layer];
|
||||
_fillLayer.allowsEdgeAntialiasing = YES;
|
||||
_fillLayer.path = _path.shapePath.initialShape.CGPath;
|
||||
_fillLayer.fillColor = _fill.color.initialColor.CGColor;
|
||||
_fillLayer.opacity = _fill.opacity.initialValue.floatValue;
|
||||
[self addSublayer:_fillLayer];
|
||||
}
|
||||
|
||||
if (stroke) {
|
||||
_strokeLayer = [LOTStrokeShapeLayer layer];
|
||||
_strokeLayer.allowsEdgeAntialiasing = YES;
|
||||
_strokeLayer.path = _path.shapePath.initialShape.CGPath;
|
||||
_strokeLayer.strokeColor = _stroke.color.initialColor.CGColor;
|
||||
_strokeLayer.opacity = _stroke.opacity.initialValue.floatValue;
|
||||
_strokeLayer.lineWidth = _stroke.width.initialValue.floatValue;
|
||||
_strokeLayer.lineDashPattern = _stroke.lineDashPattern;
|
||||
_strokeLayer.lineCap = _stroke.capType == LOTLineCapTypeRound ? kCALineCapRound : kCALineCapButt;
|
||||
switch (_stroke.joinType) {
|
||||
case LOTLineJoinTypeBevel:
|
||||
_strokeLayer.lineJoin = kCALineJoinBevel;
|
||||
break;
|
||||
case LOTLineJoinTypeMiter:
|
||||
_strokeLayer.lineJoin = kCALineJoinMiter;
|
||||
break;
|
||||
case LOTLineJoinTypeRound:
|
||||
_strokeLayer.lineJoin = kCALineJoinRound;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (trim) {
|
||||
_strokeLayer.trimStart = _trim.start.initialValue.floatValue;
|
||||
_strokeLayer.trimEnd = _trim.end.initialValue.floatValue;
|
||||
_strokeLayer.trimOffset = _trim.offset.initialValue.floatValue;
|
||||
}
|
||||
_strokeLayer.fillColor = nil;
|
||||
[self addSublayer:_strokeLayer];
|
||||
}
|
||||
|
||||
|
||||
[self _buildAnimation];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)_buildAnimation {
|
||||
if (_transform) {
|
||||
_animation = [CAAnimationGroup LOT_animationGroupForAnimatablePropertiesWithKeyPaths:@{@"opacity" : _transform.opacity,
|
||||
@"position" : _transform.position,
|
||||
@"anchorPoint" : _transform.anchor,
|
||||
@"transform" : _transform.scale,
|
||||
@"sublayerTransform.rotation" : _transform.rotation}];
|
||||
[self addAnimation:_animation forKey:@"LottieAnimation"];
|
||||
}
|
||||
|
||||
if (_stroke) {
|
||||
NSMutableDictionary *properties = [NSMutableDictionary dictionaryWithDictionary:@{@"strokeColor" : _stroke.color,
|
||||
@"opacity" : _stroke.opacity,
|
||||
@"lineWidth" : _stroke.width,
|
||||
@"path" : _path.shapePath}];
|
||||
if (_trim) {
|
||||
properties[@"trimStart"] = _trim.start;
|
||||
properties[@"trimEnd"] = _trim.end;
|
||||
properties[@"trimOffset"] = _trim.offset;
|
||||
}
|
||||
_strokeAnimation = [CAAnimationGroup LOT_animationGroupForAnimatablePropertiesWithKeyPaths:properties];
|
||||
[_strokeLayer addAnimation:_strokeAnimation forKey:@""];
|
||||
}
|
||||
|
||||
if (_fill) {
|
||||
_fillAnimation = [CAAnimationGroup LOT_animationGroupForAnimatablePropertiesWithKeyPaths:@{@"fillColor" : _fill.color,
|
||||
@"opacity" : _fill.opacity,
|
||||
@"path" : _path.shapePath}];
|
||||
[_fillLayer addAnimation:_fillAnimation forKey:@""];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
@ -1,17 +0,0 @@
|
||||
//
|
||||
// LOTStrokeShapeLayer.h
|
||||
// Pods
|
||||
//
|
||||
// Created by Brandon Withrow on 2/7/17.
|
||||
//
|
||||
//
|
||||
|
||||
#import <QuartzCore/QuartzCore.h>
|
||||
|
||||
@interface LOTStrokeShapeLayer : CAShapeLayer
|
||||
|
||||
@property CGFloat trimStart;
|
||||
@property CGFloat trimEnd;
|
||||
@property CGFloat trimOffset;
|
||||
|
||||
@end
|
||||
@ -1,87 +0,0 @@
|
||||
//
|
||||
// LOTStrokeShapeLayer.m
|
||||
// Pods
|
||||
//
|
||||
// Created by Brandon Withrow on 2/7/17.
|
||||
//
|
||||
//
|
||||
|
||||
#import "LOTStrokeShapeLayer.h"
|
||||
|
||||
@implementation LOTStrokeShapeLayer
|
||||
|
||||
@dynamic trimEnd;
|
||||
@dynamic trimStart;
|
||||
@dynamic trimOffset;
|
||||
|
||||
- (instancetype)init {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
[self _commonInit];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (instancetype)initWithLayer:(id)layer {
|
||||
if( ( self = [super initWithLayer:layer] ) ) {
|
||||
if ([layer isKindOfClass:[LOTStrokeShapeLayer class]]) {
|
||||
self.trimEnd = ((LOTStrokeShapeLayer *)layer).trimEnd;
|
||||
self.trimStart = ((LOTStrokeShapeLayer *)layer).trimStart;
|
||||
self.trimOffset = ((LOTStrokeShapeLayer *)layer).trimOffset;
|
||||
}
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)_commonInit {
|
||||
self.trimStart = 0;
|
||||
self.trimOffset = 0;
|
||||
self.trimEnd = 1;
|
||||
}
|
||||
|
||||
+ (BOOL)needsDisplayForKey:(NSString *)key {
|
||||
BOOL needsDisplay = [super needsDisplayForKey:key];
|
||||
|
||||
if ([key isEqualToString:@"trimEnd"] || [key isEqualToString:@"trimStart"] || [key isEqualToString:@"trimOffset"]) {
|
||||
needsDisplay = YES;
|
||||
}
|
||||
|
||||
return needsDisplay;
|
||||
}
|
||||
|
||||
- (void)display {
|
||||
LOTStrokeShapeLayer *presentationLayer = (LOTStrokeShapeLayer *)self.presentationLayer;
|
||||
if (presentationLayer == nil) {
|
||||
presentationLayer = self;
|
||||
}
|
||||
|
||||
CGFloat threeSixty = 360;
|
||||
CGFloat offsetAmount = fmodf(fabs(presentationLayer.trimOffset) , threeSixty) / threeSixty;
|
||||
offsetAmount = presentationLayer.trimOffset < 0 ? offsetAmount * -1 : offsetAmount;
|
||||
BOOL startFirst = presentationLayer.trimStart < presentationLayer.trimEnd;
|
||||
|
||||
CGFloat trimStart = (startFirst ? presentationLayer.trimStart : presentationLayer.trimEnd) + offsetAmount;
|
||||
CGFloat trimEnd = (startFirst ? presentationLayer.trimEnd : presentationLayer.trimStart) + offsetAmount;
|
||||
|
||||
if (trimEnd > 1 || trimStart > 1) {
|
||||
trimStart = trimStart - 1;
|
||||
trimEnd = trimEnd - 1;
|
||||
}
|
||||
if (trimStart < 0 || trimEnd < 0) {
|
||||
trimStart = trimStart + 1;
|
||||
trimEnd = trimEnd + 1;
|
||||
}
|
||||
|
||||
trimStart = trimStart * 0.5;
|
||||
trimEnd = trimEnd * 0.5;
|
||||
|
||||
[CATransaction begin];
|
||||
[CATransaction setDisableActions:YES];
|
||||
|
||||
self.strokeStart = MAX(trimStart, 0);
|
||||
self.strokeEnd = MIN(trimEnd, 1);
|
||||
|
||||
[CATransaction commit];
|
||||
}
|
||||
|
||||
@end
|
||||
@ -1,21 +0,0 @@
|
||||
//
|
||||
// LOTAnimatableBoundsValue.h
|
||||
// LottieAnimator
|
||||
//
|
||||
// Created by brandon_withrow on 7/20/16.
|
||||
// Copyright © 2016 Brandon Withrow. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <QuartzCore/QuartzCore.h>
|
||||
#import "LOTAnimatableValue.h"
|
||||
#import "LOTKeyframe.h"
|
||||
|
||||
@interface LOTAnimatableBoundsValue : NSObject <LOTAnimatableValue>
|
||||
|
||||
- (instancetype)initWithSizeValues:(NSDictionary *)sizeValue frameRate:(NSNumber *)frameRate;
|
||||
|
||||
@property (nonatomic, readonly) CGRect initialBounds;
|
||||
@property (nonatomic, readonly) LOTKeyframeGroup *keyframeGroup;
|
||||
|
||||
@end
|
||||
@ -1,199 +0,0 @@
|
||||
//
|
||||
// LOTAnimatableBoundsValue.m
|
||||
// LottieAnimator
|
||||
//
|
||||
// Created by brandon_withrow on 7/20/16.
|
||||
// Copyright © 2016 Brandon Withrow. All rights reserved.
|
||||
//
|
||||
|
||||
#import "LOTPlatformCompat.h"
|
||||
#import "LOTAnimatableBoundsValue.h"
|
||||
#import "LOTHelpers.h"
|
||||
|
||||
@interface LOTAnimatableBoundsValue ()
|
||||
|
||||
@property (nonatomic, readonly) NSArray *boundsKeyframes;
|
||||
@property (nonatomic, readonly) NSArray<NSNumber *> *keyTimes;
|
||||
@property (nonatomic, readonly) NSArray<CAMediaTimingFunction *> *timingFunctions;
|
||||
@property (nonatomic, readonly) NSTimeInterval delay;
|
||||
@property (nonatomic, readonly) NSTimeInterval duration;
|
||||
@property (nonatomic, readonly) NSNumber *startFrame;
|
||||
@property (nonatomic, readonly) NSNumber *durationFrames;
|
||||
@property (nonatomic, readonly) NSNumber *frameRate;
|
||||
|
||||
@end
|
||||
|
||||
@implementation LOTAnimatableBoundsValue
|
||||
|
||||
- (instancetype)initWithSizeValues:(NSDictionary *)sizeValue frameRate:(NSNumber *)frameRate {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_frameRate = frameRate;
|
||||
id value = sizeValue[@"k"];
|
||||
if (DEBUG_USE_NEW_RENDERER) {
|
||||
_keyframeGroup = [[LOTKeyframeGroup alloc] initWithData:value];
|
||||
}
|
||||
if ([value isKindOfClass:[NSArray class]] &&
|
||||
[[(NSArray *)value firstObject] isKindOfClass:[NSDictionary class]] &&
|
||||
[(NSArray *)value firstObject][@"t"]) {
|
||||
//Keframes
|
||||
[self _buildAnimationForKeyframes:value];
|
||||
} else if ([value isKindOfClass:[NSArray class]]) {
|
||||
//Single Value, no animation
|
||||
_initialBounds = [self _boundsRectFromValueArray:value];
|
||||
}
|
||||
if (sizeValue[@"x"]) {
|
||||
NSLog(@"%s: Warning: expressions are not supported", __PRETTY_FUNCTION__);
|
||||
}
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)_buildAnimationForKeyframes:(NSArray<NSDictionary *> *)keyframes {
|
||||
NSMutableArray *keyTimes = [NSMutableArray array];
|
||||
NSMutableArray *timingFunctions = [NSMutableArray array];
|
||||
NSMutableArray *rectKeyframes = [NSMutableArray array];
|
||||
|
||||
_startFrame = keyframes.firstObject[@"t"];
|
||||
NSNumber *endFrame = keyframes.lastObject[@"t"];
|
||||
|
||||
NSAssert((_startFrame && endFrame && _startFrame.integerValue < endFrame.integerValue),
|
||||
@"Lottie: Keyframe animation has incorrect time values or invalid number of keyframes");
|
||||
|
||||
// Calculate time duration
|
||||
_durationFrames = @(endFrame.floatValue - _startFrame.floatValue);
|
||||
|
||||
_duration = _durationFrames.floatValue / _frameRate.floatValue;
|
||||
_delay = _startFrame.floatValue / _frameRate.floatValue;
|
||||
|
||||
BOOL addStartValue = YES;
|
||||
BOOL addTimePadding = NO;
|
||||
NSArray *outBounds = nil;
|
||||
|
||||
for (NSDictionary *keyframe in keyframes) {
|
||||
// Get keyframe time value
|
||||
NSNumber *frame = keyframe[@"t"];
|
||||
// Calculate percentage value for keyframe.
|
||||
//CA Animations accept time values of 0-1 as a percentage of animation completed.
|
||||
NSNumber *timePercentage = @((frame.floatValue - _startFrame.floatValue) / _durationFrames.floatValue);
|
||||
|
||||
if (outBounds) {
|
||||
//add out value
|
||||
CGRect bounds = [self _boundsRectFromValueArray:outBounds];
|
||||
[rectKeyframes addObject:[NSValue valueWithCGRect:bounds]];
|
||||
[timingFunctions addObject:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear]];
|
||||
outBounds = nil;
|
||||
}
|
||||
|
||||
NSArray *startRect = keyframe[@"s"];
|
||||
if (addStartValue) {
|
||||
if (startRect) {
|
||||
CGRect sRect = [self _boundsRectFromValueArray:startRect];
|
||||
if (keyframe == keyframes.firstObject) {
|
||||
[rectKeyframes addObject:[NSValue valueWithCGRect:sRect]];
|
||||
_initialBounds = sRect;
|
||||
} else {
|
||||
[rectKeyframes addObject:[NSValue valueWithCGRect:sRect]];
|
||||
[timingFunctions addObject:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear]];
|
||||
}
|
||||
addStartValue = NO;
|
||||
}
|
||||
}
|
||||
|
||||
if (addTimePadding) {
|
||||
// add time padding
|
||||
NSNumber *holdPercentage = @(timePercentage.floatValue - 0.00001);
|
||||
[keyTimes addObject:[holdPercentage copy]];
|
||||
addTimePadding = NO;
|
||||
}
|
||||
|
||||
// add end value if present for keyframe
|
||||
NSArray *endRect = keyframe[@"e"];
|
||||
if (endRect) {
|
||||
CGRect bounds = [self _boundsRectFromValueArray:endRect];
|
||||
[rectKeyframes addObject:[NSValue valueWithCGRect:bounds]];
|
||||
|
||||
/*
|
||||
* Timing Function for time interpolations between keyframes
|
||||
* Should be n-1 where n is the number of keyframes
|
||||
*/
|
||||
CAMediaTimingFunction *timingFunction;
|
||||
NSDictionary *timingControlPoint1 = keyframe[@"o"];
|
||||
NSDictionary *timingControlPoint2 = keyframe[@"i"];
|
||||
|
||||
if (timingControlPoint1 && timingControlPoint2) {
|
||||
// Easing function
|
||||
CGPoint cp1 = [self _pointFromValueDict:timingControlPoint1];
|
||||
CGPoint cp2 = [self _pointFromValueDict:timingControlPoint2];
|
||||
timingFunction = [CAMediaTimingFunction functionWithControlPoints:cp1.x :cp1.y :cp2.x :cp2.y];
|
||||
} else {
|
||||
// No easing function specified, fallback to linear
|
||||
timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
|
||||
}
|
||||
[timingFunctions addObject:timingFunction];
|
||||
}
|
||||
|
||||
// add time
|
||||
[keyTimes addObject:timePercentage];
|
||||
|
||||
// Check if keyframe is a hold keyframe
|
||||
if ([keyframe[@"h"] boolValue]) {
|
||||
// set out value as start and flag next frame accordinly
|
||||
outBounds = startRect;
|
||||
addStartValue = YES;
|
||||
addTimePadding = YES;
|
||||
}
|
||||
}
|
||||
|
||||
_keyTimes = keyTimes;
|
||||
_timingFunctions = timingFunctions;
|
||||
_boundsKeyframes = rectKeyframes;
|
||||
}
|
||||
|
||||
- (CGRect)_boundsRectFromValueArray:(NSArray<NSNumber *> *)values {
|
||||
if (values.count >= 2) {
|
||||
return CGRectMake(0, 0, [values[0] floatValue], [values[1] floatValue]);
|
||||
}
|
||||
return CGRectZero;
|
||||
}
|
||||
|
||||
- (CGPoint)_pointFromValueDict:(NSDictionary *)values {
|
||||
NSNumber *xValue = @0, *yValue = @0;
|
||||
if ([values[@"x"] isKindOfClass:[NSNumber class]]) {
|
||||
xValue = values[@"x"];
|
||||
} else if ([values[@"x"] isKindOfClass:[NSArray class]]) {
|
||||
xValue = values[@"x"][0];
|
||||
}
|
||||
|
||||
if ([values[@"y"] isKindOfClass:[NSNumber class]]) {
|
||||
yValue = values[@"y"];
|
||||
} else if ([values[@"y"] isKindOfClass:[NSArray class]]) {
|
||||
yValue = values[@"y"][0];
|
||||
}
|
||||
|
||||
return CGPointMake([xValue floatValue], [yValue floatValue]);
|
||||
}
|
||||
|
||||
- (BOOL)hasAnimation {
|
||||
return (self.boundsKeyframes.count > 0);
|
||||
}
|
||||
|
||||
- (nullable CAKeyframeAnimation *)animationForKeyPath:(nonnull NSString *)keypath {
|
||||
if (self.hasAnimation == NO) {
|
||||
return nil;
|
||||
}
|
||||
CAKeyframeAnimation *keyframeAnimation = [CAKeyframeAnimation animationWithKeyPath:keypath];
|
||||
keyframeAnimation.keyTimes = self.keyTimes;
|
||||
keyframeAnimation.values = self.boundsKeyframes;
|
||||
keyframeAnimation.timingFunctions = self.timingFunctions;
|
||||
keyframeAnimation.duration = self.duration;
|
||||
keyframeAnimation.beginTime = self.delay;
|
||||
keyframeAnimation.fillMode = kCAFillModeForwards;
|
||||
return keyframeAnimation;
|
||||
}
|
||||
|
||||
- (NSString *)description {
|
||||
return NSStringFromCGRect(self.initialBounds);
|
||||
}
|
||||
|
||||
@end
|
||||
@ -1,22 +0,0 @@
|
||||
//
|
||||
// LOTAnimatableColorValue.h
|
||||
// LottieAnimator
|
||||
//
|
||||
// Created by brandon_withrow on 6/23/16.
|
||||
// Copyright © 2016 Brandon Withrow. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <QuartzCore/QuartzCore.h>
|
||||
#import "LOTAnimatableValue.h"
|
||||
#import "LOTPlatformCompat.h"
|
||||
#import "LOTKeyframe.h"
|
||||
|
||||
@interface LOTAnimatableColorValue : NSObject <LOTAnimatableValue>
|
||||
|
||||
- (instancetype)initWithColorValues:(NSDictionary *)colorValues frameRate:(NSNumber *)frameRate;
|
||||
|
||||
@property (nonatomic, readonly) UIColor *initialColor;
|
||||
@property (nonatomic, readonly) LOTKeyframeGroup *keyframeGroup;
|
||||
|
||||
@end
|
||||
@ -1,208 +0,0 @@
|
||||
//
|
||||
// LOTAnimatableColorValue.m
|
||||
// LottieAnimator
|
||||
//
|
||||
// Created by brandon_withrow on 6/23/16.
|
||||
// Copyright © 2016 Brandon Withrow. All rights reserved.
|
||||
//
|
||||
|
||||
#import "LOTAnimatableColorValue.h"
|
||||
#import "LOTHelpers.h"
|
||||
|
||||
@interface LOTAnimatableColorValue ()
|
||||
|
||||
@property (nonatomic, readonly) NSArray *colorKeyframes;
|
||||
@property (nonatomic, readonly) NSArray<NSNumber *> *keyTimes;
|
||||
@property (nonatomic, readonly) NSArray<CAMediaTimingFunction *> *timingFunctions;
|
||||
@property (nonatomic, readonly) NSTimeInterval delay;
|
||||
@property (nonatomic, readonly) NSTimeInterval duration;
|
||||
|
||||
@property (nonatomic, readonly) NSNumber *startFrame;
|
||||
@property (nonatomic, readonly) NSNumber *durationFrames;
|
||||
@property (nonatomic, readonly) NSNumber *frameRate;
|
||||
|
||||
@end
|
||||
|
||||
@implementation LOTAnimatableColorValue
|
||||
|
||||
- (instancetype)initWithColorValues:(NSDictionary *)colorValues frameRate:(NSNumber *)frameRate {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_frameRate = frameRate;
|
||||
NSArray *value = colorValues[@"k"];
|
||||
if (DEBUG_USE_NEW_RENDERER) {
|
||||
_keyframeGroup = [[LOTKeyframeGroup alloc] initWithData:value];
|
||||
}
|
||||
if ([value isKindOfClass:[NSArray class]] &&
|
||||
[[(NSArray *)value firstObject] isKindOfClass:[NSDictionary class]] &&
|
||||
[(NSArray *)value firstObject][@"t"]) {
|
||||
//Keyframes
|
||||
[self _buildAnimationForKeyframes:value];
|
||||
} else {
|
||||
//Single Value, no animation
|
||||
_initialColor = [[self _colorValueFromArray:value] copy];
|
||||
}
|
||||
if (colorValues[@"x"]) {
|
||||
NSLog(@"%s: Warning: expressions are not supported", __PRETTY_FUNCTION__);
|
||||
}
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)_buildAnimationForKeyframes:(NSArray<NSDictionary *> *)keyframes {
|
||||
NSMutableArray *keyTimes = [NSMutableArray array];
|
||||
NSMutableArray *timingFunctions = [NSMutableArray array];
|
||||
NSMutableArray *colorValues = [NSMutableArray array];
|
||||
|
||||
_startFrame = keyframes.firstObject[@"t"];
|
||||
NSNumber *endFrame = keyframes.lastObject[@"t"];
|
||||
|
||||
NSAssert((_startFrame && endFrame && _startFrame.integerValue < endFrame.integerValue),
|
||||
@"Lottie: Keyframe animation has incorrect time values or invalid number of keyframes");
|
||||
|
||||
// Calculate time duration
|
||||
_durationFrames = @(endFrame.floatValue - _startFrame.floatValue);
|
||||
|
||||
_duration = _durationFrames.floatValue / _frameRate.floatValue;
|
||||
_delay = _startFrame.floatValue / _frameRate.floatValue;
|
||||
|
||||
BOOL addStartValue = YES;
|
||||
BOOL addTimePadding = NO;
|
||||
UIColor *outColor = nil;
|
||||
|
||||
for (NSDictionary *keyframe in keyframes) {
|
||||
// Get keyframe time value
|
||||
NSNumber *frame = keyframe[@"t"];
|
||||
// Calculate percentage value for keyframe.
|
||||
//CA Animations accept time values of 0-1 as a percentage of animation completed.
|
||||
NSNumber *timePercentage = @((frame.floatValue - _startFrame.floatValue) / _durationFrames.floatValue);
|
||||
|
||||
if (outColor) {
|
||||
//add out value
|
||||
[colorValues addObject:(id)[[outColor copy] CGColor]];
|
||||
[timingFunctions addObject:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear]];
|
||||
outColor = nil;
|
||||
}
|
||||
|
||||
UIColor *startColor = [self _colorValueFromArray:keyframe[@"s"]];
|
||||
if (addStartValue) {
|
||||
// Add start value
|
||||
if (startColor) {
|
||||
if (keyframe == keyframes.firstObject) {
|
||||
_initialColor = startColor;
|
||||
}
|
||||
[colorValues addObject:(id)[[startColor copy] CGColor]];
|
||||
if (timingFunctions.count) {
|
||||
[timingFunctions addObject:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear]];
|
||||
}
|
||||
}
|
||||
addStartValue = NO;
|
||||
}
|
||||
|
||||
if (addTimePadding) {
|
||||
// add time padding
|
||||
NSNumber *holdPercentage = @(timePercentage.floatValue - 0.00001);
|
||||
[keyTimes addObject:[holdPercentage copy]];
|
||||
addTimePadding = NO;
|
||||
}
|
||||
|
||||
// add end value if present for keyframe
|
||||
UIColor *endColor = [self _colorValueFromArray:keyframe[@"e"]];
|
||||
if (endColor) {
|
||||
[colorValues addObject:(id)[[endColor copy] CGColor]];
|
||||
/*
|
||||
* Timing Function for time interpolations between keyframes
|
||||
* Should be n-1 where n is the number of keyframes
|
||||
*/
|
||||
CAMediaTimingFunction *timingFunction;
|
||||
NSDictionary *timingControlPoint1 = keyframe[@"o"];
|
||||
NSDictionary *timingControlPoint2 = keyframe[@"i"];
|
||||
|
||||
if (timingControlPoint1 && timingControlPoint2) {
|
||||
// Easing function
|
||||
CGPoint cp1 = [self _pointFromValueDict:timingControlPoint1];
|
||||
CGPoint cp2 = [self _pointFromValueDict:timingControlPoint2];
|
||||
timingFunction = [CAMediaTimingFunction functionWithControlPoints:cp1.x :cp1.y :cp2.x :cp2.y];
|
||||
} else {
|
||||
// No easing function specified, fallback to linear
|
||||
timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
|
||||
}
|
||||
[timingFunctions addObject:timingFunction];
|
||||
}
|
||||
|
||||
// add time
|
||||
[keyTimes addObject:timePercentage];
|
||||
|
||||
// Check if keyframe is a hold keyframe
|
||||
if ([keyframe[@"h"] boolValue]) {
|
||||
// set out value as start and flag next frame accordinly
|
||||
outColor = startColor;
|
||||
addStartValue = YES;
|
||||
addTimePadding = YES;
|
||||
}
|
||||
}
|
||||
|
||||
_colorKeyframes = colorValues;
|
||||
_keyTimes = keyTimes;
|
||||
_timingFunctions = timingFunctions;
|
||||
}
|
||||
|
||||
- (UIColor *)_colorValueFromArray:(NSArray<NSNumber *> *)colorArray {
|
||||
if (colorArray.count == 4) {
|
||||
BOOL shouldUse255 = NO;
|
||||
for (NSNumber *number in colorArray) {
|
||||
if (number.floatValue > 1) {
|
||||
shouldUse255 = YES;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
return [UIColor colorWithRed:colorArray[0].floatValue / (shouldUse255 ? 255.f : 1.f)
|
||||
green:colorArray[1].floatValue / (shouldUse255 ? 255.f : 1.f)
|
||||
blue:colorArray[2].floatValue / (shouldUse255 ? 255.f : 1.f)
|
||||
alpha:colorArray[3].floatValue / (shouldUse255 ? 255.f : 1.f)];
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (CGPoint)_pointFromValueDict:(NSDictionary *)values {
|
||||
NSNumber *xValue = @0, *yValue = @0;
|
||||
if ([values[@"x"] isKindOfClass:[NSNumber class]]) {
|
||||
xValue = values[@"x"];
|
||||
} else if ([values[@"x"] isKindOfClass:[NSArray class]]) {
|
||||
xValue = values[@"x"][0];
|
||||
}
|
||||
|
||||
if ([values[@"y"] isKindOfClass:[NSNumber class]]) {
|
||||
yValue = values[@"y"];
|
||||
} else if ([values[@"y"] isKindOfClass:[NSArray class]]) {
|
||||
yValue = values[@"y"][0];
|
||||
}
|
||||
|
||||
return CGPointMake([xValue floatValue], [yValue floatValue]);
|
||||
}
|
||||
|
||||
- (BOOL)hasAnimation {
|
||||
return (self.colorKeyframes.count > 0);
|
||||
}
|
||||
|
||||
- (nullable CAKeyframeAnimation *)animationForKeyPath:(nonnull NSString *)keypath {
|
||||
if (self.hasAnimation == NO) {
|
||||
return nil;
|
||||
}
|
||||
CAKeyframeAnimation *keyframeAnimation = [CAKeyframeAnimation animationWithKeyPath:keypath];
|
||||
keyframeAnimation.keyTimes = self.keyTimes;
|
||||
keyframeAnimation.values = self.colorKeyframes;
|
||||
keyframeAnimation.timingFunctions = self.timingFunctions;
|
||||
keyframeAnimation.duration = self.duration;
|
||||
keyframeAnimation.beginTime = self.delay;
|
||||
keyframeAnimation.fillMode = kCAFillModeForwards;
|
||||
return keyframeAnimation;
|
||||
}
|
||||
|
||||
- (NSString *)description {
|
||||
return self.initialColor.description;
|
||||
}
|
||||
|
||||
@end
|
||||
@ -1,27 +0,0 @@
|
||||
//
|
||||
// LOTAnimatableNumberValue.h
|
||||
// LottieAnimator
|
||||
//
|
||||
// Created by brandon_withrow on 6/23/16.
|
||||
// Copyright © 2016 Brandon Withrow. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <CoreGraphics/CoreGraphics.h>
|
||||
#import "LOTAnimatableValue.h"
|
||||
#import "LOTKeyframe.h"
|
||||
|
||||
@interface LOTAnimatableNumberValue : NSObject <LOTAnimatableValue>
|
||||
|
||||
- (instancetype)initWithNumberValues:(NSDictionary *)numberValues frameRate:(NSNumber *)frameRate;
|
||||
- (void)remapValuesFromMin:(NSNumber *)fromMin
|
||||
fromMax:(NSNumber *)fromMax
|
||||
toMin:(NSNumber *)toMin
|
||||
toMax:(NSNumber *)toMax;
|
||||
|
||||
- (void)remapValueWithBlock:(CGFloat (^)(CGFloat inValue))remapBlock;
|
||||
|
||||
@property (nonatomic, readonly) NSNumber *initialValue;
|
||||
@property (nonatomic, readonly) LOTKeyframeGroup *keyframeGroup;
|
||||
|
||||
@end
|
||||
@ -1,223 +0,0 @@
|
||||
//
|
||||
// LOTAnimatableNumberValue.m
|
||||
// LottieAnimator
|
||||
//
|
||||
// Created by brandon_withrow on 6/23/16.
|
||||
// Copyright © 2016 Brandon Withrow. All rights reserved.
|
||||
//
|
||||
|
||||
#import "LOTAnimatableNumberValue.h"
|
||||
#import "LOTHelpers.h"
|
||||
|
||||
@interface LOTAnimatableNumberValue ()
|
||||
|
||||
@property (nonatomic, readonly) NSArray<NSNumber *> *valueKeyframes;
|
||||
@property (nonatomic, readonly) NSArray<NSNumber *> *keyTimes;
|
||||
@property (nonatomic, readonly) NSArray<CAMediaTimingFunction *> *timingFunctions;
|
||||
@property (nonatomic, readonly) NSTimeInterval delay;
|
||||
@property (nonatomic, readonly) NSTimeInterval duration;
|
||||
@property (nonatomic, readonly) NSNumber *startFrame;
|
||||
@property (nonatomic, readonly) NSNumber *durationFrames;
|
||||
@property (nonatomic, readonly) NSNumber *frameRate;
|
||||
|
||||
@end
|
||||
|
||||
@implementation LOTAnimatableNumberValue
|
||||
|
||||
- (instancetype)initWithNumberValues:(NSDictionary *)numberValues frameRate:(NSNumber *)frameRate {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_frameRate = frameRate;
|
||||
id value = numberValues[@"k"];
|
||||
if (DEBUG_USE_NEW_RENDERER) {
|
||||
_keyframeGroup = [[LOTKeyframeGroup alloc] initWithData:value];
|
||||
}
|
||||
if ([value isKindOfClass:[NSArray class]] &&
|
||||
[[(NSArray *)value firstObject] isKindOfClass:[NSDictionary class]] &&
|
||||
[(NSArray *)value firstObject][@"t"]) {
|
||||
//Keframes
|
||||
[self _buildAnimationForKeyframes:value];
|
||||
} else {
|
||||
//Single Value, no animation
|
||||
_initialValue = [[self _numberValueFromObject:value] copy];
|
||||
}
|
||||
if (numberValues[@"x"]) {
|
||||
NSLog(@"%s: Warning: expressions are not supported", __PRETTY_FUNCTION__);
|
||||
}
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)_buildAnimationForKeyframes:(NSArray<NSDictionary *> *)keyframes {
|
||||
NSMutableArray *keyTimes = [NSMutableArray array];
|
||||
NSMutableArray *timingFunctions = [NSMutableArray array];
|
||||
NSMutableArray<NSNumber *> *numberValues = [NSMutableArray array];
|
||||
|
||||
_startFrame = keyframes.firstObject[@"t"];
|
||||
NSNumber *endFrame = keyframes.lastObject[@"t"];
|
||||
|
||||
NSAssert((_startFrame && endFrame && _startFrame.integerValue <= endFrame.integerValue),
|
||||
@"Lottie: Keyframe animation has incorrect time values or invalid number of keyframes");
|
||||
// Calculate time duration
|
||||
_durationFrames = @(endFrame.floatValue - _startFrame.floatValue);
|
||||
|
||||
_duration = _durationFrames.floatValue / _frameRate.floatValue;
|
||||
_delay = _startFrame.floatValue / _frameRate.floatValue;
|
||||
|
||||
|
||||
BOOL addStartValue = YES;
|
||||
BOOL addTimePadding = NO;
|
||||
NSNumber *outValue = nil;
|
||||
|
||||
for (NSDictionary *keyframe in keyframes) {
|
||||
// Get keyframe time value
|
||||
NSNumber *frame = keyframe[@"t"];
|
||||
// Calculate percentage value for keyframe.
|
||||
//CA Animations accept time values of 0-1 as a percentage of animation completed.
|
||||
NSNumber *timePercentage = @((frame.floatValue - _startFrame.floatValue) / _durationFrames.floatValue);
|
||||
|
||||
if (outValue) {
|
||||
//add out value
|
||||
[numberValues addObject:[outValue copy]];
|
||||
[timingFunctions addObject:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear]];
|
||||
outValue = nil;
|
||||
}
|
||||
|
||||
NSNumber *startValue = [self _numberValueFromObject:keyframe[@"s"]];
|
||||
if (addStartValue) {
|
||||
// Add start value
|
||||
if (startValue) {
|
||||
if (keyframe == keyframes.firstObject) {
|
||||
_initialValue = startValue;
|
||||
}
|
||||
[numberValues addObject:[startValue copy]];
|
||||
if (timingFunctions.count) {
|
||||
[timingFunctions addObject:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear]];
|
||||
}
|
||||
}
|
||||
addStartValue = NO;
|
||||
}
|
||||
|
||||
if (addTimePadding) {
|
||||
// add time padding
|
||||
NSNumber *holdPercentage = @(timePercentage.floatValue - 0.00001);
|
||||
[keyTimes addObject:[holdPercentage copy]];
|
||||
addTimePadding = NO;
|
||||
}
|
||||
|
||||
// add end value if present for keyframe
|
||||
NSNumber *endValue = [self _numberValueFromObject:keyframe[@"e"]];
|
||||
if (endValue) {
|
||||
[numberValues addObject:[endValue copy]];
|
||||
/*
|
||||
* Timing Function for time interpolations between keyframes
|
||||
* Should be n-1 where n is the number of keyframes
|
||||
*/
|
||||
CAMediaTimingFunction *timingFunction;
|
||||
NSDictionary *timingControlPoint1 = keyframe[@"o"];
|
||||
NSDictionary *timingControlPoint2 = keyframe[@"i"];
|
||||
|
||||
if (timingControlPoint1 && timingControlPoint2) {
|
||||
// Easing function
|
||||
CGPoint cp1 = [self _pointFromValueDict:timingControlPoint1];
|
||||
CGPoint cp2 = [self _pointFromValueDict:timingControlPoint2];
|
||||
timingFunction = [CAMediaTimingFunction functionWithControlPoints:cp1.x :cp1.y :cp2.x :cp2.y];
|
||||
} else {
|
||||
// No easing function specified, fallback to linear
|
||||
timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
|
||||
}
|
||||
[timingFunctions addObject:timingFunction];
|
||||
}
|
||||
|
||||
// add time
|
||||
[keyTimes addObject:timePercentage];
|
||||
|
||||
// Check if keyframe is a hold keyframe
|
||||
if ([keyframe[@"h"] boolValue]) {
|
||||
// set out value as start and flag next frame accordinly
|
||||
outValue = startValue;
|
||||
addStartValue = YES;
|
||||
addTimePadding = YES;
|
||||
}
|
||||
}
|
||||
_valueKeyframes = numberValues;
|
||||
_keyTimes = keyTimes;
|
||||
_timingFunctions = timingFunctions;
|
||||
}
|
||||
|
||||
- (NSNumber *)_numberValueFromObject:(id)valueObject {
|
||||
if ([valueObject isKindOfClass:[NSNumber class]]) {
|
||||
return valueObject;
|
||||
}
|
||||
if ([valueObject isKindOfClass:[NSArray class]] &&
|
||||
[[valueObject firstObject] isKindOfClass:[NSNumber class]]) {
|
||||
return [(NSArray *)valueObject firstObject];
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (CGPoint)_pointFromValueDict:(NSDictionary *)values {
|
||||
NSNumber *xValue = @0, *yValue = @0;
|
||||
if ([values[@"x"] isKindOfClass:[NSNumber class]]) {
|
||||
xValue = values[@"x"];
|
||||
} else if ([values[@"x"] isKindOfClass:[NSArray class]]) {
|
||||
xValue = values[@"x"][0];
|
||||
}
|
||||
|
||||
if ([values[@"y"] isKindOfClass:[NSNumber class]]) {
|
||||
yValue = values[@"y"];
|
||||
} else if ([values[@"y"] isKindOfClass:[NSArray class]]) {
|
||||
yValue = values[@"y"][0];
|
||||
}
|
||||
|
||||
return CGPointMake([xValue floatValue], [yValue floatValue]);
|
||||
}
|
||||
|
||||
- (void)remapValuesFromMin:(NSNumber *)fromMin
|
||||
fromMax:(NSNumber *)fromMax
|
||||
toMin:(NSNumber *)toMin
|
||||
toMax:(NSNumber *)toMax {
|
||||
[self remapValueWithBlock:^CGFloat(CGFloat inValue) {
|
||||
return LOT_RemapValue(inValue, fromMin.floatValue, fromMax.floatValue, toMin.floatValue, toMax.floatValue);
|
||||
}];
|
||||
|
||||
}
|
||||
|
||||
- (void)remapValueWithBlock:(CGFloat (^)(CGFloat inValue))remapBlock {
|
||||
NSMutableArray *newvalues = [NSMutableArray array];
|
||||
for (NSNumber *value in _valueKeyframes) {
|
||||
NSNumber *newValue = @(remapBlock(value.floatValue));
|
||||
[newvalues addObject:newValue];
|
||||
}
|
||||
_valueKeyframes = newvalues;
|
||||
|
||||
if (newvalues.count) {
|
||||
_initialValue = newvalues.firstObject;
|
||||
} else if (_initialValue) {
|
||||
_initialValue = @(remapBlock(_initialValue.floatValue));
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)hasAnimation {
|
||||
return (self.valueKeyframes.count > 0);
|
||||
}
|
||||
|
||||
- (nullable CAKeyframeAnimation *)animationForKeyPath:(nonnull NSString *)keypath {
|
||||
if (self.hasAnimation == NO) {
|
||||
return nil;
|
||||
}
|
||||
CAKeyframeAnimation *keyframeAnimation = [CAKeyframeAnimation animationWithKeyPath:keypath];
|
||||
keyframeAnimation.keyTimes = self.keyTimes;
|
||||
keyframeAnimation.values = self.valueKeyframes;
|
||||
keyframeAnimation.timingFunctions = self.timingFunctions;
|
||||
keyframeAnimation.duration = self.duration;
|
||||
keyframeAnimation.beginTime = self.delay;
|
||||
keyframeAnimation.fillMode = kCAFillModeForwards;
|
||||
return keyframeAnimation;
|
||||
}
|
||||
|
||||
- (NSString *)description {
|
||||
return self.initialValue.description;
|
||||
}
|
||||
|
||||
@end
|
||||
@ -1,23 +0,0 @@
|
||||
//
|
||||
// LOTAnimatablePointValue.h
|
||||
// LottieAnimator
|
||||
//
|
||||
// Created by brandon_withrow on 6/23/16.
|
||||
// Copyright © 2016 Brandon Withrow. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <CoreGraphics/CoreGraphics.h>
|
||||
#import "LOTAnimatableValue.h"
|
||||
#import "LOTKeyframe.h"
|
||||
|
||||
@interface LOTAnimatablePointValue : NSObject <LOTAnimatableValue>
|
||||
|
||||
- (instancetype)initWithPointValues:(NSDictionary *)pointValues frameRate:(NSNumber *)frameRate;
|
||||
- (void)remapPointsFromBounds:(CGRect)frombounds toBounds:(CGRect)toBounds;
|
||||
|
||||
@property (nonatomic, readonly) CGPoint initialPoint;
|
||||
@property (nonatomic, assign) BOOL usePathAnimation;
|
||||
@property (nonatomic, readonly) LOTKeyframeGroup *keyframeGroup;
|
||||
|
||||
@end
|
||||
@ -1,255 +0,0 @@
|
||||
//
|
||||
// LOTAnimatablePointValue.m
|
||||
// LottieAnimator
|
||||
//
|
||||
// Created by brandon_withrow on 6/23/16.
|
||||
// Copyright © 2016 Brandon Withrow. All rights reserved.
|
||||
//
|
||||
|
||||
#import "LOTPlatformCompat.h"
|
||||
#import "LOTAnimatablePointValue.h"
|
||||
#import "LOTHelpers.h"
|
||||
|
||||
@interface LOTAnimatablePointValue ()
|
||||
|
||||
@property (nonatomic, readonly) UIBezierPath *animationPath;
|
||||
@property (nonatomic, readonly) NSArray<NSValue *> *pointKeyframes;
|
||||
@property (nonatomic, readonly) NSArray<NSNumber *> *keyTimes;
|
||||
@property (nonatomic, readonly) NSArray<CAMediaTimingFunction *> *timingFunctions;
|
||||
@property (nonatomic, readonly) NSTimeInterval delay;
|
||||
@property (nonatomic, readonly) NSTimeInterval duration;
|
||||
@property (nonatomic, readonly) NSNumber *startFrame;
|
||||
@property (nonatomic, readonly) NSNumber *durationFrames;
|
||||
@property (nonatomic, readonly) NSNumber *frameRate;
|
||||
|
||||
@end
|
||||
|
||||
@implementation LOTAnimatablePointValue
|
||||
|
||||
- (instancetype)initWithPointValues:(NSDictionary *)pointValues frameRate:(NSNumber *)frameRate {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_usePathAnimation = YES;
|
||||
_frameRate = frameRate;
|
||||
NSArray *value = pointValues[@"k"];
|
||||
if (DEBUG_USE_NEW_RENDERER) {
|
||||
_keyframeGroup = [[LOTKeyframeGroup alloc] initWithData:value];
|
||||
}
|
||||
if ([value isKindOfClass:[NSArray class]] &&
|
||||
[[(NSArray *)value firstObject] isKindOfClass:[NSDictionary class]] &&
|
||||
[(NSArray *)value firstObject][@"t"]) {
|
||||
//Keframes
|
||||
[self _buildAnimationForKeyframes:value];
|
||||
} else {
|
||||
//Single Value, no animation
|
||||
|
||||
_initialPoint = [self _pointFromValueArray:value];
|
||||
}
|
||||
if (pointValues[@"x"]) {
|
||||
NSLog(@"%s: Warning: expressions are not supported", __PRETTY_FUNCTION__);
|
||||
}
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)_buildAnimationForKeyframes:(NSArray<NSDictionary *> *)keyframes {
|
||||
NSMutableArray *keyTimes = [NSMutableArray array];
|
||||
NSMutableArray *timingFunctions = [NSMutableArray array];
|
||||
UIBezierPath *motionPath = [UIBezierPath new];
|
||||
NSMutableArray *pointKeyframes = [NSMutableArray array];
|
||||
|
||||
_startFrame = keyframes.firstObject[@"t"];
|
||||
NSNumber *endFrame = keyframes.lastObject[@"t"];
|
||||
|
||||
NSAssert((_startFrame && endFrame && _startFrame.integerValue < endFrame.integerValue),
|
||||
@"Lottie: Keyframe animation has incorrect time values or invalid number of keyframes");
|
||||
|
||||
// Calculate time duration
|
||||
_durationFrames = @(endFrame.floatValue - _startFrame.floatValue);
|
||||
|
||||
_duration = _durationFrames.floatValue / _frameRate.floatValue;
|
||||
_delay = _startFrame.floatValue / _frameRate.floatValue;
|
||||
|
||||
BOOL addStartValue = YES;
|
||||
BOOL addTimePadding = NO;
|
||||
NSArray *outPoint = nil;
|
||||
|
||||
for (NSDictionary *keyframe in keyframes) {
|
||||
// Get keyframe time value
|
||||
NSNumber *frame = keyframe[@"t"];
|
||||
// Calculate percentage value for keyframe.
|
||||
//CA Animations accept time values of 0-1 as a percentage of animation completed.
|
||||
NSNumber *timePercentage = @((frame.floatValue - _startFrame.floatValue) / _durationFrames.floatValue);
|
||||
|
||||
if (outPoint) {
|
||||
//add out value
|
||||
CGPoint vertex = [self _pointFromValueArray:outPoint];
|
||||
[motionPath addLineToPoint:vertex];
|
||||
[pointKeyframes addObject:[NSValue valueWithCGPoint:vertex]];
|
||||
[timingFunctions addObject:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear]];
|
||||
outPoint = nil;
|
||||
}
|
||||
|
||||
NSArray *startPoint = keyframe[@"s"];
|
||||
if (addStartValue) {
|
||||
if (startPoint) {
|
||||
CGPoint sPoint = [self _pointFromValueArray:startPoint];
|
||||
if (keyframe == keyframes.firstObject) {
|
||||
[pointKeyframes addObject:[NSValue valueWithCGPoint:sPoint]];
|
||||
[motionPath moveToPoint:sPoint];
|
||||
_initialPoint = sPoint;
|
||||
} else {
|
||||
[motionPath addLineToPoint:sPoint];
|
||||
[pointKeyframes addObject:[NSValue valueWithCGPoint:sPoint]];
|
||||
[timingFunctions addObject:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear]];
|
||||
}
|
||||
addStartValue = NO;
|
||||
}
|
||||
}
|
||||
|
||||
if (addTimePadding) {
|
||||
// add time padding
|
||||
NSNumber *holdPercentage = @(timePercentage.floatValue - 0.00001);
|
||||
[keyTimes addObject:[holdPercentage copy]];
|
||||
addTimePadding = NO;
|
||||
}
|
||||
|
||||
// add end value if present for keyframe
|
||||
NSArray *endPoint = keyframe[@"e"];
|
||||
if (endPoint) {
|
||||
NSArray *controlPoint1 = keyframe[@"to"];
|
||||
NSArray *controlPoint2 = keyframe[@"ti"];
|
||||
CGPoint cp1 = CGPointZero;
|
||||
CGPoint cp2 = CGPointZero;
|
||||
CGPoint vertex = [self _pointFromValueArray:endPoint];
|
||||
[pointKeyframes addObject:[NSValue valueWithCGPoint:vertex]];
|
||||
|
||||
if (controlPoint1 && controlPoint2) {
|
||||
// Quadratic Spatial Interpolation
|
||||
cp1 = [self _pointFromValueArray:controlPoint1];
|
||||
cp2 = [self _pointFromValueArray:controlPoint2];
|
||||
}
|
||||
if (CGPointEqualToPoint(cp1, CGPointZero) &&
|
||||
CGPointEqualToPoint(cp2, CGPointZero)) {
|
||||
// Linear Spatial Interpolation
|
||||
[motionPath addLineToPoint:vertex];
|
||||
} else {
|
||||
CGPoint inVertex = [self _pointFromValueArray:startPoint];
|
||||
[motionPath addCurveToPoint:vertex
|
||||
controlPoint1:LOT_PointAddedToPoint(inVertex, cp1)
|
||||
controlPoint2:LOT_PointAddedToPoint(vertex, cp2)];
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
* Timing Function for time interpolations between keyframes
|
||||
* Should be n-1 where n is the number of keyframes
|
||||
*/
|
||||
CAMediaTimingFunction *timingFunction;
|
||||
NSDictionary *timingControlPoint1 = keyframe[@"o"];
|
||||
NSDictionary *timingControlPoint2 = keyframe[@"i"];
|
||||
|
||||
if (timingControlPoint1 && timingControlPoint2) {
|
||||
// Easing function
|
||||
CGPoint cp1 = [self _pointFromValueDict:timingControlPoint1];
|
||||
CGPoint cp2 = [self _pointFromValueDict:timingControlPoint2];
|
||||
timingFunction = [CAMediaTimingFunction functionWithControlPoints:cp1.x :cp1.y :cp2.x :cp2.y];
|
||||
} else {
|
||||
// No easing function specified, fallback to linear
|
||||
timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
|
||||
}
|
||||
[timingFunctions addObject:timingFunction];
|
||||
}
|
||||
|
||||
// add time
|
||||
[keyTimes addObject:timePercentage];
|
||||
|
||||
// Check if keyframe is a hold keyframe
|
||||
if ([keyframe[@"h"] boolValue]) {
|
||||
// set out value as start and flag next frame accordinly
|
||||
outPoint = startPoint;
|
||||
addStartValue = YES;
|
||||
addTimePadding = YES;
|
||||
}
|
||||
}
|
||||
|
||||
_animationPath = motionPath;
|
||||
_keyTimes = keyTimes;
|
||||
_timingFunctions = timingFunctions;
|
||||
_pointKeyframes = pointKeyframes;
|
||||
}
|
||||
|
||||
- (void)remapPointsFromBounds:(CGRect)frombounds toBounds:(CGRect)toBounds {
|
||||
if (DEBUG_USE_NEW_RENDERER) {
|
||||
return;
|
||||
}
|
||||
if (_pointKeyframes.count) {
|
||||
NSMutableArray *newValues = [NSMutableArray array];
|
||||
for (NSValue *pointValue in _pointKeyframes) {
|
||||
CGPoint oldPoint = pointValue.CGPointValue;
|
||||
CGPoint newPoint = CGPointMake(LOT_RemapValue(oldPoint.x, frombounds.origin.x, frombounds.size.width, toBounds.origin.x, toBounds.size.width),
|
||||
LOT_RemapValue(oldPoint.y, frombounds.origin.y, frombounds.size.height, toBounds.origin.y, toBounds.size.height));
|
||||
[newValues addObject:[NSValue valueWithCGPoint:newPoint]];
|
||||
}
|
||||
NSValue *firstPoint = newValues.firstObject;
|
||||
_initialPoint = firstPoint.CGPointValue;
|
||||
_pointKeyframes = newValues;
|
||||
_animationPath = nil;
|
||||
} else {
|
||||
CGPoint newPoint = CGPointMake(LOT_RemapValue(_initialPoint.x, frombounds.origin.x, frombounds.size.width, toBounds.origin.x, toBounds.size.width),
|
||||
LOT_RemapValue(_initialPoint.y, frombounds.origin.y, frombounds.size.height, toBounds.origin.y, toBounds.size.height));
|
||||
_initialPoint = newPoint;
|
||||
}
|
||||
}
|
||||
|
||||
- (CGPoint)_pointFromValueArray:(NSArray<NSNumber *> *)values {
|
||||
if (values.count >= 2) {
|
||||
return CGPointMake([values[0] floatValue], [values[1] floatValue]);
|
||||
}
|
||||
return CGPointZero;
|
||||
}
|
||||
|
||||
- (CGPoint)_pointFromValueDict:(NSDictionary *)values {
|
||||
NSNumber *xValue = @0, *yValue = @0;
|
||||
if ([values[@"x"] isKindOfClass:[NSNumber class]]) {
|
||||
xValue = values[@"x"];
|
||||
} else if ([values[@"x"] isKindOfClass:[NSArray class]]) {
|
||||
xValue = values[@"x"][0];
|
||||
}
|
||||
|
||||
if ([values[@"y"] isKindOfClass:[NSNumber class]]) {
|
||||
yValue = values[@"y"];
|
||||
} else if ([values[@"y"] isKindOfClass:[NSArray class]]) {
|
||||
yValue = values[@"y"][0];
|
||||
}
|
||||
|
||||
return CGPointMake([xValue floatValue], [yValue floatValue]);
|
||||
}
|
||||
|
||||
- (BOOL)hasAnimation {
|
||||
return (self.animationPath != nil || self.pointKeyframes.count > 0);
|
||||
}
|
||||
|
||||
- (nullable CAKeyframeAnimation *)animationForKeyPath:(nonnull NSString *)keypath {
|
||||
if (self.hasAnimation == NO) {
|
||||
return nil;
|
||||
}
|
||||
CAKeyframeAnimation *keyframeAnimation = [CAKeyframeAnimation animationWithKeyPath:keypath];
|
||||
keyframeAnimation.keyTimes = self.keyTimes;
|
||||
if (self.animationPath && self.usePathAnimation) {
|
||||
keyframeAnimation.path = self.animationPath.CGPath;
|
||||
} else {
|
||||
keyframeAnimation.values = self.pointKeyframes;
|
||||
}
|
||||
keyframeAnimation.timingFunctions = self.timingFunctions;
|
||||
keyframeAnimation.duration = self.duration;
|
||||
keyframeAnimation.beginTime = self.delay;
|
||||
keyframeAnimation.fillMode = kCAFillModeForwards;
|
||||
return keyframeAnimation;
|
||||
}
|
||||
|
||||
- (NSString *)description {
|
||||
return NSStringFromCGPoint(self.initialPoint);
|
||||
}
|
||||
|
||||
@end
|
||||
@ -1,20 +0,0 @@
|
||||
//
|
||||
// LOTAnimatableScaleValue.h
|
||||
// LottieAnimator
|
||||
//
|
||||
// Created by brandon_withrow on 7/11/16.
|
||||
// Copyright © 2016 Brandon Withrow. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "LOTAnimatableValue.h"
|
||||
#import "LOTKeyframe.h"
|
||||
|
||||
@interface LOTAnimatableScaleValue : NSObject <LOTAnimatableValue>
|
||||
|
||||
- (instancetype)initWithScaleValues:(NSDictionary *)scaleValues frameRate:(NSNumber *)frameRate;
|
||||
|
||||
@property (nonatomic, readonly) CATransform3D initialScale;
|
||||
@property (nonatomic, readonly) LOTKeyframeGroup *keyframeGroup;
|
||||
|
||||
@end
|
||||
@ -1,189 +0,0 @@
|
||||
//
|
||||
// LOTAnimatableScaleValue.m
|
||||
// LottieAnimator
|
||||
//
|
||||
// Created by brandon_withrow on 7/11/16.
|
||||
// Copyright © 2016 Brandon Withrow. All rights reserved.
|
||||
//
|
||||
|
||||
#import "LOTAnimatableScaleValue.h"
|
||||
#import "LOTHelpers.h"
|
||||
@interface LOTAnimatableScaleValue ()
|
||||
|
||||
@property (nonatomic, readonly) NSArray<NSValue *> *scaleKeyframes;
|
||||
@property (nonatomic, readonly) NSArray<NSNumber *> *keyTimes;
|
||||
@property (nonatomic, readonly) NSArray<CAMediaTimingFunction *> *timingFunctions;
|
||||
@property (nonatomic, readonly) NSTimeInterval delay;
|
||||
@property (nonatomic, readonly) NSTimeInterval duration;
|
||||
@property (nonatomic, readonly) BOOL hasAnimation;
|
||||
@property (nonatomic, readonly) NSNumber *startFrame;
|
||||
@property (nonatomic, readonly) NSNumber *durationFrames;
|
||||
@property (nonatomic, readonly) NSNumber *frameRate;
|
||||
|
||||
@end
|
||||
|
||||
@implementation LOTAnimatableScaleValue
|
||||
|
||||
- (instancetype)initWithScaleValues:(NSDictionary *)scaleValues frameRate:(NSNumber *)frameRate {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_frameRate = frameRate;
|
||||
id value = scaleValues[@"k"];
|
||||
if (DEBUG_USE_NEW_RENDERER) {
|
||||
_keyframeGroup = [[LOTKeyframeGroup alloc] initWithData:value];
|
||||
}
|
||||
if ([value isKindOfClass:[NSArray class]] &&
|
||||
[[(NSArray *)value firstObject] isKindOfClass:[NSDictionary class]] &&
|
||||
[(NSArray *)value firstObject][@"t"]) {
|
||||
//Keframes
|
||||
[self _buildAnimationForKeyframes:value];
|
||||
} else {
|
||||
//Single Value, no animation
|
||||
_initialScale = [self _xformForValueArray:value];
|
||||
}
|
||||
if (scaleValues[@"x"]) {
|
||||
NSLog(@"%s: Warning: expressions are not supported", __PRETTY_FUNCTION__);
|
||||
}
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)_buildAnimationForKeyframes:(NSArray<NSDictionary *> *)keyframes {
|
||||
NSMutableArray *keyTimes = [NSMutableArray array];
|
||||
NSMutableArray *timingFunctions = [NSMutableArray array];
|
||||
NSMutableArray<NSValue *> *scaleValues = [NSMutableArray array];
|
||||
|
||||
_startFrame = keyframes.firstObject[@"t"];
|
||||
NSNumber *endFrame = keyframes.lastObject[@"t"];
|
||||
|
||||
NSAssert((_startFrame && endFrame && _startFrame.integerValue < endFrame.integerValue),
|
||||
@"Lottie: Keyframe animation has incorrect time values or invalid number of keyframes");
|
||||
// Calculate time duration
|
||||
_durationFrames = @(endFrame.floatValue - _startFrame.floatValue);
|
||||
|
||||
_duration = _durationFrames.floatValue / _frameRate.floatValue;
|
||||
_delay = _startFrame.floatValue / _frameRate.floatValue;
|
||||
|
||||
BOOL addStartValue = YES;
|
||||
BOOL addTimePadding = NO;
|
||||
NSArray *outValue = nil;
|
||||
|
||||
for (NSDictionary *keyframe in keyframes) {
|
||||
// Get keyframe time value
|
||||
NSNumber *frame = keyframe[@"t"];
|
||||
// Calculate percentage value for keyframe.
|
||||
//CA Animations accept time values of 0-1 as a percentage of animation completed.
|
||||
NSNumber *timePercentage = @((frame.floatValue - _startFrame.floatValue) / _durationFrames.floatValue);
|
||||
|
||||
if (outValue) {
|
||||
//add out value
|
||||
[scaleValues addObject:[NSValue valueWithCATransform3D:[self _xformForValueArray:outValue]]];
|
||||
[timingFunctions addObject:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear]];
|
||||
outValue = nil;
|
||||
}
|
||||
|
||||
NSArray *startValue = keyframe[@"s"];
|
||||
if (addStartValue) {
|
||||
// Add start value
|
||||
if (startValue) {
|
||||
if (keyframe == keyframes.firstObject) {
|
||||
_initialScale = [self _xformForValueArray:startValue];
|
||||
}
|
||||
[scaleValues addObject:[NSValue valueWithCATransform3D:[self _xformForValueArray:startValue]]];
|
||||
if (timingFunctions.count) {
|
||||
[timingFunctions addObject:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear]];
|
||||
}
|
||||
}
|
||||
addStartValue = NO;
|
||||
}
|
||||
|
||||
if (addTimePadding) {
|
||||
// add time padding
|
||||
NSNumber *holdPercentage = @(timePercentage.floatValue - 0.00001);
|
||||
[keyTimes addObject:[holdPercentage copy]];
|
||||
addTimePadding = NO;
|
||||
}
|
||||
|
||||
// add end value if present for keyframe
|
||||
NSArray *endValue = keyframe[@"e"];
|
||||
if (endValue) {
|
||||
[scaleValues addObject:[NSValue valueWithCATransform3D:[self _xformForValueArray:endValue]]];
|
||||
/*
|
||||
* Timing Function for time interpolations between keyframes
|
||||
* Should be n-1 where n is the number of keyframes
|
||||
*/
|
||||
CAMediaTimingFunction *timingFunction;
|
||||
NSDictionary *timingControlPoint1 = keyframe[@"o"];
|
||||
NSDictionary *timingControlPoint2 = keyframe[@"i"];
|
||||
|
||||
if (timingControlPoint1 && timingControlPoint2) {
|
||||
// Easing function
|
||||
CGPoint cp1 = [self _pointFromValueDict:timingControlPoint1];
|
||||
CGPoint cp2 = [self _pointFromValueDict:timingControlPoint2];
|
||||
timingFunction = [CAMediaTimingFunction functionWithControlPoints:cp1.x :cp1.y :cp2.x :cp2.y];
|
||||
} else {
|
||||
// No easing function specified, fallback to linear
|
||||
timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
|
||||
}
|
||||
[timingFunctions addObject:timingFunction];
|
||||
}
|
||||
|
||||
// add time
|
||||
[keyTimes addObject:timePercentage];
|
||||
|
||||
// Check if keyframe is a hold keyframe
|
||||
if ([keyframe[@"h"] boolValue]) {
|
||||
// set out value as start and flag next frame accordinly
|
||||
outValue = startValue;
|
||||
addStartValue = YES;
|
||||
addTimePadding = YES;
|
||||
}
|
||||
}
|
||||
_scaleKeyframes = scaleValues;
|
||||
_keyTimes = keyTimes;
|
||||
_timingFunctions = timingFunctions;
|
||||
}
|
||||
|
||||
- (CATransform3D)_xformForValueArray:(NSArray *)value {
|
||||
if (value.count >=2) {
|
||||
return CATransform3DMakeScale([(NSNumber *)value[0] floatValue] / 100.f, [(NSNumber *)value[1] floatValue] / 100.f, 1);
|
||||
}
|
||||
return CATransform3DIdentity;
|
||||
}
|
||||
|
||||
- (CGPoint)_pointFromValueDict:(NSDictionary *)values {
|
||||
NSNumber *xValue = @0, *yValue = @0;
|
||||
if ([values[@"x"] isKindOfClass:[NSNumber class]]) {
|
||||
xValue = values[@"x"];
|
||||
} else if ([values[@"x"] isKindOfClass:[NSArray class]]) {
|
||||
xValue = values[@"x"][0];
|
||||
}
|
||||
|
||||
if ([values[@"y"] isKindOfClass:[NSNumber class]]) {
|
||||
yValue = values[@"y"];
|
||||
} else if ([values[@"y"] isKindOfClass:[NSArray class]]) {
|
||||
yValue = values[@"y"][0];
|
||||
}
|
||||
|
||||
return CGPointMake([xValue floatValue], [yValue floatValue]);
|
||||
}
|
||||
|
||||
- (BOOL)hasAnimation {
|
||||
return (self.scaleKeyframes.count > 0);
|
||||
}
|
||||
|
||||
- (nullable CAKeyframeAnimation *)animationForKeyPath:(nonnull NSString *)keypath {
|
||||
if (self.hasAnimation == NO) {
|
||||
return nil;
|
||||
}
|
||||
CAKeyframeAnimation *keyframeAnimation = [CAKeyframeAnimation animationWithKeyPath:keypath];
|
||||
keyframeAnimation.keyTimes = self.keyTimes;
|
||||
keyframeAnimation.values = self.scaleKeyframes;
|
||||
keyframeAnimation.timingFunctions = self.timingFunctions;
|
||||
keyframeAnimation.duration = self.duration;
|
||||
keyframeAnimation.beginTime = self.delay;
|
||||
keyframeAnimation.fillMode = kCAFillModeForwards;
|
||||
return keyframeAnimation;
|
||||
}
|
||||
|
||||
@end
|
||||
@ -1,21 +0,0 @@
|
||||
//
|
||||
// LOTAnimatableShapeValue.h
|
||||
// LottieAnimator
|
||||
//
|
||||
// Created by brandon_withrow on 6/23/16.
|
||||
// Copyright © 2016 Brandon Withrow. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "LOTPlatformCompat.h"
|
||||
#import "LOTAnimatableValue.h"
|
||||
#import "LOTKeyframe.h"
|
||||
|
||||
@interface LOTAnimatableShapeValue : NSObject <LOTAnimatableValue>
|
||||
|
||||
- (instancetype)initWithShapeValues:(NSDictionary *)shapeValues frameRate:(NSNumber *)frameRate closed:(BOOL)closed;
|
||||
|
||||
@property (nonatomic, readonly) UIBezierPath *initialShape;
|
||||
@property (nonatomic, readonly) LOTKeyframeGroup *keyframeGroup;
|
||||
|
||||
@end
|
||||
@ -1,316 +0,0 @@
|
||||
//
|
||||
// LOTAnimatableShapeValue.m
|
||||
// LottieAnimator
|
||||
//
|
||||
// Created by brandon_withrow on 6/23/16.
|
||||
// Copyright © 2016 Brandon Withrow. All rights reserved.
|
||||
//
|
||||
|
||||
#import "LOTAnimatableShapeValue.h"
|
||||
#import "CGGeometry+LOTAdditions.h"
|
||||
#import "LOTHelpers.h"
|
||||
|
||||
@interface LOTAnimatableShapeValue ()
|
||||
|
||||
@property (nonatomic, readonly) NSArray *shapeKeyframes;
|
||||
@property (nonatomic, readonly) NSArray<NSNumber *> *keyTimes;
|
||||
@property (nonatomic, readonly) NSArray<CAMediaTimingFunction *> *timingFunctions;
|
||||
@property (nonatomic, readonly) NSTimeInterval delay;
|
||||
@property (nonatomic, readonly) NSTimeInterval duration;
|
||||
@property (nonatomic, readonly) NSNumber *startFrame;
|
||||
@property (nonatomic, readonly) NSNumber *durationFrames;
|
||||
@property (nonatomic, readonly) NSNumber *frameRate;
|
||||
|
||||
@end
|
||||
|
||||
@implementation LOTAnimatableShapeValue
|
||||
|
||||
- (instancetype)initWithShapeValues:(NSDictionary *)shapeValues frameRate:(NSNumber *)frameRate closed:(BOOL)closed {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_frameRate = frameRate;
|
||||
id value = shapeValues[@"k"];
|
||||
if (DEBUG_USE_NEW_RENDERER) {
|
||||
_keyframeGroup = [[LOTKeyframeGroup alloc] initWithData:value];
|
||||
}
|
||||
if ([value isKindOfClass:[NSArray class]] &&
|
||||
[[(NSArray *)value firstObject] isKindOfClass:[NSDictionary class]] &&
|
||||
[(NSArray *)value firstObject][@"t"]) {
|
||||
//Keframes
|
||||
NSDictionary *first = [(NSArray *)value firstObject];
|
||||
NSDictionary *start = ([first[@"s"] isKindOfClass:[NSDictionary class]] ?
|
||||
first[@"s"] :
|
||||
[(NSArray *)first[@"s"] firstObject]);
|
||||
|
||||
if ([(start[@"c"]) isKindOfClass:[NSNumber class]]) {
|
||||
closed = [(start[@"c"]) boolValue];
|
||||
}
|
||||
|
||||
[self _buildAnimationForKeyframes:value closed:closed];
|
||||
} else if ([value isKindOfClass:[NSDictionary class]]) {
|
||||
//Single Value, no animation
|
||||
if ([value[@"c"] isKindOfClass:[NSNumber class]]) {
|
||||
closed = [value[@"c"] boolValue];
|
||||
}
|
||||
_initialShape = [self _bezierShapeFromValue:value closed:closed];
|
||||
}
|
||||
if (shapeValues[@"x"]) {
|
||||
NSLog(@"%s: Warning: expressions are not supported", __PRETTY_FUNCTION__);
|
||||
}
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)_buildAnimationForKeyframes:(NSArray<NSDictionary *> *)keyframes closed:(BOOL)closed {
|
||||
NSMutableArray *keyTimes = [NSMutableArray array];
|
||||
NSMutableArray *timingFunctions = [NSMutableArray array];
|
||||
NSMutableArray *shapeValues = [NSMutableArray array];
|
||||
|
||||
_startFrame = keyframes.firstObject[@"t"];
|
||||
NSNumber *endFrame = keyframes.lastObject[@"t"];
|
||||
|
||||
NSAssert((_startFrame && endFrame && _startFrame.integerValue < endFrame.integerValue),
|
||||
@"Lottie: Keyframe animation has incorrect time values or invalid number of keyframes");
|
||||
// Calculate time duration
|
||||
_durationFrames = @(endFrame.floatValue - _startFrame.floatValue);
|
||||
|
||||
_duration = _durationFrames.floatValue / _frameRate.floatValue;
|
||||
_delay = _startFrame.floatValue / _frameRate.floatValue;
|
||||
|
||||
BOOL addStartValue = YES;
|
||||
BOOL addTimePadding = NO;
|
||||
NSDictionary *outShape = nil;
|
||||
|
||||
for (NSDictionary *keyframe in keyframes) {
|
||||
// Get keyframe time value
|
||||
NSNumber *frame = keyframe[@"t"];
|
||||
// Calculate percentage value for keyframe.
|
||||
//CA Animations accept time values of 0-1 as a percentage of animation completed.
|
||||
NSNumber *timePercentage = @((frame.floatValue - _startFrame.floatValue) / _durationFrames.floatValue);
|
||||
|
||||
if (outShape) {
|
||||
//add out value
|
||||
[shapeValues addObject:(id)[[self _bezierShapeFromValue:outShape closed:closed] CGPath]];
|
||||
[timingFunctions addObject:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear]];
|
||||
outShape = nil;
|
||||
}
|
||||
|
||||
NSDictionary *startShape = keyframe[@"s"];
|
||||
if (addStartValue) {
|
||||
// Add start value
|
||||
if (startShape) {
|
||||
if (keyframe == keyframes.firstObject) {
|
||||
_initialShape = [self _bezierShapeFromValue:startShape closed:closed];
|
||||
}
|
||||
|
||||
[shapeValues addObject:(id)[[self _bezierShapeFromValue:startShape closed:closed] CGPath]];
|
||||
if (timingFunctions.count) {
|
||||
[timingFunctions addObject:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear]];
|
||||
}
|
||||
}
|
||||
addStartValue = NO;
|
||||
}
|
||||
|
||||
if (addTimePadding) {
|
||||
// add time padding
|
||||
NSNumber *holdPercentage = @(timePercentage.floatValue - 0.00001);
|
||||
[keyTimes addObject:[holdPercentage copy]];
|
||||
addTimePadding = NO;
|
||||
}
|
||||
|
||||
// add end value if present for keyframe
|
||||
NSDictionary *endShape = keyframe[@"e"];
|
||||
if (endShape) {
|
||||
[shapeValues addObject:(id)[[self _bezierShapeFromValue:endShape closed:closed] CGPath]];
|
||||
/*
|
||||
* Timing Function for time interpolations between keyframes
|
||||
* Should be n-1 where n is the number of keyframes
|
||||
*/
|
||||
CAMediaTimingFunction *timingFunction;
|
||||
NSDictionary *timingControlPoint1 = keyframe[@"o"];
|
||||
NSDictionary *timingControlPoint2 = keyframe[@"i"];
|
||||
|
||||
if (timingControlPoint1 && timingControlPoint2) {
|
||||
// Easing function
|
||||
CGPoint cp1 = [self _pointFromValueDict:timingControlPoint1];
|
||||
CGPoint cp2 = [self _pointFromValueDict:timingControlPoint2];
|
||||
timingFunction = [CAMediaTimingFunction functionWithControlPoints:cp1.x :cp1.y :cp2.x :cp2.y];
|
||||
} else {
|
||||
// No easing function specified, fallback to linear
|
||||
timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
|
||||
}
|
||||
[timingFunctions addObject:timingFunction];
|
||||
}
|
||||
|
||||
// add time
|
||||
[keyTimes addObject:timePercentage];
|
||||
|
||||
// Check if keyframe is a hold keyframe
|
||||
if ([keyframe[@"h"] boolValue]) {
|
||||
// set out value as start and flag next frame accordinly
|
||||
outShape = startShape;
|
||||
addStartValue = YES;
|
||||
addTimePadding = YES;
|
||||
}
|
||||
}
|
||||
|
||||
_shapeKeyframes = shapeValues;
|
||||
_keyTimes = keyTimes;
|
||||
_timingFunctions = timingFunctions;
|
||||
}
|
||||
|
||||
- (UIBezierPath *)_bezierShapeFromValue:(id)value closed:(BOOL)closedPath {
|
||||
NSDictionary *pointsData = nil;
|
||||
if ([value isKindOfClass:[NSArray class]] &&
|
||||
[[(NSArray *)value firstObject] isKindOfClass:[NSDictionary class]] &&
|
||||
[(NSDictionary *)[(NSArray *)value firstObject] objectForKey:@"v"]) {
|
||||
pointsData = [(NSArray *)value firstObject];
|
||||
} else if ([value isKindOfClass:[NSDictionary class]] &&
|
||||
[(NSDictionary *)value objectForKey:@"v"]) {
|
||||
pointsData = value;
|
||||
}
|
||||
if (!pointsData) {
|
||||
return nil;
|
||||
}
|
||||
NSArray *pointsArray = pointsData[@"v"];
|
||||
NSArray *inTangents = pointsData[@"i"];
|
||||
NSArray *outTangents = pointsData[@"o"];
|
||||
|
||||
if (pointsArray.count == 0) {
|
||||
NSLog(@"%s: Warning: shape has no vertices", __PRETTY_FUNCTION__);
|
||||
return nil;
|
||||
}
|
||||
|
||||
NSAssert((pointsArray.count == inTangents.count &&
|
||||
pointsArray.count == outTangents.count),
|
||||
@"Lottie: Incorrect number of points and tangents");
|
||||
|
||||
UIBezierPath *shape = [UIBezierPath bezierPath];
|
||||
|
||||
[shape moveToPoint:[self _vertexAtIndex:0 inArray:pointsArray]];
|
||||
|
||||
for (int i = 1; i < pointsArray.count; i ++) {
|
||||
CGPoint vertex = [self _vertexAtIndex:i inArray:pointsArray];
|
||||
CGPoint previousVertex = [self _vertexAtIndex:i - 1 inArray:pointsArray];
|
||||
CGPoint cp1 = LOT_PointAddedToPoint(previousVertex, [self _vertexAtIndex:i - 1 inArray:outTangents]);
|
||||
CGPoint cp2 = LOT_PointAddedToPoint(vertex, [self _vertexAtIndex:i inArray:inTangents]);
|
||||
|
||||
if (CGPointEqualToPoint(previousVertex, cp1) &&
|
||||
CGPointEqualToPoint(vertex, cp2)) {
|
||||
// Straight Line
|
||||
[shape addLineToPoint:vertex];
|
||||
} else {
|
||||
if (CGPointEqualToPoint(previousVertex, cp1)) {
|
||||
// Missing out tan
|
||||
cp1 = LOT_PointByLerpingPoints(previousVertex, cp2, 0.01);
|
||||
}
|
||||
|
||||
if (CGPointEqualToPoint(vertex, cp2)) {
|
||||
// Missing in tan
|
||||
cp2 = LOT_PointByLerpingPoints(cp1, vertex, 0.99);
|
||||
}
|
||||
[shape addCurveToPoint:vertex
|
||||
controlPoint1:cp1
|
||||
controlPoint2:cp2];
|
||||
}
|
||||
}
|
||||
|
||||
if (closedPath) {
|
||||
CGPoint vertex = [self _vertexAtIndex:0 inArray:pointsArray];
|
||||
CGPoint previousVertex = [self _vertexAtIndex:pointsArray.count - 1 inArray:pointsArray];
|
||||
CGPoint cp1 = LOT_PointAddedToPoint(previousVertex, [self _vertexAtIndex:pointsArray.count - 1 inArray:outTangents]);
|
||||
CGPoint cp2 = LOT_PointAddedToPoint(vertex, [self _vertexAtIndex:0 inArray:inTangents]);
|
||||
if (CGPointEqualToPoint(previousVertex, cp1) &&
|
||||
CGPointEqualToPoint(vertex, cp2)) {
|
||||
// Straight Line
|
||||
cp1 = LOT_PointByLerpingPoints(previousVertex, vertex, 0.01);
|
||||
cp2 = LOT_PointByLerpingPoints(previousVertex, vertex, 0.99);
|
||||
} else {
|
||||
if (CGPointEqualToPoint(previousVertex, cp1)) {
|
||||
// Missing out tan
|
||||
cp1 = LOT_PointByLerpingPoints(previousVertex, cp2, 0.01);
|
||||
}
|
||||
|
||||
if (CGPointEqualToPoint(vertex, cp2)) {
|
||||
// Missing in tan
|
||||
cp2 = LOT_PointByLerpingPoints(cp1, vertex, 0.99);
|
||||
}
|
||||
}
|
||||
|
||||
[shape addCurveToPoint:vertex
|
||||
controlPoint1:cp1
|
||||
controlPoint2:cp2];
|
||||
[shape closePath];
|
||||
}
|
||||
|
||||
// Double up shape for trim offsets.
|
||||
// TODO Remove this heavy solution by refactoring the drawing tree.
|
||||
|
||||
[shape appendPath:[shape copy]];
|
||||
|
||||
return shape;
|
||||
}
|
||||
|
||||
- (CGPoint)_vertexAtIndex:(NSInteger)idx inArray:(NSArray *)points {
|
||||
NSAssert((idx < points.count),
|
||||
@"Lottie: Vertex Point out of bounds");
|
||||
|
||||
NSArray *pointArray = points[idx];
|
||||
|
||||
NSAssert((pointArray.count >= 2 &&
|
||||
[pointArray.firstObject isKindOfClass:[NSNumber class]]),
|
||||
@"Lottie: Point Data Malformed");
|
||||
|
||||
return CGPointMake([(NSNumber *)pointArray[0] floatValue], [(NSNumber *)pointArray[1] floatValue]);
|
||||
}
|
||||
|
||||
- (NSNumber *)_numberValueFromObject:(id)valueObject {
|
||||
if ([valueObject isKindOfClass:[NSNumber class]]) {
|
||||
return valueObject;
|
||||
}
|
||||
if ([valueObject isKindOfClass:[NSArray class]]) {
|
||||
return [(NSArray *)valueObject firstObject];
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (CGPoint)_pointFromValueDict:(NSDictionary *)values {
|
||||
NSNumber *xValue = @0, *yValue = @0;
|
||||
if ([values[@"x"] isKindOfClass:[NSNumber class]]) {
|
||||
xValue = values[@"x"];
|
||||
} else if ([values[@"x"] isKindOfClass:[NSArray class]]) {
|
||||
xValue = values[@"x"][0];
|
||||
}
|
||||
|
||||
if ([values[@"y"] isKindOfClass:[NSNumber class]]) {
|
||||
yValue = values[@"y"];
|
||||
} else if ([values[@"y"] isKindOfClass:[NSArray class]]) {
|
||||
yValue = values[@"y"][0];
|
||||
}
|
||||
|
||||
return CGPointMake([xValue floatValue], [yValue floatValue]);
|
||||
}
|
||||
|
||||
- (BOOL)hasAnimation {
|
||||
return (self.shapeKeyframes.count > 0);
|
||||
}
|
||||
|
||||
- (nullable CAKeyframeAnimation *)animationForKeyPath:(nonnull NSString *)keypath {
|
||||
if (self.hasAnimation == NO) {
|
||||
return nil;
|
||||
}
|
||||
CAKeyframeAnimation *keyframeAnimation = [CAKeyframeAnimation animationWithKeyPath:keypath];
|
||||
keyframeAnimation.keyTimes = self.keyTimes;
|
||||
keyframeAnimation.values = self.shapeKeyframes;
|
||||
keyframeAnimation.timingFunctions = self.timingFunctions;
|
||||
keyframeAnimation.duration = self.duration;
|
||||
keyframeAnimation.beginTime = self.delay;
|
||||
keyframeAnimation.fillMode = kCAFillModeForwards;
|
||||
return keyframeAnimation;
|
||||
}
|
||||
|
||||
- (NSString *)description {
|
||||
return self.initialShape.description;
|
||||
}
|
||||
|
||||
@end
|
||||
@ -1,18 +0,0 @@
|
||||
//
|
||||
// LOTAnimatableValue.h
|
||||
// LottieAnimator
|
||||
//
|
||||
// Created by brandon_withrow on 7/19/16.
|
||||
// Copyright © 2016 Brandon Withrow. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <QuartzCore/QuartzCore.h>
|
||||
|
||||
@protocol LOTAnimatableValue <NSObject>
|
||||
// BW TODO Make this a class and remove all of the various animatable classes.
|
||||
// The new LOTKeyframe and render system does away with all of the extra classes
|
||||
- (CAKeyframeAnimation *)animationForKeyPath:(NSString *)keypath;
|
||||
- (BOOL)hasAnimation;
|
||||
|
||||
@end
|
||||
@ -1,16 +0,0 @@
|
||||
//
|
||||
// CAAnimationGroup+LOTAnimatableGroup.h
|
||||
// LottieAnimator
|
||||
//
|
||||
// Created by brandon_withrow on 7/19/16.
|
||||
// Copyright © 2016 Brandon Withrow. All rights reserved.
|
||||
//
|
||||
|
||||
#import <QuartzCore/QuartzCore.h>
|
||||
#import "LOTAnimatableValue.h"
|
||||
|
||||
@interface CAAnimationGroup (LOTAnimatableGroup)
|
||||
|
||||
+ (nullable CAAnimationGroup *)LOT_animationGroupForAnimatablePropertiesWithKeyPaths:(nonnull NSDictionary<NSString *, id<LOTAnimatableValue>> *)properties;
|
||||
|
||||
@end
|
||||
@ -1,39 +0,0 @@
|
||||
//
|
||||
// CAAnimationGroup+LOTAnimatableGroup.m
|
||||
// LottieAnimator
|
||||
//
|
||||
// Created by brandon_withrow on 7/19/16.
|
||||
// Copyright © 2016 Brandon Withrow. All rights reserved.
|
||||
//
|
||||
|
||||
#import "CAAnimationGroup+LOTAnimatableGroup.h"
|
||||
|
||||
@implementation CAAnimationGroup (LOTAnimatableGroup)
|
||||
|
||||
+ (nullable CAAnimationGroup *)LOT_animationGroupForAnimatablePropertiesWithKeyPaths:(nonnull NSDictionary<NSString *, id<LOTAnimatableValue>> *)properties {
|
||||
NSMutableArray *animations = [NSMutableArray array];
|
||||
NSTimeInterval animduration = 0;
|
||||
for (NSString *keyPath in properties.allKeys) {
|
||||
id <LOTAnimatableValue>property = properties[keyPath];
|
||||
if ([property hasAnimation]) {
|
||||
CAKeyframeAnimation *animation = [property animationForKeyPath:keyPath];
|
||||
[animations addObject:animation];
|
||||
|
||||
if (animation.duration + animation.beginTime > animduration) {
|
||||
animduration = animation.duration + animation.beginTime;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (animations.count) {
|
||||
CAAnimationGroup *animation = [CAAnimationGroup new];
|
||||
animation.animations = animations;
|
||||
animation.duration = animduration;
|
||||
animation.fillMode = kCAFillModeForwards;
|
||||
animation.removedOnCompletion = NO;
|
||||
return animation;
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
@end
|
||||
@ -1,78 +0,0 @@
|
||||
// Kindly stolen from https://github.com/BigZaphod/Chameleon
|
||||
/*
|
||||
* Copyright (c) 2011, The Iconfactory. All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* 3. Neither the name of The Iconfactory nor the names of its contributors may
|
||||
* be used to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE ICONFACTORY BE LIABLE FOR ANY DIRECT,
|
||||
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
||||
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
|
||||
* OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
|
||||
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#if !TARGET_OS_IPHONE && !TARGET_IPHONE_SIMULATOR
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <CoreGraphics/CoreGraphics.h>
|
||||
|
||||
typedef NS_OPTIONS(NSUInteger, UIRectCorner) {
|
||||
UIRectCornerTopLeft = 1 << 0,
|
||||
UIRectCornerTopRight = 1 << 1,
|
||||
UIRectCornerBottomLeft = 1 << 2,
|
||||
UIRectCornerBottomRight = 1 << 3,
|
||||
UIRectCornerAllCorners = UIRectCornerTopLeft | UIRectCornerTopRight | UIRectCornerBottomLeft | UIRectCornerBottomRight
|
||||
};
|
||||
|
||||
@interface UIBezierPath : NSObject <NSCopying>
|
||||
|
||||
+ (UIBezierPath *)bezierPath;
|
||||
+ (UIBezierPath *)bezierPathWithRect:(CGRect)rect;
|
||||
+ (UIBezierPath *)bezierPathWithOvalInRect:(CGRect)rect;
|
||||
+ (UIBezierPath *)bezierPathWithRoundedRect:(CGRect)rect cornerRadius:(CGFloat)cornerRadius;
|
||||
+ (UIBezierPath *)bezierPathWithRoundedRect:(CGRect)rect byRoundingCorners:(UIRectCorner)corners cornerRadii:(CGSize)cornerRadii;
|
||||
+ (UIBezierPath *)bezierPathWithArcCenter:(CGPoint)center radius:(CGFloat)radius startAngle:(CGFloat)startAngle endAngle:(CGFloat)endAngle clockwise:(BOOL)clockwise;
|
||||
+ (UIBezierPath *)bezierPathWithCGPath:(CGPathRef)CGPath;
|
||||
|
||||
- (void)moveToPoint:(CGPoint)point;
|
||||
- (void)addLineToPoint:(CGPoint)point;
|
||||
- (void)addArcWithCenter:(CGPoint)center radius:(CGFloat)radius startAngle:(CGFloat)startAngle endAngle:(CGFloat)endAngle clockwise:(BOOL)clockwise;
|
||||
- (void)addCurveToPoint:(CGPoint)endPoint controlPoint1:(CGPoint)controlPoint1 controlPoint2:(CGPoint)controlPoint2;
|
||||
- (void)addQuadCurveToPoint:(CGPoint)endPoint controlPoint:(CGPoint)controlPoint;
|
||||
- (void)closePath;
|
||||
- (void)removeAllPoints;
|
||||
- (void)appendPath:(UIBezierPath *)bezierPath;
|
||||
- (void)setLineDash:(const CGFloat *)pattern count:(NSInteger)count phase:(CGFloat)phase;
|
||||
- (void)getLineDash:(CGFloat *)pattern count:(NSInteger *)count phase:(CGFloat *)phase;
|
||||
- (BOOL)containsPoint:(CGPoint)point;
|
||||
- (void)applyTransform:(CGAffineTransform)transform;
|
||||
|
||||
@property (nonatomic) CGPathRef CGPath;
|
||||
@property (nonatomic, readonly) CGPoint currentPoint;
|
||||
@property (nonatomic) CGFloat lineWidth;
|
||||
@property (nonatomic) CGLineCap lineCapStyle;
|
||||
@property (nonatomic) CGLineJoin lineJoinStyle;
|
||||
@property (nonatomic) CGFloat miterLimit;
|
||||
@property (nonatomic) CGFloat flatness;
|
||||
@property (nonatomic) BOOL usesEvenOddFillRule;
|
||||
@property (readonly, getter=isEmpty) BOOL empty;
|
||||
@property (nonatomic, readonly) CGRect bounds;
|
||||
@end
|
||||
|
||||
#endif
|
||||
@ -1,310 +0,0 @@
|
||||
// Kindly stolen from https://github.com/BigZaphod/Chameleon
|
||||
/*
|
||||
* Copyright (c) 2011, The Iconfactory. All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* 3. Neither the name of The Iconfactory nor the names of its contributors may
|
||||
* be used to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE ICONFACTORY BE LIABLE FOR ANY DIRECT,
|
||||
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
||||
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
|
||||
* OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
|
||||
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#if !TARGET_OS_IPHONE && !TARGET_IPHONE_SIMULATOR
|
||||
#import "UIBezierPath.h"
|
||||
|
||||
@implementation UIBezierPath {
|
||||
CGFloat *_lineDashPattern;
|
||||
NSInteger _lineDashCount;
|
||||
CGFloat _lineDashPhase;
|
||||
}
|
||||
@synthesize CGPath = _path;
|
||||
|
||||
- (id)init {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_path = CGPathCreateMutable();
|
||||
_lineWidth = 1;
|
||||
_lineCapStyle = kCGLineCapButt;
|
||||
_lineJoinStyle = kCGLineJoinMiter;
|
||||
_miterLimit = 10;
|
||||
_flatness = 0.6;
|
||||
_usesEvenOddFillRule = NO;
|
||||
_lineDashPattern = NULL;
|
||||
_lineDashCount = 0;
|
||||
_lineDashPhase = 0;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
if (_path) CGPathRelease(_path);
|
||||
}
|
||||
|
||||
- (id)copyWithZone:(NSZone *)zone {
|
||||
UIBezierPath *copy = [[self class] new];
|
||||
|
||||
copy.CGPath = self.CGPath;
|
||||
copy.lineWidth = self.lineWidth;
|
||||
copy.lineCapStyle = self.lineCapStyle;
|
||||
copy.lineJoinStyle = self.lineJoinStyle;
|
||||
copy.miterLimit = self.miterLimit;
|
||||
copy.flatness = self.flatness;
|
||||
copy.usesEvenOddFillRule = self.usesEvenOddFillRule;
|
||||
|
||||
NSInteger lineDashCount = 0;
|
||||
[self getLineDash:NULL count:&lineDashCount phase:NULL];
|
||||
|
||||
if (lineDashCount > 0) {
|
||||
CGFloat *lineDashPattern = malloc(sizeof(CGFloat) * lineDashCount);
|
||||
CGFloat lineDashPhase = 0;
|
||||
[self getLineDash:lineDashPattern count:NULL phase:&lineDashPhase];
|
||||
[copy setLineDash:lineDashPattern count:lineDashCount phase:lineDashPhase];
|
||||
free(lineDashPattern);
|
||||
}
|
||||
|
||||
return copy;
|
||||
}
|
||||
|
||||
+ (UIBezierPath *)bezierPathWithCGPath:(CGPathRef)CGPath {
|
||||
NSAssert(CGPath != NULL, @"CGPath must not be NULL");
|
||||
UIBezierPath *bezierPath = [[self alloc] init];
|
||||
bezierPath.CGPath = CGPath;
|
||||
return bezierPath;
|
||||
}
|
||||
|
||||
+ (UIBezierPath *)bezierPath {
|
||||
UIBezierPath *bezierPath = [[self alloc] init];
|
||||
return bezierPath;
|
||||
}
|
||||
|
||||
+ (UIBezierPath *)bezierPathWithRect:(CGRect)rect {
|
||||
CGMutablePathRef path = CGPathCreateMutable();
|
||||
CGPathAddRect(path, NULL, rect);
|
||||
|
||||
UIBezierPath *bezierPath = [[self alloc] init];
|
||||
bezierPath->_path = path;
|
||||
return bezierPath;
|
||||
}
|
||||
|
||||
+ (UIBezierPath *)bezierPathWithOvalInRect:(CGRect)rect {
|
||||
CGMutablePathRef path = CGPathCreateMutable();
|
||||
CGPathAddEllipseInRect(path, NULL, rect);
|
||||
|
||||
UIBezierPath *bezierPath = [[self alloc] init];
|
||||
bezierPath->_path = path;
|
||||
return bezierPath;
|
||||
}
|
||||
|
||||
+ (UIBezierPath *)bezierPathWithRoundedRect:(CGRect)rect
|
||||
cornerRadius:(CGFloat)cornerRadius {
|
||||
return [self bezierPathWithRoundedRect:rect
|
||||
byRoundingCorners:UIRectCornerAllCorners
|
||||
cornerRadii:CGSizeMake(cornerRadius, cornerRadius)];
|
||||
}
|
||||
|
||||
+ (UIBezierPath *)bezierPathWithRoundedRect:(CGRect)rect
|
||||
byRoundingCorners:(UIRectCorner)corners
|
||||
cornerRadii:(CGSize)cornerRadii {
|
||||
|
||||
CGMutablePathRef path = CGPathCreateMutable();
|
||||
|
||||
const CGPoint topLeft = rect.origin;
|
||||
const CGPoint topRight = CGPointMake(CGRectGetMaxX(rect), CGRectGetMinY(rect));
|
||||
const CGPoint bottomRight = CGPointMake(CGRectGetMaxX(rect), CGRectGetMaxY(rect));
|
||||
const CGPoint bottomLeft = CGPointMake(CGRectGetMinX(rect), CGRectGetMaxY(rect));
|
||||
|
||||
if (corners & UIRectCornerTopLeft) {
|
||||
CGPathMoveToPoint(path, NULL, topLeft.x + cornerRadii.width, topLeft.y);
|
||||
} else {
|
||||
CGPathMoveToPoint(path, NULL, topLeft.x, topLeft.y);
|
||||
}
|
||||
|
||||
if (corners & UIRectCornerTopRight) {
|
||||
CGPathAddLineToPoint(path, NULL, topRight.x - cornerRadii.width, topRight.y);
|
||||
CGPathAddCurveToPoint(path, NULL, topRight.x, topRight.y, topRight.x, topRight.y + cornerRadii.height, topRight.x, topRight.y + cornerRadii.height);
|
||||
} else {
|
||||
CGPathAddLineToPoint(path, NULL, topRight.x, topRight.y);
|
||||
}
|
||||
|
||||
if (corners & UIRectCornerBottomRight) {
|
||||
CGPathAddLineToPoint(path, NULL, bottomRight.x, bottomRight.y - cornerRadii.height);
|
||||
CGPathAddCurveToPoint(path, NULL, bottomRight.x, bottomRight.y, bottomRight.x - cornerRadii.width, bottomRight.y, bottomRight.x - cornerRadii.width, bottomRight.y);
|
||||
} else {
|
||||
CGPathAddLineToPoint(path, NULL, bottomRight.x, bottomRight.y);
|
||||
}
|
||||
|
||||
if (corners & UIRectCornerBottomLeft) {
|
||||
CGPathAddLineToPoint(path, NULL, bottomLeft.x + cornerRadii.width, bottomLeft.y);
|
||||
CGPathAddCurveToPoint(path, NULL, bottomLeft.x, bottomLeft.y, bottomLeft.x, bottomLeft.y - cornerRadii.height, bottomLeft.x, bottomLeft.y - cornerRadii.height);
|
||||
} else {
|
||||
CGPathAddLineToPoint(path, NULL, bottomLeft.x, bottomLeft.y);
|
||||
}
|
||||
|
||||
if (corners & UIRectCornerTopLeft) {
|
||||
CGPathAddLineToPoint(path, NULL, topLeft.x, topLeft.y + cornerRadii.height);
|
||||
CGPathAddCurveToPoint(path, NULL, topLeft.x, topLeft.y, topLeft.x + cornerRadii.width, topLeft.y, topLeft.x + cornerRadii.width, topLeft.y);
|
||||
} else {
|
||||
CGPathAddLineToPoint(path, NULL, topLeft.x, topLeft.y);
|
||||
}
|
||||
|
||||
CGPathCloseSubpath(path);
|
||||
|
||||
UIBezierPath *bezierPath = [[self alloc] init];
|
||||
bezierPath->_path = path;
|
||||
return bezierPath;
|
||||
}
|
||||
|
||||
+ (UIBezierPath *)bezierPathWithArcCenter:(CGPoint)center
|
||||
radius:(CGFloat)radius
|
||||
startAngle:(CGFloat)startAngle
|
||||
endAngle:(CGFloat)endAngle
|
||||
clockwise:(BOOL)clockwise {
|
||||
|
||||
CGMutablePathRef path = CGPathCreateMutable();
|
||||
CGPathAddArc(path, NULL, center.x, center.y, radius, startAngle, endAngle, clockwise);
|
||||
|
||||
UIBezierPath *bezierPath = [[self alloc] init];
|
||||
bezierPath->_path = path;
|
||||
return bezierPath;
|
||||
}
|
||||
|
||||
- (void)moveToPoint:(CGPoint)point {
|
||||
CGMutablePathRef mutablePath = CGPathCreateMutableCopy(_path);
|
||||
CGPathMoveToPoint(mutablePath, NULL, point.x, point.y);
|
||||
self.CGPath = mutablePath;
|
||||
CGPathRelease(mutablePath);
|
||||
}
|
||||
|
||||
- (void)addLineToPoint:(CGPoint)point {
|
||||
CGMutablePathRef mutablePath = CGPathCreateMutableCopy(_path);
|
||||
CGPathAddLineToPoint(mutablePath, NULL, point.x, point.y);
|
||||
self.CGPath = mutablePath;
|
||||
CGPathRelease(mutablePath);
|
||||
}
|
||||
|
||||
- (void)addArcWithCenter:(CGPoint)center radius:(CGFloat)radius startAngle:(CGFloat)startAngle endAngle:(CGFloat)endAngle clockwise:(BOOL)clockwise {
|
||||
CGMutablePathRef mutablePath = CGPathCreateMutableCopy(_path);
|
||||
CGPathAddArc(mutablePath, NULL, center.x, center.y, radius, startAngle, endAngle, clockwise);
|
||||
self.CGPath = mutablePath;
|
||||
CGPathRelease(mutablePath);
|
||||
}
|
||||
|
||||
- (void)addCurveToPoint:(CGPoint)endPoint controlPoint1:(CGPoint)controlPoint1 controlPoint2:(CGPoint)controlPoint2 {
|
||||
CGMutablePathRef mutablePath = CGPathCreateMutableCopy(_path);
|
||||
CGPathAddCurveToPoint(mutablePath, NULL, controlPoint1.x, controlPoint1.y, controlPoint2.x, controlPoint2.y, endPoint.x, endPoint.y);
|
||||
self.CGPath = mutablePath;
|
||||
CGPathRelease(mutablePath);
|
||||
}
|
||||
|
||||
- (void)addQuadCurveToPoint:(CGPoint)endPoint controlPoint:(CGPoint)controlPoint {
|
||||
CGMutablePathRef mutablePath = CGPathCreateMutableCopy(_path);
|
||||
CGPathAddQuadCurveToPoint(mutablePath, NULL, controlPoint.x, controlPoint.y, endPoint.x, endPoint.y);
|
||||
self.CGPath = mutablePath;
|
||||
CGPathRelease(mutablePath);
|
||||
}
|
||||
|
||||
- (void)closePath {
|
||||
CGMutablePathRef mutablePath = CGPathCreateMutableCopy(_path);
|
||||
CGPathCloseSubpath(mutablePath);
|
||||
self.CGPath = mutablePath;
|
||||
CGPathRelease(mutablePath);
|
||||
}
|
||||
|
||||
- (void)removeAllPoints {
|
||||
CGMutablePathRef mutablePath = CGPathCreateMutable();
|
||||
self.CGPath = mutablePath;
|
||||
CGPathRelease(mutablePath);
|
||||
}
|
||||
|
||||
- (void)appendPath:(UIBezierPath *)bezierPath {
|
||||
if (bezierPath) {
|
||||
CGMutablePathRef mutablePath = CGPathCreateMutableCopy(_path);
|
||||
CGPathAddPath(mutablePath, NULL, bezierPath.CGPath);
|
||||
self.CGPath = mutablePath;
|
||||
CGPathRelease(mutablePath);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setCGPath:(CGPathRef)path {
|
||||
NSAssert(path != NULL, @"path must not be NULL");
|
||||
if (path != _path) {
|
||||
if (_path) CGPathRelease(_path);
|
||||
_path = CGPathCreateCopy(path);
|
||||
}
|
||||
}
|
||||
|
||||
- (CGPoint)currentPoint {
|
||||
return CGPathGetCurrentPoint(_path);
|
||||
}
|
||||
|
||||
- (void)setLineDash:(const CGFloat *)pattern count:(NSInteger)count phase:(CGFloat)phase {
|
||||
free(_lineDashPattern);
|
||||
|
||||
if (pattern && count > 0) {
|
||||
const size_t size = sizeof(CGFloat) * count;
|
||||
_lineDashPattern = malloc(size);
|
||||
bcopy(pattern, _lineDashPattern, size);
|
||||
} else {
|
||||
_lineDashPattern = NULL;
|
||||
}
|
||||
|
||||
_lineDashCount = count;
|
||||
_lineDashPhase = phase;
|
||||
}
|
||||
|
||||
- (void)getLineDash:(CGFloat *)pattern count:(NSInteger *)count phase:(CGFloat *)phase {
|
||||
if (pattern && _lineDashPattern && _lineDashCount > 0) {
|
||||
const size_t size = sizeof(CGFloat) * _lineDashCount;
|
||||
bcopy(_lineDashPattern, pattern, size);
|
||||
}
|
||||
|
||||
if (count) {
|
||||
*count = _lineDashCount;
|
||||
}
|
||||
|
||||
if (phase) {
|
||||
*phase = _lineDashPhase;
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)containsPoint:(CGPoint)point {
|
||||
return CGPathContainsPoint(_path, NULL, point, _usesEvenOddFillRule);
|
||||
}
|
||||
|
||||
- (BOOL)isEmpty {
|
||||
return CGPathIsEmpty(_path);
|
||||
}
|
||||
|
||||
- (CGRect)bounds {
|
||||
return CGPathGetBoundingBox(_path);
|
||||
}
|
||||
|
||||
- (void)applyTransform:(CGAffineTransform)transform {
|
||||
CGMutablePathRef mutablePath = CGPathCreateMutable();
|
||||
CGPathAddPath(mutablePath, &transform, _path);
|
||||
self.CGPath = mutablePath;
|
||||
CGPathRelease(mutablePath);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#endif
|
||||
66
lottie-ios/Classes/Private/LOTComposition.m
Normal file
66
lottie-ios/Classes/Private/LOTComposition.m
Normal file
@ -0,0 +1,66 @@
|
||||
//
|
||||
// LOTScene.m
|
||||
// LottieAnimator
|
||||
//
|
||||
// Created by Brandon Withrow on 12/14/15.
|
||||
// Copyright © 2015 Brandon Withrow. All rights reserved.
|
||||
//
|
||||
|
||||
#import "LOTComposition.h"
|
||||
#import "LOTLayer.h"
|
||||
#import "LOTAssetGroup.h"
|
||||
#import "LOTLayerGroup.h"
|
||||
|
||||
@implementation LOTComposition
|
||||
|
||||
- (instancetype)initWithJSON:(NSDictionary *)jsonDictionary
|
||||
withAssetBundle:(NSBundle *)bundle {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
[self _mapFromJSON:jsonDictionary withAssetBundle:bundle];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)_mapFromJSON:(NSDictionary *)jsonDictionary
|
||||
withAssetBundle:(NSBundle *)bundle {
|
||||
NSNumber *width = jsonDictionary[@"w"];
|
||||
NSNumber *height = jsonDictionary[@"h"];
|
||||
if (width && height) {
|
||||
CGRect bounds = CGRectMake(0, 0, width.floatValue, height.floatValue);
|
||||
_compBounds = bounds;
|
||||
}
|
||||
|
||||
_startFrame = [jsonDictionary[@"ip"] copy];
|
||||
_endFrame = [jsonDictionary[@"op"] copy];
|
||||
_framerate = [jsonDictionary[@"fr"] copy];
|
||||
|
||||
if (_startFrame && _endFrame && _framerate) {
|
||||
NSInteger frameDuration = (_endFrame.integerValue - _startFrame.integerValue) - 1;
|
||||
NSTimeInterval timeDuration = frameDuration / _framerate.floatValue;
|
||||
_timeDuration = timeDuration;
|
||||
}
|
||||
|
||||
NSArray *assetArray = jsonDictionary[@"assets"];
|
||||
if (assetArray.count) {
|
||||
_assetGroup = [[LOTAssetGroup alloc] initWithJSON:assetArray withAssetBundle:bundle];
|
||||
}
|
||||
|
||||
NSArray *layersJSON = jsonDictionary[@"layers"];
|
||||
if (layersJSON) {
|
||||
_layerGroup = [[LOTLayerGroup alloc] initWithLayerJSON:layersJSON
|
||||
withBounds:_compBounds
|
||||
withFramerate:_framerate
|
||||
withAssetGroup:_assetGroup];
|
||||
}
|
||||
|
||||
[_assetGroup finalizeInitialization];
|
||||
|
||||
}
|
||||
|
||||
- (void)setRootDirectory:(NSString *)rootDirectory {
|
||||
_rootDirectory = rootDirectory;
|
||||
self.assetGroup.rootDirectory = rootDirectory;
|
||||
}
|
||||
|
||||
@end
|
||||
31
lottie-ios/Classes/PublicHeaders/LOTComposition.h
Normal file
31
lottie-ios/Classes/PublicHeaders/LOTComposition.h
Normal file
@ -0,0 +1,31 @@
|
||||
//
|
||||
// LOTScene.h
|
||||
// LottieAnimator
|
||||
//
|
||||
// Created by Brandon Withrow on 12/14/15.
|
||||
// Copyright © 2015 Brandon Withrow. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <CoreGraphics/CoreGraphics.h>
|
||||
|
||||
@class LOTLayerGroup;
|
||||
@class LOTLayer;
|
||||
@class LOTAssetGroup;
|
||||
|
||||
@interface LOTComposition : NSObject
|
||||
|
||||
- (instancetype)initWithJSON:(NSDictionary *)jsonDictionary
|
||||
withAssetBundle:(NSBundle *)bundle;
|
||||
|
||||
@property (nonatomic, readonly) CGRect compBounds;
|
||||
@property (nonatomic, readonly) NSNumber *startFrame;
|
||||
@property (nonatomic, readonly) NSNumber *endFrame;
|
||||
@property (nonatomic, readonly) NSNumber *framerate;
|
||||
@property (nonatomic, readonly) NSTimeInterval timeDuration;
|
||||
@property (nonatomic, readonly) LOTLayerGroup *layerGroup;
|
||||
@property (nonatomic, readonly) LOTAssetGroup *assetGroup;
|
||||
@property (nonatomic, readwrite) NSString *rootDirectory;
|
||||
@property (nonatomic, readonly) NSBundle *assetBundle;
|
||||
|
||||
@end
|
||||
Loading…
x
Reference in New Issue
Block a user