Files
keyboard/Pods/lottie-ios/lottie-ios/Classes/Private/LOTAnimationView.m
2025-11-04 13:50:32 +08:00

917 lines
30 KiB
Objective-C

//
// 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"
#define kCompContainerTransformKey @"compContainerTransformKey"
#define kCompContainerPositionKey @"compContainerPositionKey"
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 {
return [self initWithContentsOfURL:url errorBlock:nil];
}
- (instancetype)initWithContentsOfURL:(NSURL *)url
errorBlock:(nullable LOTInitializationErrorBlock)errorBlock {
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) {
NSError *error;
NSData *animationData = [NSData dataWithContentsOfURL:url options:0 error:&error];
if (error || !animationData) {
if (errorBlock) {
errorBlock(error);
}
return;
}
NSDictionary *animationJSON = [NSJSONSerialization JSONObjectWithData:animationData
options:0 error:&error];
if (error || !animationJSON) {
if (errorBlock) {
errorBlock(error);
}
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;
_repeatCount = 1;
_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;
_repeatCount = 1;
_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 || _repeatCount < 0 ? HUGE_VALF :_repeatCount;
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 {
#if TARGET_OS_VISION
_compContainer.rasterizationScale = 2;
#else
_compContainer.rasterizationScale = self.window.screen.scale;
#endif
}
- (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;
}
// Support use LOTAnimationView with UIView:animate
NSArray *animKey = self.layer.animationKeys;
CAAnimation *animation;
if (animKey && animKey.count > 0) {
animation = [self.layer animationForKey:animKey[0]];
}
if (animation) {
[_compContainer removeAnimationForKey:kCompContainerPositionKey];
[_compContainer removeAnimationForKey:kCompContainerTransformKey];
id positionAnimCopy = animation.copy;
CABasicAnimation *positionAnimation;
if (positionAnimCopy && [positionAnimCopy isKindOfClass:CABasicAnimation.class]) {
positionAnimation = (CABasicAnimation *)positionAnimCopy;
} else {
positionAnimation = [CABasicAnimation animationWithKeyPath:@"position"];
}
positionAnimation.keyPath = @"position";
positionAnimation.additive = NO;
positionAnimation.fromValue = [NSValue valueWithCGPoint:_compContainer.presentationLayer.position];
positionAnimation.toValue = [NSValue valueWithCGPoint:centerPoint];
positionAnimation.removedOnCompletion = YES;
id xformAnimCopy = animation.copy;
CABasicAnimation *xformAnimation;
if (xformAnimCopy && [xformAnimCopy isKindOfClass:CABasicAnimation.class]) {
xformAnimation = (CABasicAnimation *)xformAnimCopy;
} else {
xformAnimCopy = [CABasicAnimation animationWithKeyPath:@"transform"];
}
xformAnimation.keyPath = @"transform";
xformAnimation.additive = NO;
xformAnimation.fromValue = [NSValue valueWithCATransform3D:_compContainer.presentationLayer.transform];
xformAnimation.toValue = [NSValue valueWithCATransform3D:xform];
xformAnimation.removedOnCompletion = YES;
_compContainer.transform = CATransform3DIdentity;
_compContainer.bounds = _sceneModel.compBounds;
_compContainer.viewportBounds = _sceneModel.compBounds;
_compContainer.position = centerPoint;
_compContainer.transform = xform;
_compContainer.anchorPoint = self.layer.anchorPoint;
[_compContainer addAnimation:positionAnimation forKey:kCompContainerPositionKey];
[_compContainer addAnimation:xformAnimation forKey:kCompContainerTransformKey];
} else {
[CATransaction begin];
[CATransaction setDisableActions:YES];
CATransaction.animationDuration = 0;
_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