Add support for native animated events on iOS

Summary:
This adds native support for `Animated.event` on iOS.

**Test plan**
Tested in the native animated UIExplorer example that it works properly like on Android.
Closes https://github.com/facebook/react-native/pull/9598

Differential Revision: D4110331

fbshipit-source-id: 15748d23d0f475f2bcd1040ca3dca33e2620f058
This commit is contained in:
James Ide 2016-11-01 03:56:50 -07:00 committed by Facebook Github Bot
parent 0fe1c7a9ff
commit fc11a5fde8
9 changed files with 215 additions and 18 deletions

View File

@ -191,6 +191,7 @@ class EventExample extends React.Component {
<Animated.ScrollView
horizontal
style={{ height: 100, marginTop: 16 }}
scrollEventThrottle={16}
onScroll={
Animated.event([{
nativeEvent: { contentOffset: { x: this.state.scrollX } }
@ -462,6 +463,14 @@ exports.examples = [
);
},
},
{
title: 'Animated events',
render: function() {
return (
<EventExample />
);
},
},
{
title: 'Internal Settings',
render: function() {
@ -470,13 +479,4 @@ exports.examples = [
);
},
},
{
title: 'Animated events',
platform: 'android',
render: function() {
return (
<EventExample />
);
},
},
];

View File

@ -0,0 +1,24 @@
/**
* 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 <Foundation/Foundation.h>
#import "RCTValueAnimatedNode.h"
#import "RCTEventDispatcher.h"
@interface RCTEventAnimation : NSObject
@property (nonatomic, readonly, weak) RCTValueAnimatedNode *valueNode;
- (instancetype)initWithEventPath:(NSArray<NSString *> *)eventPath
valueNode:(RCTValueAnimatedNode *)valueNode;
- (void)updateWithEvent:(id<RCTEvent>)event;
@end

View File

@ -0,0 +1,40 @@
/**
* 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 "RCTEventAnimation.h"
@implementation RCTEventAnimation
{
NSArray<NSString *> *_eventPath;
}
- (instancetype)initWithEventPath:(NSArray<NSString *> *)eventPath
valueNode:(RCTValueAnimatedNode *)valueNode
{
if ((self = [super init])) {
_eventPath = eventPath;
_valueNode = valueNode;
}
return self;
}
- (void)updateWithEvent:(id<RCTEvent>)event
{
NSArray *args = event.arguments;
// Supported events args are in the following order: viewTag, eventName, eventData.
id currentValue = args[2];
for (NSString *key in _eventPath) {
currentValue = [currentValue valueForKey:key];
}
_valueNode.value = ((NSNumber *)currentValue).doubleValue;
[_valueNode setNeedsUpdate];
}
@end

View File

@ -19,6 +19,8 @@
13E501EF1D07A6C9005F35D8 /* RCTTransformAnimatedNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 13E501E51D07A6C9005F35D8 /* RCTTransformAnimatedNode.m */; };
13E501F01D07A6C9005F35D8 /* RCTValueAnimatedNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 13E501E71D07A6C9005F35D8 /* RCTValueAnimatedNode.m */; };
193F64F41D776EC6004D1CAA /* RCTDiffClampAnimatedNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 193F64F31D776EC6004D1CAA /* RCTDiffClampAnimatedNode.m */; };
19F00F221DC8847500113FEE /* RCTEventAnimation.m in Sources */ = {isa = PBXBuildFile; fileRef = 19F00F211DC8847500113FEE /* RCTEventAnimation.m */; };
19F00F231DC8848E00113FEE /* RCTEventAnimation.m in Sources */ = {isa = PBXBuildFile; fileRef = 19F00F211DC8847500113FEE /* RCTEventAnimation.m */; };
2D3B5EF21D9B0B3100451313 /* RCTAnimationUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = 13E501B81D07A644005F35D8 /* RCTAnimationUtils.m */; };
2D3B5EF31D9B0B3400451313 /* RCTViewPropertyMapper.m in Sources */ = {isa = PBXBuildFile; fileRef = 13E501C81D07A644005F35D8 /* RCTViewPropertyMapper.m */; };
2D3B5EF41D9B0B3700451313 /* RCTNativeAnimatedModule.m in Sources */ = {isa = PBXBuildFile; fileRef = 13E501BE1D07A644005F35D8 /* RCTNativeAnimatedModule.m */; };
@ -88,6 +90,8 @@
13E501E71D07A6C9005F35D8 /* RCTValueAnimatedNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTValueAnimatedNode.m; sourceTree = "<group>"; };
193F64F21D776EC6004D1CAA /* RCTDiffClampAnimatedNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTDiffClampAnimatedNode.h; sourceTree = "<group>"; };
193F64F31D776EC6004D1CAA /* RCTDiffClampAnimatedNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTDiffClampAnimatedNode.m; sourceTree = "<group>"; };
19F00F201DC8847500113FEE /* RCTEventAnimation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTEventAnimation.h; sourceTree = "<group>"; };
19F00F211DC8847500113FEE /* RCTEventAnimation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTEventAnimation.m; sourceTree = "<group>"; };
2D2A28201D9B03D100D4039D /* libRCTAnimation-tvOS.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libRCTAnimation-tvOS.a"; sourceTree = BUILT_PRODUCTS_DIR; };
5C9894931D999639008027DB /* RCTDivisionAnimatedNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTDivisionAnimatedNode.h; sourceTree = "<group>"; };
5C9894941D999639008027DB /* RCTDivisionAnimatedNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTDivisionAnimatedNode.m; sourceTree = "<group>"; };
@ -175,6 +179,8 @@
isa = PBXGroup;
children = (
94C1294A1D4069170025F25C /* RCTAnimationDriver.h */,
19F00F201DC8847500113FEE /* RCTEventAnimation.h */,
19F00F211DC8847500113FEE /* RCTEventAnimation.m */,
94C1294C1D4069170025F25C /* RCTFrameAnimation.h */,
94C1294D1D4069170025F25C /* RCTFrameAnimation.m */,
94C1294E1D4069170025F25C /* RCTSpringAnimation.h */,
@ -266,6 +272,7 @@
2D3B5EF21D9B0B3100451313 /* RCTAnimationUtils.m in Sources */,
2D3B5EF51D9B0B4800451313 /* RCTDivisionAnimatedNode.m in Sources */,
2D3B5EF71D9B0B4800451313 /* RCTAdditionAnimatedNode.m in Sources */,
19F00F231DC8848E00113FEE /* RCTEventAnimation.m in Sources */,
2D3B5EF41D9B0B3700451313 /* RCTNativeAnimatedModule.m in Sources */,
2D3B5EF61D9B0B4800451313 /* RCTDiffClampAnimatedNode.m in Sources */,
2D3B5EF81D9B0B4800451313 /* RCTAnimatedNode.m in Sources */,
@ -289,6 +296,7 @@
13E501F01D07A6C9005F35D8 /* RCTValueAnimatedNode.m in Sources */,
94DAE3F91D7334A70059942F /* RCTModuloAnimatedNode.m in Sources */,
193F64F41D776EC6004D1CAA /* RCTDiffClampAnimatedNode.m in Sources */,
19F00F221DC8847500113FEE /* RCTEventAnimation.m in Sources */,
13E501EE1D07A6C9005F35D8 /* RCTStyleAnimatedNode.m in Sources */,
13E501CC1D07A644005F35D8 /* RCTAnimationUtils.m in Sources */,
13E501CF1D07A644005F35D8 /* RCTNativeAnimatedModule.m in Sources */,

