123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845 |
- //
- // LOTAnimationView
- // LottieAnimator
- //
- // Created by Brandon Withrow on 12/14/15.
- // Copyright © 2015 Brandon Withrow. All rights reserved.
- //
- #import "LOTAnimationView.h"
- #import "LOTPlatformCompat.h"
- #import "LOTModels.h"
- #import "LOTHelpers.h"
- #import "LOTAnimationView_Internal.h"
- #import "LOTAnimationCache.h"
- #import "LOTCompositionContainer.h"
- static NSString * const kCompContainerAnimationKey = @"play";
- @implementation LOTAnimationView {
- LOTCompositionContainer *_compContainer;
- NSNumber *_playRangeStartFrame;
- NSNumber *_playRangeEndFrame;
- CGFloat _playRangeStartProgress;
- CGFloat _playRangeEndProgress;
- NSBundle *_bundle;
- CGFloat _animationProgress;
- // Properties for tracking automatic restoration of animation.
- BOOL _shouldRestoreStateWhenAttachedToWindow;
- LOTAnimationCompletionBlock _completionBlockToRestoreWhenAttachedToWindow;
- }
- # pragma mark - Convenience Initializers
- + (nonnull instancetype)animationNamed:(nonnull NSString *)animationName {
- return [self animationNamed:animationName inBundle:[NSBundle mainBundle]];
- }
- + (nonnull instancetype)animationNamed:(nonnull NSString *)animationName inBundle:(nonnull NSBundle *)bundle {
- LOTComposition *comp = [LOTComposition animationNamed:animationName inBundle:bundle];
- return [[self alloc] initWithModel:comp inBundle:bundle];
- }
- + (nonnull instancetype)animationFromJSON:(nonnull NSDictionary *)animationJSON {
- return [self animationFromJSON:animationJSON inBundle:[NSBundle mainBundle]];
- }
- + (nonnull instancetype)animationFromJSON:(nullable NSDictionary *)animationJSON inBundle:(nullable NSBundle *)bundle {
- LOTComposition *comp = [LOTComposition animationFromJSON:animationJSON inBundle:bundle];
- return [[self alloc] initWithModel:comp inBundle:bundle];
- }
- + (nonnull instancetype)animationWithFilePath:(nonnull NSString *)filePath {
- LOTComposition *comp = [LOTComposition animationWithFilePath:filePath];
- return [[self alloc] initWithModel:comp inBundle:[NSBundle mainBundle]];
- }
- # pragma mark - Initializers
- - (instancetype)initWithContentsOfURL:(NSURL *)url {
- self = [self initWithFrame:CGRectZero];
- if (self) {
- LOTComposition *laScene = [[LOTAnimationCache sharedCache] animationForKey:url.absoluteString];
- if (laScene) {
- laScene.cacheKey = url.absoluteString;
- [self _initializeAnimationContainer];
- [self _setupWithSceneModel:laScene];
- } else {
- dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void) {
- NSData *animationData = [NSData dataWithContentsOfURL:url];
- if (!animationData) {
- return;
- }
- NSError *error;
- NSDictionary *animationJSON = [NSJSONSerialization JSONObjectWithData:animationData
- options:0 error:&error];
- if (error || !animationJSON) {
- return;
- }
-
- LOTComposition *laScene = [[LOTComposition alloc] initWithJSON:animationJSON withAssetBundle:[NSBundle mainBundle]];
- dispatch_async(dispatch_get_main_queue(), ^(void) {
- [[LOTAnimationCache sharedCache] addAnimation:laScene forKey:url.absoluteString];
- laScene.cacheKey = url.absoluteString;
- [self _initializeAnimationContainer];
- [self _setupWithSceneModel:laScene];
- });
- });
- }
- }
- return self;
- }
- - (instancetype)initWithModel:(LOTComposition *)model inBundle:(NSBundle *)bundle {
- self = [self initWithFrame:model.compBounds];
- if (self) {
- _bundle = bundle;
- [self _initializeAnimationContainer];
- [self _setupWithSceneModel:model];
- }
- return self;
- }
- - (instancetype)initWithFrame:(CGRect)frame {
- self = [super initWithFrame:frame];
- if (self) {
- [self _commonInit];
- }
- return self;
- }
- - (instancetype)initWithCoder:(NSCoder *)coder {
- self = [super initWithCoder:coder];
- if (self) {
- [self _commonInit];
- }
- return self;
- }
- # pragma mark - Inspectables
- - (void)setAnimation:(NSString *)animationName {
-
- _animation = animationName;
-
- [self setAnimationNamed:animationName];
-
- }
- # pragma mark - Internal Methods
- #if TARGET_OS_IPHONE || TARGET_OS_SIMULATOR
- - (void)_initializeAnimationContainer {
- self.clipsToBounds = YES;
- }
- - (void)_commonInit {
- _animationSpeed = 1;
- _animationProgress = 0;
- _loopAnimation = NO;
- _autoReverseAnimation = NO;
- _playRangeEndFrame = nil;
- _playRangeStartFrame = nil;
- _playRangeEndProgress = 0;
- _playRangeStartProgress = 0;
- _shouldRasterizeWhenIdle = NO;
- [NSNotificationCenter.defaultCenter addObserver:self selector:@selector(_handleWillEnterForeground) name:UIApplicationWillEnterForegroundNotification object:nil];
- [NSNotificationCenter.defaultCenter addObserver:self selector:@selector(_handleWillEnterBackground) name:UIApplicationDidEnterBackgroundNotification object:nil];
- }
- #else
- - (void)_initializeAnimationContainer {
- self.wantsLayer = YES;
- }
- - (void)_commonInit {
- _animationSpeed = 1;
- _animationProgress = 0;
- _loopAnimation = NO;
- _autoReverseAnimation = NO;
- _playRangeEndFrame = nil;
- _playRangeStartFrame = nil;
- _playRangeEndProgress = 0;
- _playRangeStartProgress = 0;
- _shouldRasterizeWhenIdle = NO;
- }
- #endif
- - (void)dealloc {
- [NSNotificationCenter.defaultCenter removeObserver:self];
- }
- - (void)_setupWithSceneModel:(LOTComposition *)model {
- if (_sceneModel) {
- [self _removeCurrentAnimationIfNecessary];
- [self _callCompletionIfNecessary:NO];
- [_compContainer removeFromSuperlayer];
- _compContainer = nil;
- _sceneModel = nil;
- [self _commonInit];
- }
-
- _sceneModel = model;
- _compContainer = [[LOTCompositionContainer alloc] initWithModel:nil inLayerGroup:nil withLayerGroup:_sceneModel.layerGroup withAssestGroup:_sceneModel.assetGroup];
- [self.layer addSublayer:_compContainer];
- [self _restoreState];
- [self setNeedsLayout];
- }
- - (void)_restoreState {
- if (_isAnimationPlaying) {
- _isAnimationPlaying = NO;
- if (_playRangeStartFrame && _playRangeEndFrame) {
- [self playFromFrame:_playRangeStartFrame toFrame:_playRangeEndFrame withCompletion:self.completionBlock];
- } else if (_playRangeEndProgress != _playRangeStartProgress) {
- [self playFromProgress:_playRangeStartProgress toProgress:_playRangeEndProgress withCompletion:self.completionBlock];
- } else {
- [self playWithCompletion:self.completionBlock];
- }
- } else {
- self.animationProgress = _animationProgress;
- }
- }
- - (void)_removeCurrentAnimationIfNecessary {
- _isAnimationPlaying = NO;
- [_compContainer removeAllAnimations];
- _compContainer.shouldRasterize = _shouldRasterizeWhenIdle;
- }
- - (CGFloat)_progressForFrame:(NSNumber *)frame {
- if (!_sceneModel) {
- return 0;
- }
- return ((frame.floatValue - _sceneModel.startFrame.floatValue) / (_sceneModel.endFrame.floatValue - _sceneModel.startFrame.floatValue));
- }
- - (NSNumber *)_frameForProgress:(CGFloat)progress {
- if (!_sceneModel) {
- return @0;
- }
- return @(((_sceneModel.endFrame.floatValue - _sceneModel.startFrame.floatValue) * progress) + _sceneModel.startFrame.floatValue);
- }
- - (BOOL)_isSpeedNegative {
- // If the animation speed is negative, then we're moving backwards.
- return _animationSpeed >= 0;
- }
- - (void)_handleWindowChanges:(BOOL)hasNewWindow
- {
- // When this view or its superview is leaving the screen, e.g. a modal is presented or another
- // screen is pushed, this method will get called with newWindow value set to nil - indicating that
- // this view will be detached from the visible window.
- // When a view is detached, animations will stop - but will not automatically resumed when it's
- // re-attached back to window, e.g. when the presented modal is dismissed or another screen is
- // pop.
- if (hasNewWindow) {
- // The view is being re-attached, resume animation if needed.
- if (_shouldRestoreStateWhenAttachedToWindow) {
- _shouldRestoreStateWhenAttachedToWindow = NO;
-
- _isAnimationPlaying = YES;
- _completionBlock = _completionBlockToRestoreWhenAttachedToWindow;
- _completionBlockToRestoreWhenAttachedToWindow = nil;
-
- [self performSelector:@selector(_restoreState) withObject:nil afterDelay:0 inModes:@[NSRunLoopCommonModes]];
- }
- } else {
- // The view is being detached, capture information that need to be restored later.
- if (_isAnimationPlaying) {
- LOTAnimationCompletionBlock completion = _completionBlock;
- [self pause];
- _shouldRestoreStateWhenAttachedToWindow = YES;
- _completionBlockToRestoreWhenAttachedToWindow = completion;
- _completionBlock = nil;
- }
- }
- }
- - (void)_handleWillEnterBackground {
- [self _handleWindowChanges: false];
- }
- - (void)_handleWillEnterForeground {
- [self _handleWindowChanges: (self.window != nil)];
- }
- # pragma mark - Completion Block
- - (void)_callCompletionIfNecessary:(BOOL)complete {
- if (self.completionBlock) {
- LOTAnimationCompletionBlock completion = self.completionBlock;
- self.completionBlock = nil;
- completion(complete);
- }
- }
- # pragma mark - External Methods
- - (void)setAnimationNamed:(nonnull NSString *)animationName {
- LOTComposition *comp = [LOTComposition animationNamed:animationName];
- [self _initializeAnimationContainer];
- [self _setupWithSceneModel:comp];
- }
-
- - (void)setAnimationNamed:(NSString *)animationName inBundle:(NSBundle *)bundle {
- LOTComposition *comp = [LOTComposition animationNamed:animationName inBundle:bundle];
-
- [self _initializeAnimationContainer];
- [self _setupWithSceneModel:comp];
- }
- - (void)setAnimationFromJSON:(nonnull NSDictionary *)animationJSON {
- LOTComposition *comp = [LOTComposition animationFromJSON:animationJSON];
- [self _initializeAnimationContainer];
- [self _setupWithSceneModel:comp];
- }
-
- - (void)setAnimationFromJSON:(NSDictionary *)animationJSON inBundle:(NSBundle *)bundle {
- LOTComposition *comp = [LOTComposition animationFromJSON:animationJSON inBundle:bundle];
-
- [self _initializeAnimationContainer];
- [self _setupWithSceneModel:comp];
- }
- # pragma mark - External Methods - Model
- - (void)setSceneModel:(LOTComposition *)sceneModel {
- [self _setupWithSceneModel:sceneModel];
- }
- # pragma mark - External Methods - Play Control
- - (void)play {
- if (!_sceneModel) {
- _isAnimationPlaying = YES;
- return;
- }
- [self playFromFrame:_sceneModel.startFrame toFrame:_sceneModel.endFrame withCompletion:nil];
- }
- - (void)playWithCompletion:(LOTAnimationCompletionBlock)completion {
- if (!_sceneModel) {
- _isAnimationPlaying = YES;
- self.completionBlock = completion;
- return;
- }
- [self playFromFrame:_sceneModel.startFrame toFrame:_sceneModel.endFrame withCompletion:completion];
- }
- - (void)playToProgress:(CGFloat)progress withCompletion:(nullable LOTAnimationCompletionBlock)completion {
- [self playFromProgress:0 toProgress:progress withCompletion:completion];
- }
- - (void)playFromProgress:(CGFloat)fromStartProgress
- toProgress:(CGFloat)toEndProgress
- withCompletion:(nullable LOTAnimationCompletionBlock)completion {
- if (!_sceneModel) {
- _isAnimationPlaying = YES;
- self.completionBlock = completion;
- _playRangeStartProgress = fromStartProgress;
- _playRangeEndProgress = toEndProgress;
- return;
- }
- [self playFromFrame:[self _frameForProgress:fromStartProgress]
- toFrame:[self _frameForProgress:toEndProgress]
- withCompletion:completion];
- }
- - (void)playToFrame:(nonnull NSNumber *)toFrame
- withCompletion:(nullable LOTAnimationCompletionBlock)completion {
- [self playFromFrame:_sceneModel.startFrame toFrame:toFrame withCompletion:completion];
- }
- - (void)playFromFrame:(nonnull NSNumber *)fromStartFrame
- toFrame:(nonnull NSNumber *)toEndFrame
- withCompletion:(nullable LOTAnimationCompletionBlock)completion {
- if (_isAnimationPlaying) {
- return;
- }
- _playRangeStartFrame = fromStartFrame;
- _playRangeEndFrame = toEndFrame;
- if (completion) {
- self.completionBlock = completion;
- }
- if (!_sceneModel) {
- _isAnimationPlaying = YES;
- return;
- }
- BOOL playingForward = ((_animationSpeed > 0) && (toEndFrame.floatValue > fromStartFrame.floatValue))
- || ((_animationSpeed < 0) && (fromStartFrame.floatValue > toEndFrame.floatValue));
- CGFloat leftFrameValue = MIN(fromStartFrame.floatValue, toEndFrame.floatValue);
- CGFloat rightFrameValue = MAX(fromStartFrame.floatValue, toEndFrame.floatValue);
- NSNumber *currentFrame = [self _frameForProgress:_animationProgress];
- currentFrame = @(MAX(MIN(currentFrame.floatValue, rightFrameValue), leftFrameValue));
- if (currentFrame.floatValue == rightFrameValue && playingForward) {
- currentFrame = @(leftFrameValue);
- } else if (currentFrame.floatValue == leftFrameValue && !playingForward) {
- currentFrame = @(rightFrameValue);
- }
- _animationProgress = [self _progressForFrame:currentFrame];
-
- CGFloat currentProgress = _animationProgress * (_sceneModel.endFrame.floatValue - _sceneModel.startFrame.floatValue);
- CGFloat skipProgress;
- if (playingForward) {
- skipProgress = currentProgress - leftFrameValue;
- } else {
- skipProgress = rightFrameValue - currentProgress;
- }
- NSTimeInterval offset = MAX(0, skipProgress) / _sceneModel.framerate.floatValue;
- if (!self.window) {
- _shouldRestoreStateWhenAttachedToWindow = YES;
- _completionBlockToRestoreWhenAttachedToWindow = self.completionBlock;
- self.completionBlock = nil;
- } else {
- NSTimeInterval duration = (ABS(toEndFrame.floatValue - fromStartFrame.floatValue) / _sceneModel.framerate.floatValue);
- CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"currentFrame"];
- animation.speed = _animationSpeed;
- animation.fromValue = fromStartFrame;
- animation.toValue = toEndFrame;
- animation.duration = duration;
- animation.fillMode = kCAFillModeBoth;
- animation.repeatCount = _loopAnimation ? HUGE_VALF : 1;
- animation.autoreverses = _autoReverseAnimation;
- animation.delegate = self;
- animation.removedOnCompletion = NO;
- if (offset != 0) {
- CFTimeInterval currentTime = CACurrentMediaTime();
- CFTimeInterval currentLayerTime = [self.layer convertTime:currentTime fromLayer:nil];
- animation.beginTime = currentLayerTime - (offset * 1 / _animationSpeed);
- }
- [_compContainer addAnimation:animation forKey:kCompContainerAnimationKey];
- _compContainer.shouldRasterize = NO;
- }
- _isAnimationPlaying = YES;
- }
- #pragma mark - Other Time Controls
- - (void)stop {
- _isAnimationPlaying = NO;
- if (_sceneModel) {
- [self setProgressWithFrame:_sceneModel.startFrame callCompletionIfNecessary:YES];
- }
- }
- - (void)pause {
- if (!_sceneModel ||
- !_isAnimationPlaying) {
- _isAnimationPlaying = NO;
- return;
- }
- NSNumber *frame = [_compContainer.presentationLayer.currentFrame copy];
- [self setProgressWithFrame:frame callCompletionIfNecessary:YES];
- }
- - (void)setAnimationProgress:(CGFloat)animationProgress {
- if (!_sceneModel) {
- _animationProgress = animationProgress;
- return;
- }
- [self setProgressWithFrame:[self _frameForProgress:animationProgress] callCompletionIfNecessary:YES];
- }
- - (void)setProgressWithFrame:(nonnull NSNumber *)currentFrame {
- [self setProgressWithFrame:currentFrame callCompletionIfNecessary:YES];
- }
- - (void)setProgressWithFrame:(nonnull NSNumber *)currentFrame callCompletionIfNecessary:(BOOL)callCompletion {
- [self _removeCurrentAnimationIfNecessary];
- if (_shouldRestoreStateWhenAttachedToWindow) {
- _shouldRestoreStateWhenAttachedToWindow = NO;
- self.completionBlock = _completionBlockToRestoreWhenAttachedToWindow;
- _completionBlockToRestoreWhenAttachedToWindow = nil;
- }
- _animationProgress = [self _progressForFrame:currentFrame];
- [CATransaction begin];
- [CATransaction setDisableActions:YES];
- _compContainer.currentFrame = currentFrame;
- [_compContainer setNeedsDisplay];
- [CATransaction commit];
- if (callCompletion) {
- [self _callCompletionIfNecessary:NO];
- }
- }
- - (void)setLoopAnimation:(BOOL)loopAnimation {
- _loopAnimation = loopAnimation;
- if (_isAnimationPlaying && _sceneModel) {
- NSNumber *frame = [_compContainer.presentationLayer.currentFrame copy];
- [self setProgressWithFrame:frame callCompletionIfNecessary:NO];
- [self playFromFrame:_playRangeStartFrame toFrame:_playRangeEndFrame withCompletion:self.completionBlock];
- }
- }
- - (void)setAnimationSpeed:(CGFloat)animationSpeed {
- _animationSpeed = animationSpeed;
- if (_isAnimationPlaying && _sceneModel) {
- NSNumber *frame = [_compContainer.presentationLayer.currentFrame copy];
- [self setProgressWithFrame:frame callCompletionIfNecessary:NO];
- [self playFromFrame:_playRangeStartFrame toFrame:_playRangeEndFrame withCompletion:self.completionBlock];
- }
- }
- - (void)forceDrawingUpdate {
- [self _layoutAndForceUpdate];
- }
- # pragma mark - External Methods - Idle Rasterization
- - (void)setShouldRasterizeWhenIdle:(BOOL)shouldRasterize {
- _shouldRasterizeWhenIdle = shouldRasterize;
- if (!_isAnimationPlaying) {
- _compContainer.shouldRasterize = _shouldRasterizeWhenIdle;
- }
- }
- # pragma mark - External Methods - Cache
- - (void)setCacheEnable:(BOOL)cacheEnable {
- _cacheEnable = cacheEnable;
- if (!self.sceneModel.cacheKey) {
- return;
- }
- if (cacheEnable) {
- [[LOTAnimationCache sharedCache] addAnimation:_sceneModel forKey:self.sceneModel.cacheKey];
- } else {
- [[LOTAnimationCache sharedCache] removeAnimationForKey:self.sceneModel.cacheKey];
- }
- }
- # pragma mark - External Methods - Interactive Controls
- - (void)setValueDelegate:(id<LOTValueDelegate> _Nonnull)delegate
- forKeypath:(LOTKeypath * _Nonnull)keypath {
- [_compContainer setValueDelegate:delegate forKeypath:keypath];
- [self _layoutAndForceUpdate];
- }
- - (nullable NSArray *)keysForKeyPath:(nonnull LOTKeypath *)keypath {
- return [_compContainer keysForKeyPath:keypath];
- }
- - (CGPoint)convertPoint:(CGPoint)point
- toKeypathLayer:(nonnull LOTKeypath *)keypath {
- [self _layoutAndForceUpdate];
- return [_compContainer convertPoint:point toKeypathLayer:keypath withParentLayer:self.layer];
- }
- - (CGRect)convertRect:(CGRect)rect
- toKeypathLayer:(nonnull LOTKeypath *)keypath {
- [self _layoutAndForceUpdate];
- return [_compContainer convertRect:rect toKeypathLayer:keypath withParentLayer:self.layer];
- }
- - (CGPoint)convertPoint:(CGPoint)point
- fromKeypathLayer:(nonnull LOTKeypath *)keypath {
- [self _layoutAndForceUpdate];
- return [_compContainer convertPoint:point fromKeypathLayer:keypath withParentLayer:self.layer];
- }
- - (CGRect)convertRect:(CGRect)rect
- fromKeypathLayer:(nonnull LOTKeypath *)keypath {
- [self _layoutAndForceUpdate];
- return [_compContainer convertRect:rect fromKeypathLayer:keypath withParentLayer:self.layer];
- }
- #if TARGET_OS_IPHONE || TARGET_OS_SIMULATOR
- - (void)addSubview:(nonnull LOTView *)view
- toKeypathLayer:(nonnull LOTKeypath *)keypath {
- [self _layoutAndForceUpdate];
- CGRect viewRect = view.frame;
- LOTView *wrapperView = [[LOTView alloc] initWithFrame:viewRect];
- view.frame = view.bounds;
- view.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
- [wrapperView addSubview:view];
- [self addSubview:wrapperView];
- [_compContainer addSublayer:wrapperView.layer toKeypathLayer:keypath];
- }
- - (void)maskSubview:(nonnull LOTView *)view
- toKeypathLayer:(nonnull LOTKeypath *)keypath {
- [self _layoutAndForceUpdate];
- CGRect viewRect = view.frame;
- LOTView *wrapperView = [[LOTView alloc] initWithFrame:viewRect];
- view.frame = view.bounds;
- view.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
- [wrapperView addSubview:view];
- [self addSubview:wrapperView];
- [_compContainer maskSublayer:wrapperView.layer toKeypathLayer:keypath];
- }
- #else
- - (void)addSubview:(nonnull LOTView *)view
- toKeypathLayer:(nonnull LOTKeypath *)keypath {
- [self _layout];
- CGRect viewRect = view.frame;
- LOTView *wrapperView = [[LOTView alloc] initWithFrame:viewRect];
- view.frame = view.bounds;
- view.autoresizingMask = NSViewWidthSizable | NSViewHeightSizable;
- [wrapperView addSubview:view];
- [self addSubview:wrapperView];
- [_compContainer addSublayer:wrapperView.layer toKeypathLayer:keypath];
- }
- - (void)maskSubview:(nonnull LOTView *)view
- toKeypathLayer:(nonnull LOTKeypath *)keypath {
- [self _layout];
- CGRect viewRect = view.frame;
- LOTView *wrapperView = [[LOTView alloc] initWithFrame:viewRect];
- view.frame = view.bounds;
- view.autoresizingMask = NSViewWidthSizable | NSViewHeightSizable;
- [wrapperView addSubview:view];
- [self addSubview:wrapperView];
- [_compContainer maskSublayer:wrapperView.layer toKeypathLayer:keypath];
- }
- #endif
- # pragma mark - Semi-Private Methods
- - (CALayer * _Nullable)layerForKey:(NSString * _Nonnull)keyname {
- return _compContainer.childMap[keyname];
- }
- - (NSArray * _Nonnull)compositionLayers {
- return _compContainer.childLayers;
- }
- # pragma mark - Getters and Setters
- - (CGFloat)animationDuration {
- if (!_sceneModel) {
- return 0;
- }
- CAAnimation *play = [_compContainer animationForKey:kCompContainerAnimationKey];
- if (play) {
- return play.duration;
- }
- return (_sceneModel.endFrame.floatValue - _sceneModel.startFrame.floatValue) / _sceneModel.framerate.floatValue;
- }
- - (CGFloat)animationProgress {
- if (_isAnimationPlaying &&
- _compContainer.presentationLayer) {
- CGFloat activeProgress = [self _progressForFrame:[(LOTCompositionContainer *)_compContainer.presentationLayer currentFrame]];
- return activeProgress;
- }
- return _animationProgress;
- }
- # pragma mark - Overrides
- #if TARGET_OS_IPHONE || TARGET_OS_SIMULATOR
- #define LOTViewContentMode UIViewContentMode
- #define LOTViewContentModeScaleToFill UIViewContentModeScaleToFill
- #define LOTViewContentModeScaleAspectFit UIViewContentModeScaleAspectFit
- #define LOTViewContentModeScaleAspectFill UIViewContentModeScaleAspectFill
- #define LOTViewContentModeRedraw UIViewContentModeRedraw
- #define LOTViewContentModeCenter UIViewContentModeCenter
- #define LOTViewContentModeTop UIViewContentModeTop
- #define LOTViewContentModeBottom UIViewContentModeBottom
- #define LOTViewContentModeLeft UIViewContentModeLeft
- #define LOTViewContentModeRight UIViewContentModeRight
- #define LOTViewContentModeTopLeft UIViewContentModeTopLeft
- #define LOTViewContentModeTopRight UIViewContentModeTopRight
- #define LOTViewContentModeBottomLeft UIViewContentModeBottomLeft
- #define LOTViewContentModeBottomRight UIViewContentModeBottomRight
- - (CGSize)intrinsicContentSize {
- if (!_sceneModel) {
- return CGSizeMake(UIViewNoIntrinsicMetric, UIViewNoIntrinsicMetric);
- }
- return _sceneModel.compBounds.size;
- }
- - (void)didMoveToSuperview {
- [super didMoveToSuperview];
- if (self.superview == nil) {
- [self _callCompletionIfNecessary:NO];
- }
- }
- - (void)willMoveToWindow:(UIWindow *)newWindow {
- [self _handleWindowChanges:(newWindow != nil)];
- }
- - (void)didMoveToWindow {
- _compContainer.rasterizationScale = self.window.screen.scale;
- }
- - (void)setContentMode:(LOTViewContentMode)contentMode {
- [super setContentMode:contentMode];
- [self setNeedsLayout];
- }
- - (void)layoutSubviews {
- [super layoutSubviews];
- [self _layout];
- }
- #else
- - (void)viewWillMoveToWindow:(NSWindow *)newWindow {
- [self _handleWindowChanges:(newWindow != nil)];
- }
- - (void)viewDidMoveToWindow {
- _compContainer.rasterizationScale = self.window.screen.backingScaleFactor;
- }
-
- - (void)setCompletionBlock:(LOTAnimationCompletionBlock)completionBlock {
- if (completionBlock) {
- _completionBlock = ^(BOOL finished) {
- dispatch_async(dispatch_get_main_queue(), ^{ completionBlock(finished); });
- };
- }
- else {
- _completionBlock = nil;
- }
- }
- - (void)setContentMode:(LOTViewContentMode)contentMode {
- _contentMode = contentMode;
- [self setNeedsLayout];
- }
- - (void)setNeedsLayout {
- self.needsLayout = YES;
- }
- - (BOOL)isFlipped {
- return YES;
- }
- - (BOOL)wantsUpdateLayer {
- return YES;
- }
- - (void)layout {
- [super layout];
- [self _layout];
- }
- #endif
- - (void)_layoutAndForceUpdate {
- [CATransaction begin];
- [CATransaction setDisableActions:YES];
- [self _layout];
- [_compContainer displayWithFrame:_compContainer.currentFrame forceUpdate:YES];
- [CATransaction commit];
- }
- - (void)_layout {
- CGPoint centerPoint = LOT_RectGetCenterPoint(self.bounds);
- CATransform3D xform;
- if (self.contentMode == LOTViewContentModeScaleToFill) {
- CGSize scaleSize = CGSizeMake(self.bounds.size.width / self.sceneModel.compBounds.size.width,
- self.bounds.size.height / self.sceneModel.compBounds.size.height);
- xform = CATransform3DMakeScale(scaleSize.width, scaleSize.height, 1);
- } else if (self.contentMode == LOTViewContentModeScaleAspectFit) {
- CGFloat compAspect = self.sceneModel.compBounds.size.width / self.sceneModel.compBounds.size.height;
- CGFloat viewAspect = self.bounds.size.width / self.bounds.size.height;
- BOOL scaleWidth = compAspect > viewAspect;
- CGFloat dominantDimension = scaleWidth ? self.bounds.size.width : self.bounds.size.height;
- CGFloat compDimension = scaleWidth ? self.sceneModel.compBounds.size.width : self.sceneModel.compBounds.size.height;
- CGFloat scale = dominantDimension / compDimension;
- xform = CATransform3DMakeScale(scale, scale, 1);
- } else if (self.contentMode == LOTViewContentModeScaleAspectFill) {
- CGFloat compAspect = self.sceneModel.compBounds.size.width / self.sceneModel.compBounds.size.height;
- CGFloat viewAspect = self.bounds.size.width / self.bounds.size.height;
- BOOL scaleWidth = compAspect < viewAspect;
- CGFloat dominantDimension = scaleWidth ? self.bounds.size.width : self.bounds.size.height;
- CGFloat compDimension = scaleWidth ? self.sceneModel.compBounds.size.width : self.sceneModel.compBounds.size.height;
- CGFloat scale = dominantDimension / compDimension;
- xform = CATransform3DMakeScale(scale, scale, 1);
- } else {
- xform = CATransform3DIdentity;
- }
- [CATransaction begin];
- [CATransaction setDisableActions:YES];
- _compContainer.transform = CATransform3DIdentity;
- _compContainer.bounds = _sceneModel.compBounds;
- _compContainer.viewportBounds = _sceneModel.compBounds;
- _compContainer.transform = xform;
- _compContainer.position = centerPoint;
- [CATransaction commit];
- }
- # pragma mark - CAAnimationDelegate
- - (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)complete {
- if ([_compContainer animationForKey:kCompContainerAnimationKey] == anim &&
- [anim isKindOfClass:[CABasicAnimation class]]) {
- CABasicAnimation *playAnimation = (CABasicAnimation *)anim;
- NSNumber *frame = _compContainer.presentationLayer.currentFrame;
- if (complete) {
- // Set the final frame based on the animation to/from values. If playing forward, use the
- // toValue otherwise we want to end on the fromValue.
- frame = [self _isSpeedNegative] ? (NSNumber *)playAnimation.toValue : (NSNumber *)playAnimation.fromValue;
- }
- [self _removeCurrentAnimationIfNecessary];
- [self setProgressWithFrame:frame callCompletionIfNecessary:NO];
- [self _callCompletionIfNecessary:complete];
- }
- }
- # pragma mark - DEPRECATED
- - (void)addSubview:(nonnull LOTView *)view
- toLayerNamed:(nonnull NSString *)layer
- applyTransform:(BOOL)applyTransform {
- NSLog(@"%s: Function is DEPRECATED. Please use addSubview:forKeypathLayer:", __PRETTY_FUNCTION__);
- LOTKeypath *keypath = [LOTKeypath keypathWithString:layer];
- if (applyTransform) {
- [self addSubview:view toKeypathLayer:keypath];
- } else {
- [self maskSubview:view toKeypathLayer:keypath];
- }
- }
- - (CGRect)convertRect:(CGRect)rect
- toLayerNamed:(NSString *_Nullable)layerName {
- NSLog(@"%s: Function is DEPRECATED. Please use convertRect:forKeypathLayer:", __PRETTY_FUNCTION__);
- LOTKeypath *keypath = [LOTKeypath keypathWithString:layerName];
- return [self convertRect:rect toKeypathLayer:keypath];
- }
- - (void)setValue:(nonnull id)value
- forKeypath:(nonnull NSString *)keypath
- atFrame:(nullable NSNumber *)frame {
- NSLog(@"%s: Function is DEPRECATED and no longer functional. Please use setValueCallback:forKeypath:", __PRETTY_FUNCTION__);
- }
- - (void)logHierarchyKeypaths {
- NSArray *keypaths = [self keysForKeyPath:[LOTKeypath keypathWithString:@"**"]];
- for (NSString *keypath in keypaths) {
- NSLog(@"%@", keypath);
- }
- }
- @end
|