Permalink
Browse files

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 #9598

Differential Revision: D4110331

fbshipit-source-id: 15748d23d0f475f2bcd1040ca3dca33e2620f058
  • Loading branch information...
James Ide Facebook Github Bot
James Ide authored and Facebook Github Bot committed Nov 1, 2016
1 parent 0fe1c7a commit fc11a5fde847f40f446823ac2bbb0d2e64236306
@@ -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 } }
@@ -463,19 +464,18 @@ exports.examples = [
},
},
{
- title: 'Internal Settings',
+ title: 'Animated events',
render: function() {
return (
- <InternalSettings />
+ <EventExample />
);
},
},
{
- title: 'Animated events',
- platform: 'android',
+ title: 'Internal Settings',
render: function() {
return (
- <EventExample />
+ <InternalSettings />
);
},
},
@@ -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
@@ -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
@@ -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 */,
@@ -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
@@ -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 @@ @implementation RCTNativeAnimatedModule
{
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 @@ - (void)setBridge:(RCTBridge *)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];
}
@@ -157,7 +167,7 @@ - (dispatch_queue_t)methodQueue
[_activeAnimations addObject:animationDriver];
_animationDrivers[animationId] = animationDriver;
[animationDriver startAnimation];
- [self startAnimation];
+ [self startAnimationLoopIfNeeded];
}
RCT_EXPORT_METHOD(stopAnimation:(nonnull NSNumber *)animationId)
@@ -263,12 +273,64 @@ - (dispatch_queue_t)methodQueue
}
}
+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 @@ - (void)updateViewsProps
#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 @@ - (void)updateAnimations
}
[_finishedAnimations removeAllObjects];
- if (_activeAnimations.count == 0) {
- [_displayLink invalidate];
- _displayLink = nil;
- }
+ [self stopAnimationLoopIfNeeded];
}
@end
View
@@ -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|
@@ -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)
Oops, something went wrong.

0 comments on commit fc11a5f

Please sign in to comment.