mirror of
https://github.com/status-im/react-native.git
synced 2025-02-04 05:34:15 +00:00
[ReactNative] Implement proper event coalescing
This commit is contained in:
parent
17e9cd6297
commit
4fc15dbf17
@ -95,6 +95,11 @@ static const char *__rct_import_##module##_##method##__ = #module"."#method;
|
||||
/**
|
||||
* The event dispatcher is a wrapper around -enqueueJSCall:args: that provides a
|
||||
* higher-level interface for sending UI events such as touches and text input.
|
||||
*
|
||||
* NOTE: RCTEventDispatcher is now a bridge module, this is implemented as a
|
||||
* category but remains declared in the bridge to avoid breaking changes
|
||||
*
|
||||
* To be moved.
|
||||
*/
|
||||
@property (nonatomic, readonly) RCTEventDispatcher *eventDispatcher;
|
||||
|
||||
|
@ -18,7 +18,6 @@
|
||||
|
||||
#import "RCTContextExecutor.h"
|
||||
#import "RCTConvert.h"
|
||||
#import "RCTEventDispatcher.h"
|
||||
#import "RCTJavaScriptLoader.h"
|
||||
#import "RCTKeyCommands.h"
|
||||
#import "RCTLog.h"
|
||||
@ -211,7 +210,6 @@ static NSArray *RCTBridgeModuleClassesByModuleID(void)
|
||||
|
||||
@property (nonatomic, strong) RCTBatchedBridge *batchedBridge;
|
||||
@property (nonatomic, strong) RCTBridgeModuleProviderBlock moduleProvider;
|
||||
@property (nonatomic, strong, readwrite) RCTEventDispatcher *eventDispatcher;
|
||||
|
||||
- (void)_invokeAndProcessModule:(NSString *)module
|
||||
method:(NSString *)method
|
||||
@ -875,11 +873,6 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
|
||||
return _batchedBridge.modules;
|
||||
}
|
||||
|
||||
- (RCTEventDispatcher *)eventDispatcher
|
||||
{
|
||||
return _eventDispatcher ?: _batchedBridge.eventDispatcher;
|
||||
}
|
||||
|
||||
#define RCT_INNER_BRIDGE_ONLY(...) \
|
||||
- (void)__VA_ARGS__ \
|
||||
{ \
|
||||
@ -943,11 +936,6 @@ RCT_INNER_BRIDGE_ONLY(_invokeAndProcessModule:(NSString *)module method:(NSStrin
|
||||
_javaScriptExecutor = RCTCreateExecutor(executorClass);
|
||||
_latestJSExecutor = _javaScriptExecutor;
|
||||
|
||||
/**
|
||||
* Setup event dispatcher before initializing modules to allow init calls
|
||||
*/
|
||||
self.eventDispatcher = [[RCTEventDispatcher alloc] initWithBridge:self];
|
||||
|
||||
/**
|
||||
* Initialize and register bridge modules *before* adding the display link
|
||||
* so we don't have threading issues
|
||||
|
@ -9,7 +9,7 @@
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
@class RCTBridge;
|
||||
#import "RCTBridge.h"
|
||||
|
||||
typedef NS_ENUM(NSInteger, RCTTextEventType) {
|
||||
RCTTextEventTypeFocus,
|
||||
@ -28,14 +28,36 @@ typedef NS_ENUM(NSInteger, RCTScrollEventType) {
|
||||
RCTScrollEventTypeEndAnimation,
|
||||
};
|
||||
|
||||
@protocol RCTEvent <NSObject>
|
||||
|
||||
@required
|
||||
|
||||
@property (nonatomic, strong, readonly) NSNumber *viewTag;
|
||||
@property (nonatomic, copy, readonly) NSString *eventName;
|
||||
@property (nonatomic, copy, readonly) NSDictionary *body;
|
||||
@property (nonatomic, assign, readonly) uint16_t coalescingKey;
|
||||
|
||||
- (BOOL)canCoalesce;
|
||||
- (id<RCTEvent>)coalesceWithEvent:(id<RCTEvent>)newEvent;
|
||||
|
||||
+ (NSString *)moduleDotMethod;
|
||||
|
||||
@end
|
||||
|
||||
@interface RCTBaseEvent : NSObject <RCTEvent>
|
||||
|
||||
- (instancetype)initWithViewTag:(NSNumber *)viewTag
|
||||
eventName:(NSString *)eventName
|
||||
body:(NSDictionary *)body NS_DESIGNATED_INITIALIZER;
|
||||
|
||||
@end
|
||||
|
||||
/**
|
||||
* This class wraps the -[RCTBridge enqueueJSCall:args:] method, and
|
||||
* provides some convenience methods for generating event calls.
|
||||
*/
|
||||
@interface RCTEventDispatcher : NSObject
|
||||
|
||||
- (instancetype)initWithBridge:(RCTBridge *)bridge;
|
||||
|
||||
/**
|
||||
* Send an application-specific event that does not relate to a specific
|
||||
* view, e.g. a navigation or data update notification.
|
||||
@ -61,13 +83,6 @@ typedef NS_ENUM(NSInteger, RCTScrollEventType) {
|
||||
reactTag:(NSNumber *)reactTag
|
||||
text:(NSString *)text;
|
||||
|
||||
/**
|
||||
* Send a scroll event.
|
||||
* (You can send a fake scroll event by passing nil for scrollView).
|
||||
*/
|
||||
- (void)sendScrollEventWithType:(RCTScrollEventType)type
|
||||
reactTag:(NSNumber *)reactTag
|
||||
scrollView:(UIScrollView *)scrollView
|
||||
userData:(NSDictionary *)userData;
|
||||
- (void)sendEvent:(id<RCTEvent>)event;
|
||||
|
||||
@end
|
||||
|
@ -11,16 +11,76 @@
|
||||
|
||||
#import "RCTAssert.h"
|
||||
#import "RCTBridge.h"
|
||||
#import "RCTSparseArray.h"
|
||||
|
||||
static uint64_t RCTGetEventID(id<RCTEvent> event)
|
||||
{
|
||||
return (
|
||||
[event.viewTag intValue] |
|
||||
(((uint64_t)event.eventName.hash & 0xFFFF) << 32) |
|
||||
(((uint64_t)event.coalescingKey) << 48)
|
||||
);
|
||||
}
|
||||
|
||||
@implementation RCTBaseEvent
|
||||
|
||||
@synthesize viewTag = _viewTag;
|
||||
@synthesize eventName = _eventName;
|
||||
@synthesize body = _body;
|
||||
|
||||
- (instancetype)initWithViewTag:(NSNumber *)viewTag
|
||||
eventName:(NSString *)eventName
|
||||
body:(NSDictionary *)body
|
||||
{
|
||||
if ((self = [super init])) {
|
||||
_viewTag = viewTag;
|
||||
_eventName = eventName;
|
||||
_body = body;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (uint16_t)coalescingKey
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
- (BOOL)canCoalesce
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (id<RCTEvent>)coalesceWithEvent:(id<RCTEvent>)newEvent
|
||||
{
|
||||
return newEvent;
|
||||
}
|
||||
|
||||
+ (NSString *)moduleDotMethod
|
||||
{
|
||||
return nil;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@interface RCTEventDispatcher() <RCTBridgeModule, RCTFrameUpdateObserver>
|
||||
|
||||
@end
|
||||
|
||||
@implementation RCTEventDispatcher
|
||||
{
|
||||
RCTBridge __weak *_bridge;
|
||||
RCTSparseArray *_eventQueue;
|
||||
NSLock *_eventQueueLock;
|
||||
}
|
||||
|
||||
- (instancetype)initWithBridge:(RCTBridge *)bridge
|
||||
@synthesize bridge = _bridge;
|
||||
|
||||
RCT_EXPORT_MODULE()
|
||||
|
||||
- (instancetype)init
|
||||
{
|
||||
if ((self = [super init])) {
|
||||
_bridge = bridge;
|
||||
_eventQueue = [[RCTSparseArray alloc] init];
|
||||
_eventQueueLock = [[NSLock alloc] init];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
@ -70,58 +130,71 @@ RCT_IMPORT_METHOD(RCTEventEmitter, receiveEvent);
|
||||
}];
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO: throttling
|
||||
* NOTE: the old system used a per-scrollview throttling
|
||||
* which would be fairly easy to re-implement if needed,
|
||||
* but this is non-optimal as it leads to degradation in
|
||||
* scroll responsiveness. A better solution would be to
|
||||
* coalesce multiple scroll events into a single batch.
|
||||
*/
|
||||
- (void)sendScrollEventWithType:(RCTScrollEventType)type
|
||||
reactTag:(NSNumber *)reactTag
|
||||
scrollView:(UIScrollView *)scrollView
|
||||
userData:(NSDictionary *)userData
|
||||
- (void)sendEvent:(id<RCTEvent>)event
|
||||
{
|
||||
static NSString *events[] = {
|
||||
@"topScrollBeginDrag",
|
||||
@"topScroll",
|
||||
@"topScrollEndDrag",
|
||||
@"topMomentumScrollBegin",
|
||||
@"topMomentumScrollEnd",
|
||||
@"topScrollAnimationEnd",
|
||||
};
|
||||
|
||||
NSDictionary *body = @{
|
||||
@"contentOffset": @{
|
||||
@"x": @(scrollView.contentOffset.x),
|
||||
@"y": @(scrollView.contentOffset.y)
|
||||
},
|
||||
@"contentInset": @{
|
||||
@"top": @(scrollView.contentInset.top),
|
||||
@"left": @(scrollView.contentInset.left),
|
||||
@"bottom": @(scrollView.contentInset.bottom),
|
||||
@"right": @(scrollView.contentInset.right)
|
||||
},
|
||||
@"contentSize": @{
|
||||
@"width": @(scrollView.contentSize.width),
|
||||
@"height": @(scrollView.contentSize.height)
|
||||
},
|
||||
@"layoutMeasurement": @{
|
||||
@"width": @(scrollView.frame.size.width),
|
||||
@"height": @(scrollView.frame.size.height)
|
||||
},
|
||||
@"zoomScale": @(scrollView.zoomScale ?: 1),
|
||||
@"target": reactTag
|
||||
};
|
||||
|
||||
if (userData) {
|
||||
NSMutableDictionary *mutableBody = [body mutableCopy];
|
||||
[mutableBody addEntriesFromDictionary:userData];
|
||||
body = mutableBody;
|
||||
if (!event.canCoalesce) {
|
||||
[self dispatchEvent:event];
|
||||
return;
|
||||
}
|
||||
|
||||
[self sendInputEventWithName:events[type] body:body];
|
||||
[_eventQueueLock lock];
|
||||
|
||||
uint64_t eventID = RCTGetEventID(event);
|
||||
id<RCTEvent> previousEvent = _eventQueue[eventID];
|
||||
|
||||
if (previousEvent) {
|
||||
event = [previousEvent coalesceWithEvent:event];
|
||||
}
|
||||
|
||||
_eventQueue[eventID] = event;
|
||||
|
||||
[_eventQueueLock unlock];
|
||||
}
|
||||
|
||||
- (void)dispatchEvent:(id<RCTEvent>)event
|
||||
{
|
||||
NSMutableArray *arguments = [[NSMutableArray alloc] init];
|
||||
|
||||
if (event.viewTag) {
|
||||
[arguments addObject:event.viewTag];
|
||||
}
|
||||
|
||||
[arguments addObject:event.eventName];
|
||||
|
||||
if (event.body) {
|
||||
[arguments addObject:event.body];
|
||||
}
|
||||
|
||||
[_bridge enqueueJSCall:[[event class] moduleDotMethod]
|
||||
args:arguments];
|
||||
}
|
||||
|
||||
- (dispatch_queue_t)methodQueue
|
||||
{
|
||||
return RCTJSThread;
|
||||
}
|
||||
|
||||
- (void)didUpdateFrame:(RCTFrameUpdate *)update
|
||||
{
|
||||
RCTSparseArray *eventQueue;
|
||||
|
||||
[_eventQueueLock lock];
|
||||
eventQueue = _eventQueue;
|
||||
_eventQueue = [[RCTSparseArray alloc] init];
|
||||
[_eventQueueLock unlock];
|
||||
|
||||
for (id<RCTEvent> event in eventQueue.allObjects) {
|
||||
[self dispatchEvent:event];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation RCTBridge (RCTEventDispatcher)
|
||||
|
||||
- (RCTEventDispatcher *)eventDispatcher
|
||||
{
|
||||
return self.modules[RCTBridgeModuleNameForClass([RCTEventDispatcher class])];
|
||||
}
|
||||
|
||||
@end
|
||||
|
@ -15,6 +15,7 @@
|
||||
#import "RCTEventDispatcher.h"
|
||||
#import "RCTLog.h"
|
||||
#import "RCTNavItem.h"
|
||||
#import "RCTScrollView.h"
|
||||
#import "RCTUtils.h"
|
||||
#import "RCTView.h"
|
||||
#import "RCTWrapperViewController.h"
|
||||
|
@ -10,13 +10,12 @@
|
||||
#import <UIKit/UIScrollView.h>
|
||||
|
||||
#import "RCTAutoInsetsProtocol.h"
|
||||
#import "RCTEventDispatcher.h"
|
||||
#import "RCTScrollableProtocol.h"
|
||||
#import "RCTView.h"
|
||||
|
||||
@protocol UIScrollViewDelegate;
|
||||
|
||||
@class RCTEventDispatcher;
|
||||
|
||||
@interface RCTScrollView : RCTView <UIScrollViewDelegate, RCTScrollableProtocol, RCTAutoInsetsProtocol>
|
||||
|
||||
- (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher NS_DESIGNATED_INITIALIZER;
|
||||
@ -48,3 +47,16 @@
|
||||
@property (nonatomic, copy) NSIndexSet *stickyHeaderIndices;
|
||||
|
||||
@end
|
||||
|
||||
@interface RCTEventDispatcher (RCTScrollView)
|
||||
|
||||
/**
|
||||
* Send a scroll event.
|
||||
* (You can send a fake scroll event by passing nil for scrollView).
|
||||
*/
|
||||
- (void)sendScrollEventWithType:(RCTScrollEventType)type
|
||||
reactTag:(NSNumber *)reactTag
|
||||
scrollView:(UIScrollView *)scrollView
|
||||
userData:(NSDictionary *)userData;
|
||||
|
||||
@end
|
||||
|
@ -21,6 +21,107 @@
|
||||
CGFloat const ZINDEX_DEFAULT = 0;
|
||||
CGFloat const ZINDEX_STICKY_HEADER = 50;
|
||||
|
||||
@interface RCTScrollEvent : NSObject <RCTEvent>
|
||||
|
||||
- (instancetype)initWithType:(RCTScrollEventType)type
|
||||
reactTag:(NSNumber *)reactTag
|
||||
scrollView:(UIScrollView *)scrollView
|
||||
userData:(NSDictionary *)userData NS_DESIGNATED_INITIALIZER;
|
||||
|
||||
@end
|
||||
|
||||
@implementation RCTScrollEvent
|
||||
{
|
||||
RCTScrollEventType _type;
|
||||
UIScrollView *_scrollView;
|
||||
NSDictionary *_userData;
|
||||
}
|
||||
|
||||
@synthesize viewTag = _viewTag;
|
||||
|
||||
- (instancetype)initWithType:(RCTScrollEventType)type
|
||||
reactTag:(NSNumber *)reactTag
|
||||
scrollView:(UIScrollView *)scrollView
|
||||
userData:(NSDictionary *)userData
|
||||
{
|
||||
if (self = [super init]) {
|
||||
_type = type;
|
||||
_viewTag = reactTag;
|
||||
_scrollView = scrollView;
|
||||
_userData = userData;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (uint16_t)coalescingKey
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
- (NSDictionary *)body
|
||||
{
|
||||
NSDictionary *body = @{
|
||||
@"contentOffset": @{
|
||||
@"x": @(_scrollView.contentOffset.x),
|
||||
@"y": @(_scrollView.contentOffset.y)
|
||||
},
|
||||
@"contentInset": @{
|
||||
@"top": @(_scrollView.contentInset.top),
|
||||
@"left": @(_scrollView.contentInset.left),
|
||||
@"bottom": @(_scrollView.contentInset.bottom),
|
||||
@"right": @(_scrollView.contentInset.right)
|
||||
},
|
||||
@"contentSize": @{
|
||||
@"width": @(_scrollView.contentSize.width),
|
||||
@"height": @(_scrollView.contentSize.height)
|
||||
},
|
||||
@"layoutMeasurement": @{
|
||||
@"width": @(_scrollView.frame.size.width),
|
||||
@"height": @(_scrollView.frame.size.height)
|
||||
},
|
||||
@"zoomScale": @(_scrollView.zoomScale ?: 1),
|
||||
};
|
||||
|
||||
if (_userData) {
|
||||
NSMutableDictionary *mutableBody = [body mutableCopy];
|
||||
[mutableBody addEntriesFromDictionary:_userData];
|
||||
body = mutableBody;
|
||||
}
|
||||
|
||||
return body;
|
||||
}
|
||||
|
||||
- (NSString *)eventName
|
||||
{
|
||||
static NSString *events[] = {
|
||||
@"topScrollBeginDrag",
|
||||
@"topScroll",
|
||||
@"topScrollEndDrag",
|
||||
@"topMomentumScrollBegin",
|
||||
@"topMomentumScrollEnd",
|
||||
@"topScrollAnimationEnd",
|
||||
};
|
||||
|
||||
return events[_type];
|
||||
}
|
||||
|
||||
- (BOOL)canCoalesce
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (id<RCTEvent>)coalesceWithEvent:(id<RCTEvent>)newEvent
|
||||
{
|
||||
return newEvent;
|
||||
}
|
||||
|
||||
+ (NSString *)moduleDotMethod
|
||||
{
|
||||
return @"RCTEventEmitter.receiveEvent";
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
/**
|
||||
* Include a custom scroll view subclass because we want to limit certain
|
||||
* default UIKit behaviors such as textFields automatically scrolling
|
||||
@ -442,6 +543,7 @@ RCT_SCROLL_EVENT_HANDLER(scrollViewDidZoom, RCTScrollEventTypeMove)
|
||||
reactTag:self.reactTag
|
||||
scrollView:scrollView
|
||||
userData:userData];
|
||||
|
||||
// Update dispatch time
|
||||
_lastScrollDispatchTime = now;
|
||||
_allowNextScrollNoMatterWhat = NO;
|
||||
@ -630,3 +732,19 @@ RCT_SET_AND_PRESERVE_OFFSET(setScrollIndicatorInsets, UIEdgeInsets);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation RCTEventDispatcher (RCTScrollView)
|
||||
|
||||
- (void)sendScrollEventWithType:(RCTScrollEventType)type
|
||||
reactTag:(NSNumber *)reactTag
|
||||
scrollView:(UIScrollView *)scrollView
|
||||
userData:(NSDictionary *)userData
|
||||
{
|
||||
RCTScrollEvent *scrollEvent = [[RCTScrollEvent alloc] initWithType:type
|
||||
reactTag:reactTag
|
||||
scrollView:scrollView
|
||||
userData:userData];
|
||||
[self sendEvent:scrollEvent];
|
||||
}
|
||||
|
||||
@end
|
||||
|
Loading…
x
Reference in New Issue
Block a user