/** * 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 "RCTNativeAnimatedModule.h" #import #import #import #import "RCTAdditionAnimatedNode.h" #import "RCTAnimationDriver.h" #import "RCTAnimationUtils.h" #import "RCTDiffClampAnimatedNode.h" #import "RCTDivisionAnimatedNode.h" #import "RCTEventAnimation.h" #import "RCTFrameAnimation.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 RCTNativeAnimatedModule { NSMutableDictionary *_animationNodes; NSMutableDictionary> *_animationDrivers; NSMutableDictionary *_eventAnimationDrivers; NSMutableSet> *_activeAnimations; NSMutableSet> *_finishedAnimations; NSMutableSet *_updatedValueNodes; NSMutableSet *_propAnimationNodes; CADisplayLink *_displayLink; } RCT_EXPORT_MODULE() - (void)setBridge:(RCTBridge *)bridge { [super setBridge:bridge]; _animationNodes = [NSMutableDictionary new]; _animationDrivers = [NSMutableDictionary new]; _eventAnimationDrivers = [NSMutableDictionary new]; _activeAnimations = [NSMutableSet new]; _finishedAnimations = [NSMutableSet new]; _updatedValueNodes = [NSMutableSet new]; _propAnimationNodes = [NSMutableSet new]; [bridge.eventDispatcher addDispatchObserver:self]; } - (void)dealloc { [self.bridge.eventDispatcher removeDispatchObserver:self]; } - (dispatch_queue_t)methodQueue { return dispatch_get_main_queue(); } - (NSArray *)supportedEvents { return @[@"onAnimatedValueUpdate"]; } RCT_EXPORT_METHOD(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; if ([node isKindOfClass:[RCTPropsAnimatedNode class]]) { [_propAnimationNodes addObject:(RCTPropsAnimatedNode *)node]; } } RCT_EXPORT_METHOD(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]; } RCT_EXPORT_METHOD(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]; } RCT_EXPORT_METHOD(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 { RCTLogError(@"Unsupported animation type: %@", config[@"type"]); return; } [_activeAnimations addObject:animationDriver]; _animationDrivers[animationId] = animationDriver; [animationDriver startAnimation]; [self startAnimationLoopIfNeeded]; } RCT_EXPORT_METHOD(stopAnimation:(nonnull NSNumber *)animationId) { id driver = _animationDrivers[animationId]; if (driver) { [driver removeAnimation]; [_animationDrivers removeObjectForKey:animationId]; [_activeAnimations removeObject:driver]; [_finishedAnimations removeObject:driver]; } } RCT_EXPORT_METHOD(setAnimatedNodeValue:(nonnull NSNumber *)nodeTag value:(nonnull NSNumber *)value) { RCTAnimatedNode *node = _animationNodes[nodeTag]; if (![node isKindOfClass:[RCTValueAnimatedNode class]]) { RCTLogError(@"Not a value node."); return; } RCTValueAnimatedNode *valueNode = (RCTValueAnimatedNode *)node; valueNode.value = value.floatValue; [valueNode setNeedsUpdate]; [self updateViewsProps]; [valueNode cleanupAnimationUpdate]; } RCT_EXPORT_METHOD(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]; [_updatedValueNodes addObject:valueNode]; } RCT_EXPORT_METHOD(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]; } RCT_EXPORT_METHOD(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]; } RCT_EXPORT_METHOD(connectAnimatedNodeToView:(nonnull NSNumber *)nodeTag viewTag:(nonnull NSNumber *)viewTag) { RCTAnimatedNode *node = _animationNodes[nodeTag]; if (viewTag && [node isKindOfClass:[RCTPropsAnimatedNode class]]) { [(RCTPropsAnimatedNode *)node connectToView:viewTag animatedModule:self]; } } RCT_EXPORT_METHOD(disconnectAnimatedNodeFromView:(nonnull NSNumber *)nodeTag viewTag:(nonnull NSNumber *)viewTag) { RCTAnimatedNode *node = _animationNodes[nodeTag]; if (viewTag && node && [node isKindOfClass:[RCTPropsAnimatedNode class]]) { [(RCTPropsAnimatedNode *)node disconnectFromView:viewTag]; } } RCT_EXPORT_METHOD(dropAnimatedNode:(nonnull NSNumber *)tag) { RCTAnimatedNode *node = _animationNodes[tag]; if (node) { [node detachNode]; [_animationNodes removeObjectForKey:tag]; if ([node isKindOfClass:[RCTValueAnimatedNode class]]) { [_updatedValueNodes removeObject:(RCTValueAnimatedNode *)node]; } else if ([node isKindOfClass:[RCTPropsAnimatedNode class]]) { [_propAnimationNodes removeObject:(RCTPropsAnimatedNode *)node]; } } } RCT_EXPORT_METHOD(startListeningToAnimatedNodeValue:(nonnull NSNumber *)tag) { RCTAnimatedNode *node = _animationNodes[tag]; if (node && [node isKindOfClass:[RCTValueAnimatedNode class]]) { ((RCTValueAnimatedNode *)node).valueObserver = self; } } RCT_EXPORT_METHOD(stopListeningToAnimatedNodeValue:(nonnull NSNumber *)tag) { RCTAnimatedNode *node = _animationNodes[tag]; if (node && [node isKindOfClass:[RCTValueAnimatedNode class]]) { ((RCTValueAnimatedNode *)node).valueObserver = nil; } } RCT_EXPORT_METHOD(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]; _eventAnimationDrivers[[NSString stringWithFormat:@"%@%@", viewTag, eventName]] = driver; } RCT_EXPORT_METHOD(removeAnimatedEventFromView:(nonnull NSNumber *)viewTag eventName:(nonnull NSString *)eventName) { [_eventAnimationDrivers removeObjectForKey:[NSString stringWithFormat:@"%@%@", viewTag, eventName]]; } - (void)animatedNode:(RCTValueAnimatedNode *)node didUpdateValue:(CGFloat)value { [self sendEventWithName:@"onAnimatedValueUpdate" body:@{@"tag": node.nodeTag, @"value": @(value)}]; } - (void)eventDispatcherWillDispatchEvent:(id)event { // Native animated events only work for events dispatched from the main queue. if (!RCTIsMainQueue() || _eventAnimationDrivers.count == 0) { return; } NSString *key = [NSString stringWithFormat:@"%@%@", event.viewTag, event.eventName]; RCTEventAnimation *driver = _eventAnimationDrivers[key]; if (driver) { [driver updateWithEvent:event]; [self updateViewsProps]; [driver.valueNode cleanupAnimationUpdate]; } } - (void)updateViewsProps { for (RCTPropsAnimatedNode *propsNode in _propAnimationNodes) { [propsNode updateNodeIfNecessary]; } } #pragma mark -- Animation Loop - (void)startAnimationLoopIfNeeded { if (!_displayLink && (_activeAnimations.count > 0 || _updatedValueNodes.count > 0)) { _displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(updateAnimations)]; [_displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes]; } } - (void)stopAnimationLoopIfNeeded { if (_displayLink && _activeAnimations.count == 0 && _updatedValueNodes.count == 0) { [_displayLink invalidate]; _displayLink = nil; } } - (void)updateAnimations { // Step Current active animations // This also recursively marks children nodes as needing update for (id animationDriver in _activeAnimations) { [animationDriver stepAnimation]; } // Perform node updates for marked nodes. // At this point all nodes that are in need of an update are properly marked as such. for (RCTPropsAnimatedNode *propsNode in _propAnimationNodes) { [propsNode updateNodeIfNecessary]; } // Cleanup nodes and prepare for next cycle. Remove updated nodes from bucket. for (id driverNode in _activeAnimations) { [driverNode cleanupAnimationUpdate]; } for (RCTValueAnimatedNode *valueNode in _updatedValueNodes) { [valueNode cleanupAnimationUpdate]; } [_updatedValueNodes removeAllObjects]; for (id driverNode in _activeAnimations) { if (driverNode.animationHasFinished) { [driverNode removeAnimation]; [_finishedAnimations addObject:driverNode]; } } for (id driverNode in _finishedAnimations) { [_activeAnimations removeObject:driverNode]; [_animationDrivers removeObjectForKey:driverNode.animationId]; } [_finishedAnimations removeAllObjects]; [self stopAnimationLoopIfNeeded]; } @end