Removed old rendered

This commit is contained in:
brandon_withrow 2017-07-28 17:10:29 -07:00
parent 502b18ff46
commit e858c6afd1
40 changed files with 1009 additions and 4786 deletions

File diff suppressed because it is too large Load Diff

View File

@ -5,6 +5,7 @@
#import "LOTAnimationTransitionController.h"
#import "LOTAnimationView.h"
#import "LOTAnimationView_Compat.h"
#import "LOTComposition.h"
#import "Lottie.h"
FOUNDATION_EXPORT double LottieVersionNumber;

View File

@ -5,6 +5,7 @@
#import "LOTAnimationTransitionController.h"
#import "LOTAnimationView.h"
#import "LOTAnimationView_Compat.h"
#import "LOTComposition.h"
#import "Lottie.h"
FOUNDATION_EXPORT double LottieVersionNumber;

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View 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

View 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