Skip to content

Commit a32be38

Browse files
sherginfacebook-github-bot
authored andcommitted
Fabric: Introducing RCTSurfaceTouchHandler
Summary: RCTSurfaceTouchHandler is a complete rewrite of RCTTouchHandler which uses direct Fabric-specific event dispatching pipeline and several new approaches to managing active events (such as high-performant C++ collections, better management of identifier pool, and so on). Besides that, the new implementation is much more W3C compliant that it used to be (see old TODOs near `receiveTouches()` implementation in Javascript). So, touch events work now! Reviewed By: fkgozali Differential Revision: D8246713 fbshipit-source-id: 218dc15cd8f982237de7e2497ff36a7bfe6d37cc
1 parent d01290d commit a32be38

File tree

4 files changed

+389
-2
lines changed

4 files changed

+389
-2
lines changed

React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,4 +67,9 @@ - (BOOL)didActivateAccessibilityCustomAction:(UIAccessibilityCustomAction *)acti
6767
return YES;
6868
}
6969

70+
- (SharedEventHandlers)touchEventHandlers
71+
{
72+
return _eventHandlers;
73+
}
74+
7075
@end

React/Fabric/RCTSurfaceTouchHandler.h

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
/**
2+
* Copyright (c) 2015-present, Facebook, Inc.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
#import <UIKit/UIKit.h>
9+
10+
NS_ASSUME_NONNULL_BEGIN
11+
12+
@interface RCTSurfaceTouchHandler : UIGestureRecognizer
13+
14+
- (void)attachToView:(UIView *)view;
15+
- (void)detachFromView:(UIView *)view;
16+
17+
@end
18+
19+
NS_ASSUME_NONNULL_END
Lines changed: 360 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,360 @@
1+
/**
2+
* Copyright (c) 2015-present, Facebook, Inc.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
#import "RCTSurfaceTouchHandler.h"
9+
10+
#import <UIKit/UIGestureRecognizerSubclass.h>
11+
#import <fabric/view/ViewEventHandlers.h>
12+
#import <React/RCTUtils.h>
13+
#import <React/RCTViewComponentView.h>
14+
15+
#import "RCTConversions.h"
16+
17+
using namespace facebook::react;
18+
19+
template <size_t size>
20+
class IdentifierPool {
21+
public:
22+
23+
void enqueue(int index) {
24+
usage[index] = false;
25+
}
26+
27+
int dequeue() {
28+
while (true) {
29+
if (!usage[lastIndex]) {
30+
usage[lastIndex] = true;
31+
return lastIndex;
32+
}
33+
lastIndex = (lastIndex + 1) % size;
34+
}
35+
}
36+
37+
void reset() {
38+
for (int i = 0; i < size; i++) {
39+
usage[i] = false;
40+
}
41+
}
42+
43+
private:
44+
45+
bool usage[size];
46+
int lastIndex;
47+
};
48+
49+
@protocol RCTTouchableComponentViewProtocol <NSObject>
50+
- (SharedViewEventHandlers)touchEventHandlers;
51+
@end
52+
53+
typedef NS_ENUM(NSInteger, RCTTouchEventType) {
54+
RCTTouchEventTypeTouchStart,
55+
RCTTouchEventTypeTouchMove,
56+
RCTTouchEventTypeTouchEnd,
57+
RCTTouchEventTypeTouchCancel,
58+
};
59+
60+
struct ActiveTouch {
61+
Touch touch;
62+
SharedViewEventHandlers eventHandlers;
63+
64+
struct Hasher {
65+
size_t operator()(const ActiveTouch &activeTouch) const {
66+
return std::hash<decltype(activeTouch.touch.identifier)>()(activeTouch.touch.identifier);
67+
}
68+
};
69+
70+
struct Comparator {
71+
bool operator()(const ActiveTouch &lhs, const ActiveTouch &rhs) const {
72+
return lhs.touch.identifier == rhs.touch.identifier;
73+
}
74+
};
75+
};
76+
77+
static void UpdateActiveTouchWithUITouch(ActiveTouch &activeTouch, UITouch *uiTouch, UIView *rootComponentView) {
78+
CGPoint offsetPoint = [uiTouch locationInView:uiTouch.view];
79+
CGPoint screenPoint = [uiTouch locationInView:uiTouch.window];
80+
CGPoint pagePoint = [uiTouch locationInView:rootComponentView];
81+
82+
activeTouch.touch.offsetPoint = RCTPointFromCGPoint(offsetPoint);
83+
activeTouch.touch.screenPoint = RCTPointFromCGPoint(screenPoint);
84+
activeTouch.touch.pagePoint = RCTPointFromCGPoint(pagePoint);
85+
86+
activeTouch.touch.timestamp = uiTouch.timestamp;
87+
88+
if (RCTForceTouchAvailable()) {
89+
activeTouch.touch.force = uiTouch.force / uiTouch.maximumPossibleForce;
90+
}
91+
}
92+
93+
static ActiveTouch CreateTouchWithUITouch(UITouch *uiTouch, UIView *rootComponentView) {
94+
UIView *componentView = uiTouch.view;
95+
96+
ActiveTouch activeTouch = {};
97+
98+
if ([componentView respondsToSelector:@selector(touchEventHandlers)]) {
99+
activeTouch.eventHandlers = [(id<RCTTouchableComponentViewProtocol>)componentView touchEventHandlers];
100+
activeTouch.touch.target = (Tag)componentView.tag;
101+
}
102+
103+
UpdateActiveTouchWithUITouch(activeTouch, uiTouch, rootComponentView);
104+
return activeTouch;
105+
}
106+
107+
static BOOL AllTouchesAreCancelledOrEnded(NSSet<UITouch *> *touches) {
108+
for (UITouch *touch in touches) {
109+
if (touch.phase == UITouchPhaseBegan ||
110+
touch.phase == UITouchPhaseMoved ||
111+
touch.phase == UITouchPhaseStationary) {
112+
return NO;
113+
}
114+
}
115+
return YES;
116+
}
117+
118+
static BOOL AnyTouchesChanged(NSSet<UITouch *> *touches) {
119+
for (UITouch *touch in touches) {
120+
if (touch.phase == UITouchPhaseBegan ||
121+
touch.phase == UITouchPhaseMoved) {
122+
return YES;
123+
}
124+
}
125+
return NO;
126+
}
127+
128+
/**
129+
* Surprisingly, `__unsafe_unretained id` pointers are not regular pointers
130+
* and `std::hash<>` cannot hash them.
131+
* This is quite trivial but decent implementation of hasher function
132+
* inspired by this research: https://stackoverflow.com/a/21062520/496389.
133+
*/
134+
template<typename PointerT>
135+
struct PointerHasher {
136+
constexpr std::size_t operator()(const PointerT &value) const {
137+
return reinterpret_cast<size_t>(&value);
138+
}
139+
};
140+
141+
@interface RCTSurfaceTouchHandler () <UIGestureRecognizerDelegate>
142+
@end
143+
144+
@implementation RCTSurfaceTouchHandler {
145+
std::unordered_map<
146+
__unsafe_unretained UITouch *,
147+
ActiveTouch,
148+
PointerHasher<__unsafe_unretained UITouch *>
149+
> _activeTouches;
150+
151+
UIView *_rootComponentView;
152+
IdentifierPool<11> _identifierPool;
153+
}
154+
155+
- (instancetype)init
156+
{
157+
if (self = [super initWithTarget:nil action:nil]) {
158+
// `cancelsTouchesInView` and `delaysTouches*` are needed in order
159+
// to be used as a top level event delegated recognizer.
160+
// Otherwise, lower-level components not built using React Native,
161+
// will fail to recognize gestures.
162+
self.cancelsTouchesInView = NO;
163+
self.delaysTouchesBegan = NO; // This is default value.
164+
self.delaysTouchesEnded = NO;
165+
166+
self.delegate = self;
167+
}
168+
169+
return self;
170+
}
171+
172+
RCT_NOT_IMPLEMENTED(- (instancetype)initWithTarget:(id)target action:(SEL)action)
173+
174+
- (void)attachToView:(UIView *)view
175+
{
176+
RCTAssert(self.view == nil, @"RCTTouchHandler already has attached view.");
177+
178+
[view addGestureRecognizer:self];
179+
_rootComponentView = view;
180+
}
181+
182+
- (void)detachFromView:(UIView *)view
183+
{
184+
RCTAssertParam(view);
185+
RCTAssert(self.view == view, @"RCTTouchHandler attached to another view.");
186+
187+
[view removeGestureRecognizer:self];
188+
_rootComponentView = nil;
189+
}
190+
191+
- (void)_registerTouches:(NSSet<UITouch *> *)touches
192+
{
193+
for (UITouch *touch in touches) {
194+
auto &&activeTouch = CreateTouchWithUITouch(touch, _rootComponentView);
195+
activeTouch.touch.identifier = _identifierPool.dequeue();
196+
_activeTouches.emplace(touch, activeTouch);
197+
}
198+
}
199+
200+
- (void)_updateTouches:(NSSet<UITouch *> *)touches
201+
{
202+
for (UITouch *touch in touches) {
203+
UpdateActiveTouchWithUITouch(_activeTouches[touch], touch, _rootComponentView);
204+
}
205+
}
206+
207+
- (void)_unregisterTouches:(NSSet<UITouch *> *)touches
208+
{
209+
for (UITouch *touch in touches) {
210+
auto &&activeTouch = _activeTouches[touch];
211+
_identifierPool.enqueue(activeTouch.touch.identifier);
212+
_activeTouches.erase(touch);
213+
}
214+
}
215+
216+
- (void)_dispatchTouches:(NSSet<UITouch *> *)touches eventType:(RCTTouchEventType)eventType
217+
{
218+
TouchEvent event = {};
219+
std::unordered_set<ActiveTouch, ActiveTouch::Hasher, ActiveTouch::Comparator> changedActiveTouches = {};
220+
std::unordered_set<SharedViewEventHandlers> uniqueEventHandlers = {};
221+
BOOL isEndishEventType = eventType == RCTTouchEventTypeTouchEnd || eventType == RCTTouchEventTypeTouchCancel;
222+
223+
for (UITouch *touch in touches) {
224+
auto &&activeTouch = _activeTouches[touch];
225+
226+
if (!activeTouch.eventHandlers) {
227+
continue;
228+
}
229+
230+
changedActiveTouches.insert(activeTouch);
231+
event.changedTouches.insert(activeTouch.touch);
232+
uniqueEventHandlers.insert(activeTouch.eventHandlers);
233+
}
234+
235+
for (auto &&pair : _activeTouches) {
236+
if (!pair.second.eventHandlers) {
237+
continue;
238+
}
239+
240+
if (
241+
isEndishEventType &&
242+
event.changedTouches.find(pair.second.touch) != event.changedTouches.end()
243+
) {
244+
continue;
245+
}
246+
247+
event.touches.insert(pair.second.touch);
248+
}
249+
250+
for (auto &&eventHandlers : uniqueEventHandlers) {
251+
event.targetTouches.clear();
252+
253+
for (auto &&pair : _activeTouches) {
254+
if (pair.second.eventHandlers == eventHandlers) {
255+
event.targetTouches.insert(pair.second.touch);
256+
}
257+
}
258+
259+
switch (eventType) {
260+
case RCTTouchEventTypeTouchStart:
261+
eventHandlers->onTouchStart(event);
262+
break;
263+
case RCTTouchEventTypeTouchMove:
264+
eventHandlers->onTouchMove(event);
265+
break;
266+
case RCTTouchEventTypeTouchEnd:
267+
eventHandlers->onTouchEnd(event);
268+
break;
269+
case RCTTouchEventTypeTouchCancel:
270+
eventHandlers->onTouchCancel(event);
271+
break;
272+
}
273+
}
274+
}
275+
276+
#pragma mark - `UIResponder`-ish touch-delivery methods
277+
278+
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
279+
{
280+
[super touchesBegan:touches withEvent:event];
281+
282+
[self _registerTouches:touches];
283+
[self _dispatchTouches:touches eventType:RCTTouchEventTypeTouchStart];
284+
285+
if (self.state == UIGestureRecognizerStatePossible) {
286+
self.state = UIGestureRecognizerStateBegan;
287+
} else if (self.state == UIGestureRecognizerStateBegan) {
288+
self.state = UIGestureRecognizerStateChanged;
289+
}
290+
}
291+
292+
- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
293+
{
294+
[super touchesMoved:touches withEvent:event];
295+
296+
[self _updateTouches:touches];
297+
[self _dispatchTouches:touches eventType:RCTTouchEventTypeTouchMove];
298+
299+
self.state = UIGestureRecognizerStateChanged;
300+
}
301+
302+
- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
303+
{
304+
[super touchesEnded:touches withEvent:event];
305+
306+
[self _updateTouches:touches];
307+
[self _dispatchTouches:touches eventType:RCTTouchEventTypeTouchEnd];
308+
[self _unregisterTouches:touches];
309+
310+
if (AllTouchesAreCancelledOrEnded(event.allTouches)) {
311+
self.state = UIGestureRecognizerStateEnded;
312+
} else if (AnyTouchesChanged(event.allTouches)) {
313+
self.state = UIGestureRecognizerStateChanged;
314+
}
315+
}
316+
317+
- (void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
318+
{
319+
[super touchesCancelled:touches withEvent:event];
320+
321+
[self _updateTouches:touches];
322+
[self _dispatchTouches:touches eventType:RCTTouchEventTypeTouchCancel];
323+
[self _unregisterTouches:touches];
324+
325+
if (AllTouchesAreCancelledOrEnded(event.allTouches)) {
326+
self.state = UIGestureRecognizerStateCancelled;
327+
} else if (AnyTouchesChanged(event.allTouches)) {
328+
self.state = UIGestureRecognizerStateChanged;
329+
}
330+
}
331+
332+
- (void)reset
333+
{
334+
// Technically, `_activeTouches` must be already empty at this point,
335+
// but just to be sure, we clear it explicitly.
336+
_activeTouches.clear();
337+
_identifierPool.reset();
338+
}
339+
340+
- (BOOL)canPreventGestureRecognizer:(__unused UIGestureRecognizer *)preventedGestureRecognizer
341+
{
342+
return NO;
343+
}
344+
345+
- (BOOL)canBePreventedByGestureRecognizer:(UIGestureRecognizer *)preventingGestureRecognizer
346+
{
347+
// We fail in favour of other external gesture recognizers.
348+
// iOS will ask `delegate`'s opinion about this gesture recognizer little bit later.
349+
return ![preventingGestureRecognizer.view isDescendantOfView:self.view];
350+
}
351+
352+
#pragma mark - UIGestureRecognizerDelegate
353+
354+
- (BOOL)gestureRecognizer:(__unused UIGestureRecognizer *)gestureRecognizer shouldRequireFailureOfGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
355+
{
356+
// Same condition for `failure of` as for `be prevented by`.
357+
return [self canBePreventedByGestureRecognizer:otherGestureRecognizer];
358+
}
359+
360+
@end

0 commit comments

Comments
 (0)