View File

@ -9,7 +9,8 @@
#import "RCTBridgeModule.h"
#import "RCTValueAnimatedNode.h"
#import "RCTEventEmitter.h"
#import "RCTEventDispatcher.h"
@interface RCTNativeAnimatedModule : RCTEventEmitter <RCTBridgeModule, RCTValueAnimatedNodeObserver>
@interface RCTNativeAnimatedModule : RCTEventEmitter <RCTBridgeModule, RCTValueAnimatedNodeObserver, RCTEventDispatcherObserver>
@end

View File

@ -15,6 +15,7 @@
#import "RCTAnimationUtils.h"
#import "RCTBridge.h"
#import "RCTConvert.h"
#import "RCTEventAnimation.h"
#import "RCTInterpolationAnimatedNode.h"
#import "RCTLog.h"
#import "RCTDiffClampAnimatedNode.h"
@ -30,6 +31,7 @@
{
NSMutableDictionary<NSNumber *, RCTAnimatedNode *> *_animationNodes;
NSMutableDictionary<NSNumber *, id<RCTAnimationDriver>> *_animationDrivers;
NSMutableDictionary<NSString *, RCTEventAnimation *> *_eventAnimationDrivers;
NSMutableSet<id<RCTAnimationDriver>> *_activeAnimations;
NSMutableSet<id<RCTAnimationDriver>> *_finishedAnimations;
NSMutableSet<RCTValueAnimatedNode *> *_updatedValueNodes;
@ -45,10 +47,18 @@ RCT_EXPORT_MODULE()
_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];
}
@ -157,7 +167,7 @@ RCT_EXPORT_METHOD(startAnimatingNode:(nonnull NSNumber *)animationId
[_activeAnimations addObject:animationDriver];
_animationDrivers[animationId] = animationDriver;
[animationDriver startAnimation];
[self startAnimation];
[self startAnimationLoopIfNeeded];
}
RCT_EXPORT_METHOD(stopAnimation:(nonnull NSNumber *)animationId)
@ -263,12 +273,64 @@ RCT_EXPORT_METHOD(stopListeningToAnimatedNodeValue:(nonnull NSNumber *)tag)
}
}
RCT_EXPORT_METHOD(addAnimatedEventToView:(nonnull NSNumber *)viewTag
eventName:(nonnull NSString *)eventName
eventMapping:(NSDictionary<NSString *, id> *)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<NSString *> *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)}];
}
- (BOOL)eventDispatcherWillDispatchEvent:(id<RCTEvent>)event
{
// Native animated events only work for events dispatched from the main queue.
if (!RCTIsMainQueue() || _eventAnimationDrivers.count == 0) {
return NO;
}
NSString *key = [NSString stringWithFormat:@"%@%@", event.viewTag, event.eventName];
RCTEventAnimation *driver = _eventAnimationDrivers[key];
if (driver) {
[driver updateWithEvent:event];
[self updateViewsProps];
[driver.valueNode cleanupAnimationUpdate];
return YES;
}
return NO;
}
- (void)updateViewsProps
{
for (RCTPropsAnimatedNode *propsNode in _propAnimationNodes) {
@ -278,14 +340,22 @@ RCT_EXPORT_METHOD(stopListeningToAnimatedNodeValue:(nonnull NSNumber *)tag)
#pragma mark -- Animation Loop
- (void)startAnimation
- (void)startAnimationLoopIfNeeded
{
if (!_displayLink && _activeAnimations.count > 0) {
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
@ -321,10 +391,7 @@ RCT_EXPORT_METHOD(stopListeningToAnimatedNodeValue:(nonnull NSNumber *)tag)
}
[_finishedAnimations removeAllObjects];
if (_activeAnimations.count == 0) {
[_displayLink invalidate];
_displayLink = nil;
}
[self stopAnimationLoopIfNeeded];
}
@end

View File

@ -56,7 +56,7 @@ Pod::Spec.new do |s|
s.subspec 'RCTAnimation' do |ss|
ss.dependency 'React/Core'
ss.source_files = "Libraries/NativeAnimation/{Nodes/*,*}.{h,m}"
ss.source_files = "Libraries/NativeAnimation/{Drivers/*,Nodes/*,*}.{h,m}"
end
s.subspec 'RCTCameraRoll' do |ss|

View File

@ -51,6 +51,20 @@ RCT_EXTERN NSString *RCTNormalizeInputEventName(NSString *eventName);
@end
/**
* This protocol allows observing events dispatched by RCTEventDispatcher.
*/
@protocol RCTEventDispatcherObserver <NSObject>
/**
* Called before dispatching an event, on the same thread the event was
* dispatched from. Return YES if the event was handled and must not be
* sent to JS.
*/
- (BOOL)eventDispatcherWillDispatchEvent:(id<RCTEvent>)event;
@end
/**
* This class wraps the -[RCTBridge enqueueJSCall:args:] method, and
@ -93,6 +107,16 @@ __deprecated_msg("Use RCTDirectEventBlock or RCTBubblingEventBlock instead");
*/
- (void)sendEvent:(id<RCTEvent>)event;
/**
* Add an event dispatcher observer.
*/
- (void)addDispatchObserver:(id<RCTEventDispatcherObserver>)observer;
/**
* Remove an event dispatcher observer.
*/
- (void)removeDispatchObserver:(id<RCTEventDispatcherObserver>)observer;
@end
@interface RCTBridge (RCTEventDispatcher)

View File

@ -46,6 +46,8 @@ static NSNumber *RCTGetEventID(id<RCTEvent> event)
// This array contains ids of events in order they come in, so we can emit them to JS in the exact same order.
NSMutableArray<NSNumber *> *_eventQueue;
BOOL _eventsDispatchScheduled;
NSMutableArray<id<RCTEventDispatcherObserver>> *_observers;
NSLock *_observersLock;
}
@synthesize bridge = _bridge;
@ -59,6 +61,8 @@ RCT_EXPORT_MODULE()
_eventQueue = [NSMutableArray new];
_eventQueueLock = [NSLock new];
_eventsDispatchScheduled = NO;
_observers = [NSMutableArray new];
_observersLock = [NSLock new];
}
- (void)sendAppEventWithName:(NSString *)name body:(id)body
@ -140,6 +144,21 @@ RCT_EXPORT_MODULE()
- (void)sendEvent:(id<RCTEvent>)event
{
[_observersLock lock];
BOOL eventHandled = NO;
for (id<RCTEventDispatcherObserver> observer in _observers) {
if ([observer eventDispatcherWillDispatchEvent:event]) {
eventHandled = YES;
}
}
[_observersLock unlock];
if (eventHandled) {
return;
}
[_eventQueueLock lock];
NSNumber *eventID = RCTGetEventID(event);
@ -171,6 +190,20 @@ RCT_EXPORT_MODULE()
}
}
- (void)addDispatchObserver:(id<RCTEventDispatcherObserver>)observer
{
[_observersLock lock];
[_observers addObject:observer];
[_observersLock unlock];
}
- (void)removeDispatchObserver:(id<RCTEventDispatcherObserver>)observer
{
[_observersLock lock];
[_observers removeObject:observer];
[_observersLock unlock];
}
- (void)dispatchEvent:(id<RCTEvent>)event
{
[_bridge enqueueJSCall:[[event class] moduleDotMethod] args:[event arguments]];