/** * Copyright (c) 2015-present, Facebook, Inc. * All rights reserved. * * This source code is licensed under the BSD-style license found in the * LICENSE file in the root directory of this source tree. An additional grant * of patent rights can be found in the PATENTS file in the same directory. */ #import "RCTNativeAnimatedNodesManager.h" #import #import "RCTAdditionAnimatedNode.h" #import "RCTAnimatedNode.h" #import "RCTAnimationDriver.h" #import "RCTDiffClampAnimatedNode.h" #import "RCTDivisionAnimatedNode.h" #import "RCTEventAnimation.h" #import "RCTFrameAnimation.h" #import "RCTDecayAnimation.h" #import "RCTInterpolationAnimatedNode.h" #import "RCTModuloAnimatedNode.h" #import "RCTMultiplicationAnimatedNode.h" #import "RCTPropsAnimatedNode.h" #import "RCTSpringAnimation.h" #import "RCTStyleAnimatedNode.h" #import "RCTTransformAnimatedNode.h" #import "RCTValueAnimatedNode.h" @implementation RCTNativeAnimatedNodesManager { RCTUIManager *_uiManager; NSMutableDictionary *_animationNodes; // Mapping of a view tag and an event name to a list of event animation drivers. 99% of the time // there will be only one driver per mapping so all code code should be optimized around that. NSMutableDictionary *> *_eventDrivers; NSMutableSet> *_activeAnimations; CADisplayLink *_displayLink; } - (instancetype)initWithUIManager:(nonnull RCTUIManager *)uiManager { if ((self = [super init])) { _uiManager = uiManager; _animationNodes = [NSMutableDictionary new]; _eventDrivers = [NSMutableDictionary new]; _activeAnimations = [NSMutableSet new]; } return self; } #pragma mark -- Graph - (void)createAnimatedNode:(nonnull NSNumber *)tag config:(NSDictionary *)config { static NSDictionary *map; static dispatch_once_t mapToken; dispatch_once(&mapToken, ^{ map = @{@"style" : [RCTStyleAnimatedNode class], @"value" : [RCTValueAnimatedNode class], @"props" : [RCTPropsAnimatedNode class], @"interpolation" : [RCTInterpolationAnimatedNode class], @"addition" : [RCTAdditionAnimatedNode class], @"diffclamp": [RCTDiffClampAnimatedNode class], @"division" : [RCTDivisionAnimatedNode class], @"multiplication" : [RCTMultiplicationAnimatedNode class], @"modulus" : [RCTModuloAnimatedNode class], @"transform" : [RCTTransformAnimatedNode class]}; }); NSString *nodeType = [RCTConvert NSString:config[@"type"]]; Class nodeClass = map[nodeType]; if (!nodeClass) { RCTLogError(@"Animated node type %@ not supported natively", nodeType); return; } RCTAnimatedNode *node = [[nodeClass alloc] initWithTag:tag config:config]; _animationNodes[tag] = node; [node setNeedsUpdate]; } - (void)connectAnimatedNodes:(nonnull NSNumber *)parentTag childTag:(nonnull NSNumber *)childTag { RCTAssertParam(parentTag); RCTAssertParam(childTag); RCTAnimatedNode *parentNode = _animationNodes[parentTag]; RCTAnimatedNode *childNode = _animationNodes[childTag]; RCTAssertParam(parentNode); RCTAssertParam(childNode); [parentNode addChild:childNode]; [childNode setNeedsUpdate]; } - (void)disconnectAnimatedNodes:(nonnull NSNumber *)parentTag childTag:(nonnull NSNumber *)childTag { RCTAssertParam(parentTag); RCTAssertParam(childTag); RCTAnimatedNode *parentNode = _animationNodes[parentTag]; RCTAnimatedNode *childNode = _animationNodes[childTag]; RCTAssertParam(parentNode); RCTAssertParam(childNode); [parentNode removeChild:childNode]; [childNode setNeedsUpdate]; } - (void)connectAnimatedNodeToView:(nonnull NSNumber *)nodeTag viewTag:(nonnull NSNumber *)viewTag viewName:(nonnull NSString *)viewName { RCTAnimatedNode *node = _animationNodes[nodeTag]; if ([node isKindOfClass:[RCTPropsAnimatedNode class]]) { [(RCTPropsAnimatedNode *)node connectToView:viewTag viewName:viewName uiManager:_uiManager]; } [node setNeedsUpdate]; } - (void)disconnectAnimatedNodeFromView:(nonnull NSNumber *)nodeTag viewTag:(nonnull NSNumber *)viewTag { RCTAnimatedNode *node = _animationNodes[nodeTag]; if ([node isKindOfClass:[RCTPropsAnimatedNode class]]) { [(RCTPropsAnimatedNode *)node disconnectFromView:viewTag]; } } - (void)restoreDefaultValues:(nonnull NSNumber *)nodeTag { RCTAnimatedNode *node = _animationNodes[nodeTag]; // Restoring default values needs to happen before UIManager operations so it is // possible the node hasn't been created yet if it is being connected and // disconnected in the same batch. In that case we don't need to restore // default values since it will never actually update the view. if (node == nil) { return; } if (![node isKindOfClass:[RCTPropsAnimatedNode class]]) { RCTLogError(@"Not a props node."); } [(RCTPropsAnimatedNode *)node restoreDefaultValues]; } - (void)dropAnimatedNode:(nonnull NSNumber *)tag { RCTAnimatedNode *node = _animationNodes[tag]; if (node) { [node detachNode]; [_animationNodes removeObjectForKey:tag]; } } #pragma mark -- Mutations - (void)setAnimatedNodeValue:(nonnull NSNumber *)nodeTag value:(nonnull NSNumber *)value { RCTAnimatedNode *node = _animationNodes[nodeTag]; if (![node isKindOfClass:[RCTValueAnimatedNode class]]) { RCTLogError(@"Not a value node."); return; } [self stopAnimationsForNode:node]; RCTValueAnimatedNode *valueNode = (RCTValueAnimatedNode *)node; valueNode.value = value.floatValue; [valueNode setNeedsUpdate]; } - (void)setAnimatedNodeOffset:(nonnull NSNumber *)nodeTag offset:(nonnull NSNumber *)offset { RCTAnimatedNode *node = _animationNodes[nodeTag]; if (![node isKindOfClass:[RCTValueAnimatedNode class]]) { RCTLogError(@"Not a value node."); return; } RCTValueAnimatedNode *valueNode = (RCTValueAnimatedNode *)node; [valueNode setOffset:offset.floatValue]; [valueNode setNeedsUpdate]; } - (void)flattenAnimatedNodeOffset:(nonnull NSNumber *)nodeTag { RCTAnimatedNode *node = _animationNodes[nodeTag]; if (![node isKindOfClass:[RCTValueAnimatedNode class]]) { RCTLogError(@"Not a value node."); return; } RCTValueAnimatedNode *valueNode = (RCTValueAnimatedNode *)node; [valueNode flattenOffset]; } - (void)extractAnimatedNodeOffset:(nonnull NSNumber *)nodeTag { RCTAnimatedNode *node = _animationNodes[nodeTag]; if (![node isKindOfClass:[RCTValueAnimatedNode class]]) { RCTLogError(@"Not a value node."); return; } RCTValueAnimatedNode *valueNode = (RCTValueAnimatedNode *)node; [valueNode extractOffset]; } #pragma mark -- Drivers - (void)startAnimatingNode:(nonnull NSNumber *)animationId nodeTag:(nonnull NSNumber *)nodeTag config:(NSDictionary *)config endCallback:(RCTResponseSenderBlock)callBack { RCTValueAnimatedNode *valueNode = (RCTValueAnimatedNode *)_animationNodes[nodeTag]; NSString *type = config[@"type"]; id animationDriver; if ([type isEqual:@"frames"]) { animationDriver = [[RCTFrameAnimation alloc] initWithId:animationId config:config forNode:valueNode callBack:callBack]; } else if ([type isEqual:@"spring"]) { animationDriver = [[RCTSpringAnimation alloc] initWithId:animationId config:config forNode:valueNode callBack:callBack]; } else if ([type isEqual:@"decay"]) { animationDriver = [[RCTDecayAnimation alloc] initWithId:animationId config:config forNode:valueNode callBack:callBack]; } else { RCTLogError(@"Unsupported animation type: %@", config[@"type"]); return; } [_activeAnimations addObject:animationDriver]; [animationDriver startAnimation]; [self startAnimationLoopIfNeeded]; } - (void)stopAnimation:(nonnull NSNumber *)animationId { for (id driver in _activeAnimations) { if ([driver.animationId isEqual:animationId]) { [driver stopAnimation]; [_activeAnimations removeObject:driver]; break; } } } - (void)stopAnimationsForNode:(nonnull RCTAnimatedNode *)node { NSMutableArray> *discarded = [NSMutableArray new]; for (id driver in _activeAnimations) { if ([driver.valueNode isEqual:node]) { [discarded addObject:driver]; } } for (id driver in discarded) { [driver stopAnimation]; [_activeAnimations removeObject:driver]; } } #pragma mark -- Events - (void)addAnimatedEventToView:(nonnull NSNumber *)viewTag eventName:(nonnull NSString *)eventName eventMapping:(NSDictionary *)eventMapping { NSNumber *nodeTag = [RCTConvert NSNumber:eventMapping[@"animatedValueTag"]]; RCTAnimatedNode *node = _animationNodes[nodeTag]; if (!node) { RCTLogError(@"Animated node with tag %@ does not exists", nodeTag); return; } if (![node isKindOfClass:[RCTValueAnimatedNode class]]) { RCTLogError(@"Animated node connected to event should be of type RCTValueAnimatedNode"); return; } NSArray *eventPath = [RCTConvert NSStringArray:eventMapping[@"nativeEventPath"]]; RCTEventAnimation *driver = [[RCTEventAnimation alloc] initWithEventPath:eventPath valueNode:(RCTValueAnimatedNode *)node]; NSString *key = [NSString stringWithFormat:@"%@%@", viewTag, eventName]; if (_eventDrivers[key] != nil) { [_eventDrivers[key] addObject:driver]; } else { NSMutableArray *drivers = [NSMutableArray new]; [drivers addObject:driver]; _eventDrivers[key] = drivers; } } - (void)removeAnimatedEventFromView:(nonnull NSNumber *)viewTag eventName:(nonnull NSString *)eventName animatedNodeTag:(nonnull NSNumber *)animatedNodeTag { NSString *key = [NSString stringWithFormat:@"%@%@", viewTag, eventName]; if (_eventDrivers[key] != nil) { if (_eventDrivers[key].count == 1) { [_eventDrivers removeObjectForKey:key]; } else { NSMutableArray *driversForKey = _eventDrivers[key]; for (NSUInteger i = 0; i < driversForKey.count; i++) { if (driversForKey[i].valueNode.nodeTag == animatedNodeTag) { [driversForKey removeObjectAtIndex:i]; break; } } } } } - (void)handleAnimatedEvent:(id)event { if (_eventDrivers.count == 0) { return; } NSString *key = [NSString stringWithFormat:@"%@%@", event.viewTag, event.eventName]; NSMutableArray *driversForKey = _eventDrivers[key]; if (driversForKey) { for (RCTEventAnimation *driver in driversForKey) { [self stopAnimationsForNode:driver.valueNode]; [driver updateWithEvent:event]; } [self updateAnimations]; } } #pragma mark -- Listeners - (void)startListeningToAnimatedNodeValue:(nonnull NSNumber *)tag valueObserver:(id)valueObserver { RCTAnimatedNode *node = _animationNodes[tag]; if ([node isKindOfClass:[RCTValueAnimatedNode class]]) { ((RCTValueAnimatedNode *)node).valueObserver = valueObserver; } } - (void)stopListeningToAnimatedNodeValue:(nonnull NSNumber *)tag { RCTAnimatedNode *node = _animationNodes[tag]; if ([node isKindOfClass:[RCTValueAnimatedNode class]]) { ((RCTValueAnimatedNode *)node).valueObserver = nil; } } #pragma mark -- Animation Loop - (void)startAnimationLoopIfNeeded { if (!_displayLink && _activeAnimations.count > 0) { _displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(stepAnimations:)]; [_displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes]; } } - (void)stopAnimationLoopIfNeeded { if (_activeAnimations.count == 0) { [self stopAnimationLoop]; } } - (void)stopAnimationLoop { if (_displayLink) { [_displayLink invalidate]; _displayLink = nil; } } - (void)stepAnimations:(CADisplayLink *)displaylink { NSTimeInterval time = displaylink.timestamp; for (id animationDriver in _activeAnimations) { [animationDriver stepAnimationWithTime:time]; } [self updateAnimations]; for (id animationDriver in [_activeAnimations copy]) { if (animationDriver.animationHasFinished) { [animationDriver stopAnimation]; [_activeAnimations removeObject:animationDriver]; } } [self stopAnimationLoopIfNeeded]; } #pragma mark -- Updates - (void)updateAnimations { [_animationNodes enumerateKeysAndObjectsUsingBlock:^(NSNumber *key, RCTAnimatedNode *node, BOOL *stop) { if (node.needsUpdate) { [node updateNodeIfNecessary]; } }]; } @end