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:
parent
0fe1c7a9ff
commit
fc11a5fde8
|
@ -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 />
|
||||
);
|
||||
},
|
||||
},
|
||||
];
|
||||
|
|
|
@ -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 @@
|
|||
{
|
||||
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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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]];
|
||||
|
|
Loading…
Reference in New Issue