/** * 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 "RCTNativeAnimatedNodesManager.h" typedef void (^AnimatedOperation)(RCTNativeAnimatedNodesManager *nodesManager); @implementation RCTNativeAnimatedModule { RCTNativeAnimatedNodesManager *_nodesManager; // Oparations called after views have been updated. NSMutableArray *_operations; // Operations called before views have been updated. NSMutableArray *_preOperations; } RCT_EXPORT_MODULE(); - (void)invalidate { [_nodesManager stopAnimationLoop]; [self.bridge.eventDispatcher removeDispatchObserver:self]; [self.bridge.uiManager.observerCoordinator removeObserver:self]; } - (dispatch_queue_t)methodQueue { // This module needs to be on the same queue as the UIManager to avoid // having to lock `_operations` and `_preOperations` since `uiManagerWillFlushUIBlocks` // will be called from that queue. return RCTGetUIManagerQueue(); } - (void)setBridge:(RCTBridge *)bridge { [super setBridge:bridge]; _nodesManager = [[RCTNativeAnimatedNodesManager alloc] initWithUIManager:self.bridge.uiManager]; _operations = [NSMutableArray new]; _preOperations = [NSMutableArray new]; [bridge.eventDispatcher addDispatchObserver:self]; [bridge.uiManager.observerCoordinator addObserver:self]; } #pragma mark -- API RCT_EXPORT_METHOD(createAnimatedNode:(nonnull NSNumber *)tag config:(NSDictionary *)config) { [self addOperationBlock:^(RCTNativeAnimatedNodesManager *nodesManager) { [nodesManager createAnimatedNode:tag config:config]; }]; } RCT_EXPORT_METHOD(connectAnimatedNodes:(nonnull NSNumber *)parentTag childTag:(nonnull NSNumber *)childTag) { [self addOperationBlock:^(RCTNativeAnimatedNodesManager *nodesManager) { [nodesManager connectAnimatedNodes:parentTag childTag:childTag]; }]; } RCT_EXPORT_METHOD(disconnectAnimatedNodes:(nonnull NSNumber *)parentTag childTag:(nonnull NSNumber *)childTag) { [self addOperationBlock:^(RCTNativeAnimatedNodesManager *nodesManager) { [nodesManager disconnectAnimatedNodes:parentTag childTag:childTag]; }]; } RCT_EXPORT_METHOD(startAnimatingNode:(nonnull NSNumber *)animationId nodeTag:(nonnull NSNumber *)nodeTag config:(NSDictionary *)config endCallback:(RCTResponseSenderBlock)callBack) { [self addOperationBlock:^(RCTNativeAnimatedNodesManager *nodesManager) { [nodesManager startAnimatingNode:animationId nodeTag:nodeTag config:config endCallback:callBack]; }]; } RCT_EXPORT_METHOD(stopAnimation:(nonnull NSNumber *)animationId) { [self addOperationBlock:^(RCTNativeAnimatedNodesManager *nodesManager) { [nodesManager stopAnimation:animationId]; }]; } RCT_EXPORT_METHOD(setAnimatedNodeValue:(nonnull NSNumber *)nodeTag value:(nonnull NSNumber *)value) { [self addOperationBlock:^(RCTNativeAnimatedNodesManager *nodesManager) { [nodesManager setAnimatedNodeValue:nodeTag value:value]; }]; } RCT_EXPORT_METHOD(setAnimatedNodeOffset:(nonnull NSNumber *)nodeTag offset:(nonnull NSNumber *)offset) { [self addOperationBlock:^(RCTNativeAnimatedNodesManager *nodesManager) { [nodesManager setAnimatedNodeOffset:nodeTag offset:offset]; }]; } RCT_EXPORT_METHOD(flattenAnimatedNodeOffset:(nonnull NSNumber *)nodeTag) { [self addOperationBlock:^(RCTNativeAnimatedNodesManager *nodesManager) { [nodesManager flattenAnimatedNodeOffset:nodeTag]; }]; } RCT_EXPORT_METHOD(extractAnimatedNodeOffset:(nonnull NSNumber *)nodeTag) { [self addOperationBlock:^(RCTNativeAnimatedNodesManager *nodesManager) { [nodesManager extractAnimatedNodeOffset:nodeTag]; }]; } RCT_EXPORT_METHOD(connectAnimatedNodeToView:(nonnull NSNumber *)nodeTag viewTag:(nonnull NSNumber *)viewTag) { NSString *viewName = [self.bridge.uiManager viewNameForReactTag:viewTag]; [self addOperationBlock:^(RCTNativeAnimatedNodesManager *nodesManager) { [nodesManager connectAnimatedNodeToView:nodeTag viewTag:viewTag viewName:viewName]; }]; } RCT_EXPORT_METHOD(disconnectAnimatedNodeFromView:(nonnull NSNumber *)nodeTag viewTag:(nonnull NSNumber *)viewTag) { // Disconnecting a view also restores its default values so we have to make // sure this happens before views get updated with their new props. This is // why we enqueue this on the pre-operations queue. [self addPreOperationBlock:^(RCTNativeAnimatedNodesManager *nodesManager) { [nodesManager disconnectAnimatedNodeFromView:nodeTag viewTag:viewTag]; }]; } RCT_EXPORT_METHOD(dropAnimatedNode:(nonnull NSNumber *)tag) { [self addOperationBlock:^(RCTNativeAnimatedNodesManager *nodesManager) { [nodesManager dropAnimatedNode:tag]; }]; } RCT_EXPORT_METHOD(startListeningToAnimatedNodeValue:(nonnull NSNumber *)tag) { __weak id valueObserver = self; [self addOperationBlock:^(RCTNativeAnimatedNodesManager *nodesManager) { [nodesManager startListeningToAnimatedNodeValue:tag valueObserver:valueObserver]; }]; } RCT_EXPORT_METHOD(stopListeningToAnimatedNodeValue:(nonnull NSNumber *)tag) { [self addOperationBlock:^(RCTNativeAnimatedNodesManager *nodesManager) { [nodesManager stopListeningToAnimatedNodeValue:tag]; }]; } RCT_EXPORT_METHOD(addAnimatedEventToView:(nonnull NSNumber *)viewTag eventName:(nonnull NSString *)eventName eventMapping:(NSDictionary *)eventMapping) { [self addOperationBlock:^(RCTNativeAnimatedNodesManager *nodesManager) { [nodesManager addAnimatedEventToView:viewTag eventName:eventName eventMapping:eventMapping]; }]; } RCT_EXPORT_METHOD(removeAnimatedEventFromView:(nonnull NSNumber *)viewTag eventName:(nonnull NSString *)eventName animatedNodeTag:(nonnull NSNumber *)animatedNodeTag) { [self addOperationBlock:^(RCTNativeAnimatedNodesManager *nodesManager) { [nodesManager removeAnimatedEventFromView:viewTag eventName:eventName animatedNodeTag:animatedNodeTag]; }]; } #pragma mark -- Batch handling - (void)addOperationBlock:(AnimatedOperation)operation { [_operations addObject:operation]; } - (void)addPreOperationBlock:(AnimatedOperation)operation { [_preOperations addObject:operation]; } #pragma mark - RCTUIManagerObserver - (void)uiManagerWillFlushUIBlocks:(RCTUIManager *)uiManager { if (_preOperations.count == 0 && _operations.count == 0) { return; } NSArray *preOperations = _preOperations; NSArray *operations = _operations; _preOperations = [NSMutableArray new]; _operations = [NSMutableArray new]; [uiManager prependUIBlock:^(__unused RCTUIManager *manager, __unused NSDictionary *viewRegistry) { for (AnimatedOperation operation in preOperations) { operation(self->_nodesManager); } }]; [uiManager addUIBlock:^(__unused RCTUIManager *manager, __unused NSDictionary *viewRegistry) { for (AnimatedOperation operation in operations) { operation(self->_nodesManager); } [self->_nodesManager updateAnimations]; }]; } #pragma mark -- Events - (NSArray *)supportedEvents { return @[@"onAnimatedValueUpdate"]; } - (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()) { return; } return [_nodesManager handleAnimatedEvent:event]; } @end