123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329 |
- //
- // LOTLayerContainer.m
- // Lottie
- //
- // Created by brandon_withrow on 7/18/17.
- // Copyright © 2017 Airbnb. All rights reserved.
- //
- #import "LOTLayerContainer.h"
- #import "LOTTransformInterpolator.h"
- #import "LOTNumberInterpolator.h"
- #import "CGGeometry+LOTAdditions.h"
- #import "LOTRenderGroup.h"
- #import "LOTHelpers.h"
- #import "LOTMaskContainer.h"
- #import "LOTAsset.h"
- #if TARGET_OS_IPHONE || TARGET_OS_SIMULATOR
- #import "LOTCacheProvider.h"
- #endif
- @implementation LOTLayerContainer {
- LOTTransformInterpolator *_transformInterpolator;
- LOTNumberInterpolator *_opacityInterpolator;
- NSNumber *_inFrame;
- NSNumber *_outFrame;
- CALayer *DEBUG_Center;
- LOTRenderGroup *_contentsGroup;
- LOTMaskContainer *_maskLayer;
- }
- @dynamic currentFrame;
- - (instancetype)initWithModel:(LOTLayer *)layer
- inLayerGroup:(LOTLayerGroup *)layerGroup {
- self = [super init];
- if (self) {
- _wrapperLayer = [CALayer new];
- [self addSublayer:_wrapperLayer];
- DEBUG_Center = [CALayer layer];
-
- DEBUG_Center.bounds = CGRectMake(0, 0, 20, 20);
- DEBUG_Center.borderColor = [UIColor blueColor].CGColor;
- DEBUG_Center.borderWidth = 2;
- DEBUG_Center.masksToBounds = YES;
-
- if (ENABLE_DEBUG_SHAPES) {
- [_wrapperLayer addSublayer:DEBUG_Center];
- }
- self.actions = @{@"hidden" : [NSNull null], @"opacity" : [NSNull null], @"transform" : [NSNull null]};
- _wrapperLayer.actions = [self.actions copy];
- _timeStretchFactor = @1;
- [self commonInitializeWith:layer inLayerGroup:layerGroup];
- }
- return self;
- }
- - (void)commonInitializeWith:(LOTLayer *)layer
- inLayerGroup:(LOTLayerGroup *)layerGroup {
- if (layer == nil) {
- return;
- }
- _layerName = layer.layerName;
- if (layer.layerType == LOTLayerTypeImage ||
- layer.layerType == LOTLayerTypeSolid ||
- layer.layerType == LOTLayerTypePrecomp) {
- _wrapperLayer.bounds = CGRectMake(0, 0, layer.layerWidth.floatValue, layer.layerHeight.floatValue);
- _wrapperLayer.anchorPoint = CGPointMake(0, 0);
- _wrapperLayer.masksToBounds = YES;
- DEBUG_Center.position = LOT_RectGetCenterPoint(self.bounds);
- }
-
- if (layer.layerType == LOTLayerTypeImage) {
- [self _setImageForAsset:layer.imageAsset];
- }
-
- _inFrame = [layer.inFrame copy];
- _outFrame = [layer.outFrame copy];
- _timeStretchFactor = [layer.timeStretch copy];
- _transformInterpolator = [LOTTransformInterpolator transformForLayer:layer];
- if (layer.parentID != nil) {
- NSNumber *parentID = layer.parentID;
- LOTTransformInterpolator *childInterpolator = _transformInterpolator;
- while (parentID != nil) {
- LOTLayer *parentModel = [layerGroup layerModelForID:parentID];
- LOTTransformInterpolator *interpolator = [LOTTransformInterpolator transformForLayer:parentModel];
- childInterpolator.inputNode = interpolator;
- childInterpolator = interpolator;
- parentID = parentModel.parentID;
- }
- }
- _opacityInterpolator = [[LOTNumberInterpolator alloc] initWithKeyframes:layer.opacity.keyframes];
- if (layer.layerType == LOTLayerTypeShape &&
- layer.shapes.count) {
- [self buildContents:layer.shapes];
- }
- if (layer.layerType == LOTLayerTypeSolid) {
- _wrapperLayer.backgroundColor = layer.solidColor.CGColor;
- }
- if (layer.masks.count) {
- _maskLayer = [[LOTMaskContainer alloc] initWithMasks:layer.masks];
- _wrapperLayer.mask = _maskLayer;
- }
-
- NSMutableDictionary *interpolators = [NSMutableDictionary dictionary];
- interpolators[@"Opacity"] = _opacityInterpolator;
- interpolators[@"Anchor Point"] = _transformInterpolator.anchorInterpolator;
- interpolators[@"Scale"] = _transformInterpolator.scaleInterpolator;
- interpolators[@"Rotation"] = _transformInterpolator.rotationInterpolator;
- if (_transformInterpolator.positionXInterpolator &&
- _transformInterpolator.positionYInterpolator) {
- interpolators[@"X Position"] = _transformInterpolator.positionXInterpolator;
- interpolators[@"Y Position"] = _transformInterpolator.positionYInterpolator;
- } else if (_transformInterpolator.positionInterpolator) {
- interpolators[@"Position"] = _transformInterpolator.positionInterpolator;
- }
- // Deprecated
- interpolators[@"Transform.Opacity"] = _opacityInterpolator;
- interpolators[@"Transform.Anchor Point"] = _transformInterpolator.anchorInterpolator;
- interpolators[@"Transform.Scale"] = _transformInterpolator.scaleInterpolator;
- interpolators[@"Transform.Rotation"] = _transformInterpolator.rotationInterpolator;
- if (_transformInterpolator.positionXInterpolator &&
- _transformInterpolator.positionYInterpolator) {
- interpolators[@"Transform.X Position"] = _transformInterpolator.positionXInterpolator;
- interpolators[@"Transform.Y Position"] = _transformInterpolator.positionYInterpolator;
- } else if (_transformInterpolator.positionInterpolator) {
- interpolators[@"Transform.Position"] = _transformInterpolator.positionInterpolator;
- }
- _valueInterpolators = interpolators;
- }
- - (void)buildContents:(NSArray *)contents {
- _contentsGroup = [[LOTRenderGroup alloc] initWithInputNode:nil contents:contents keyname:_layerName];
- [_wrapperLayer addSublayer:_contentsGroup.containerLayer];
- }
- #if TARGET_OS_IPHONE || TARGET_OS_SIMULATOR
- - (void)_setImageForAsset:(LOTAsset *)asset {
- if (asset.imageName) {
- UIImage *image;
- if ([asset.imageName hasPrefix:@"data:"]) {
- // Contents look like a data: URL. Ignore asset.imageDirectory and simply load the image directly.
- NSURL *imageUrl = [NSURL URLWithString:asset.imageName];
- NSData *imageData = [NSData dataWithContentsOfURL:imageUrl];
- image = [UIImage imageWithData:imageData];
- } else if (asset.rootDirectory.length > 0) {
- NSString *rootDirectory = asset.rootDirectory;
- if (asset.imageDirectory.length > 0) {
- rootDirectory = [rootDirectory stringByAppendingPathComponent:asset.imageDirectory];
- }
- NSString *imagePath = [rootDirectory stringByAppendingPathComponent:asset.imageName];
-
- id<LOTImageCache> imageCache = [LOTCacheProvider imageCache];
- if (imageCache) {
- image = [imageCache imageForKey:imagePath];
- if (!image) {
- image = [UIImage imageWithContentsOfFile:imagePath];
- [imageCache setImage:image forKey:imagePath];
- }
- } else {
- image = [UIImage imageWithContentsOfFile:imagePath];
- }
- } else {
- NSString *imagePath = [asset.assetBundle pathForResource:asset.imageName ofType:nil];
- image = [UIImage imageWithContentsOfFile:imagePath];
- }
- //try loading from asset catalogue instead if all else fails
- if (!image) {
- image = [UIImage imageNamed:asset.imageName inBundle: asset.assetBundle compatibleWithTraitCollection:nil];
- }
-
- if (image) {
- _wrapperLayer.contents = (__bridge id _Nullable)(image.CGImage);
- } else {
- NSLog(@"%s: Warn: image not found: %@", __PRETTY_FUNCTION__, asset.imageName);
- }
- }
- }
- #else
- - (void)_setImageForAsset:(LOTAsset *)asset {
- if (asset.imageName) {
- NSArray *components = [asset.imageName componentsSeparatedByString:@"."];
- NSImage *image = [NSImage imageNamed:components.firstObject];
- if (image == nil) {
- if (asset.rootDirectory.length > 0 && asset.imageDirectory.length > 0) {
- NSString *imagePath = [[asset.rootDirectory stringByAppendingPathComponent:asset.imageDirectory] stringByAppendingPathComponent:asset.imageName];
- image = [[NSImage alloc] initWithContentsOfFile:imagePath];
- }
- }
- if (image) {
- NSWindow *window = [NSApp mainWindow];
- CGFloat desiredScaleFactor = [window backingScaleFactor];
- CGFloat actualScaleFactor = [image recommendedLayerContentsScale:desiredScaleFactor];
- id layerContents = [image layerContentsForContentsScale:actualScaleFactor];
- _wrapperLayer.contents = layerContents;
- }
- }
-
- }
- #endif
- // MARK - Animation
- + (BOOL)needsDisplayForKey:(NSString *)key {
- if ([key isEqualToString:@"currentFrame"]) {
- return YES;
- }
- return [super needsDisplayForKey:key];
- }
- - (id<CAAction>)actionForKey:(NSString *)event {
- if ([event isEqualToString:@"currentFrame"]) {
- CABasicAnimation *theAnimation = [CABasicAnimation
- animationWithKeyPath:event];
- theAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
- theAnimation.fromValue = [[self presentationLayer] valueForKey:event];
- return theAnimation;
- }
- return [super actionForKey:event];
- }
- - (id)initWithLayer:(id)layer {
- if (self = [super initWithLayer:layer]) {
- if ([layer isKindOfClass:[LOTLayerContainer class]]) {
- LOTLayerContainer *other = (LOTLayerContainer *)layer;
- self.currentFrame = [other.currentFrame copy];
- }
- }
- return self;
- }
- - (void)display {
- @synchronized(self) {
- LOTLayerContainer *presentation = self;
- if (self.animationKeys.count &&
- self.presentationLayer) {
- presentation = (LOTLayerContainer *)self.presentationLayer;
- }
- [self displayWithFrame:presentation.currentFrame];
- }
- }
- - (void)displayWithFrame:(NSNumber *)frame {
- [self displayWithFrame:frame forceUpdate:NO];
- }
- - (void)displayWithFrame:(NSNumber *)frame forceUpdate:(BOOL)forceUpdate {
- NSNumber *newFrame = @(frame.floatValue / self.timeStretchFactor.floatValue);
- if (ENABLE_DEBUG_LOGGING) NSLog(@"View %@ Displaying Frame %@, with local time %@", self, frame, newFrame);
- BOOL hidden = NO;
- if (_inFrame && _outFrame) {
- hidden = (frame.floatValue < _inFrame.floatValue ||
- frame.floatValue > _outFrame.floatValue);
- }
- self.hidden = hidden;
- if (hidden) {
- return;
- }
- if (_opacityInterpolator && [_opacityInterpolator hasUpdateForFrame:newFrame]) {
- self.opacity = [_opacityInterpolator floatValueForFrame:newFrame];
- }
- if (_transformInterpolator && [_transformInterpolator hasUpdateForFrame:newFrame]) {
- _wrapperLayer.transform = [_transformInterpolator transformForFrame:newFrame];
- }
- [_contentsGroup updateWithFrame:newFrame withModifierBlock:nil forceLocalUpdate:forceUpdate];
- _maskLayer.currentFrame = newFrame;
- }
- - (void)setViewportBounds:(CGRect)viewportBounds {
- _viewportBounds = viewportBounds;
- if (_maskLayer) {
- CGPoint center = LOT_RectGetCenterPoint(viewportBounds);
- viewportBounds.origin = CGPointMake(-center.x, -center.y);
- _maskLayer.bounds = viewportBounds;
- }
- }
- - (void)searchNodesForKeypath:(LOTKeypath * _Nonnull)keypath {
- if (_contentsGroup == nil && [keypath pushKey:self.layerName]) {
- // Matches self.
- if ([keypath pushKey:@"Transform"]) {
- // Is a transform node, check interpolators
- LOTValueInterpolator *interpolator = _valueInterpolators[keypath.currentKey];
- if (interpolator) {
- // We have a match!
- [keypath pushKey:keypath.currentKey];
- [keypath addSearchResultForCurrentPath:_wrapperLayer];
- [keypath popKey];
- }
- if (keypath.endOfKeypath) {
- [keypath addSearchResultForCurrentPath:_wrapperLayer];
- }
- [keypath popKey];
- }
- if (keypath.endOfKeypath) {
- [keypath addSearchResultForCurrentPath:_wrapperLayer];
- }
- [keypath popKey];
- }
- [_contentsGroup searchNodesForKeypath:keypath];
- }
- - (void)setValueDelegate:(id<LOTValueDelegate> _Nonnull)delegate
- forKeypath:(LOTKeypath * _Nonnull)keypath {
- if ([keypath pushKey:self.layerName]) {
- // Matches self.
- if ([keypath pushKey:@"Transform"]) {
- // Is a transform node, check interpolators
- LOTValueInterpolator *interpolator = _valueInterpolators[keypath.currentKey];
- if (interpolator) {
- // We have a match!
- [interpolator setValueDelegate:delegate];
- }
- [keypath popKey];
- }
- [keypath popKey];
- }
- [_contentsGroup setValueDelegate:delegate forKeypath:keypath];
- }
- @end
|