Skip to content

Commit 744d921

Browse files
authored
[iOS] Stylus support (#3113)
## Description This PR adds stylus support on `iOS`. >[!WARNING] > This PR lacks support for `Hover` since currently we are unable to test that. >[!NOTE] > You can read more about this feature in #3107 ## Test plan Tested on **_StylusData_** example
1 parent efd5da9 commit 744d921

File tree

5 files changed

+185
-11
lines changed

5 files changed

+185
-11
lines changed

apple/Handlers/RNPanHandler.m

Lines changed: 57 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
//
88

99
#import "RNPanHandler.h"
10+
#import "RNGHStylusData.h"
1011

1112
#if TARGET_OS_OSX
1213

@@ -31,6 +32,10 @@ @interface RNBetterPanGestureRecognizer : UIPanGestureRecognizer
3132
@property (nonatomic) CGFloat failOffsetYEnd;
3233
@property (nonatomic) CGFloat activateAfterLongPress;
3334

35+
#if !TARGET_OS_OSX && !TARGET_OS_TV
36+
@property (atomic, readonly, strong) RNGHStylusData *stylusData;
37+
#endif
38+
3439
- (id)initWithGestureHandler:(RNGestureHandler *)gestureHandler;
3540

3641
@end
@@ -80,6 +85,28 @@ - (void)setMinimumNumberOfTouches:(NSUInteger)minimumNumberOfTouches
8085
}
8186
#endif
8287

88+
#if !TARGET_OS_OSX && !TARGET_OS_TV
89+
- (void)tryUpdateStylusData:(UIEvent *)event
90+
{
91+
UITouch *touch = [[event allTouches] anyObject];
92+
93+
if (touch.type != UITouchTypePencil) {
94+
return;
95+
} else if (_stylusData == nil) {
96+
_stylusData = [[RNGHStylusData alloc] init];
97+
}
98+
99+
_stylusData.altitudeAngle = touch.altitudeAngle;
100+
_stylusData.azimuthAngle = [touch azimuthAngleInView:nil];
101+
_stylusData.pressure = touch.force / touch.maximumPossibleForce;
102+
103+
CGPoint tilts = ghSpherical2tilt(_stylusData.altitudeAngle, _stylusData.azimuthAngle);
104+
105+
_stylusData.tiltX = tilts.x;
106+
_stylusData.tiltY = tilts.y;
107+
}
108+
#endif
109+
83110
- (void)activateAfterLongPress
84111
{
85112
self.state = UIGestureRecognizerStateBegan;
@@ -102,6 +129,8 @@ - (void)interactionsBegan:(NSSet *)touches withEvent:(UIEvent *)event
102129
} else {
103130
super.minimumNumberOfTouches = _realMinimumNumberOfTouches;
104131
}
132+
133+
[self tryUpdateStylusData:event];
105134
#endif
106135

107136
#if TARGET_OS_OSX
@@ -150,17 +179,28 @@ - (void)interactionsMoved:(NSSet *)touches withEvent:(UIEvent *)event
150179
[self setTranslation:CGPointMake(0, 0) inView:self.view];
151180
}
152181
}
182+
183+
[self tryUpdateStylusData:event];
153184
#endif
154185
}
155186

156187
- (void)interactionsEnded:(NSSet *)touches withEvent:(UIEvent *)event
157188
{
158189
[_gestureHandler.pointerTracker touchesEnded:touches withEvent:event];
190+
191+
#if !TARGET_OS_TV && !TARGET_OS_OSX
192+
[self tryUpdateStylusData:event];
193+
#endif
159194
}
160195

161196
- (void)interactionsCancelled:(NSSet *)touches withEvent:(UIEvent *)event
162197
{
163198
[_gestureHandler.pointerTracker touchesCancelled:touches withEvent:event];
199+
200+
#if !TARGET_OS_TV && !TARGET_OS_OSX
201+
[self tryUpdateStylusData:event];
202+
#endif
203+
164204
[self reset];
165205
}
166206

@@ -224,6 +264,10 @@ - (void)reset
224264
self.enabled = YES;
225265
[super reset];
226266
[_gestureHandler reset];
267+
268+
#if !TARGET_OS_TV && !TARGET_OS_OSX
269+
_stylusData = nil;
270+
#endif
227271
}
228272

229273
- (void)updateHasCustomActivationCriteria
@@ -405,17 +449,23 @@ - (RNGestureHandlerEventExtraData *)eventExtraData:(NSPanGestureRecognizer *)rec
405449
withTranslation:[recognizer translationInView:recognizer.view.window.contentView]
406450
withVelocity:[recognizer velocityInView:recognizer.view.window.contentView]
407451
withNumberOfTouches:1
408-
withPointerType:RNGestureHandlerMouse];
452+
withPointerType:RNGestureHandlerMouse
453+
withStylusData:nil];
409454
}
410455
#else
411456
- (RNGestureHandlerEventExtraData *)eventExtraData:(UIPanGestureRecognizer *)recognizer
412457
{
413-
return [RNGestureHandlerEventExtraData forPan:[recognizer locationInView:recognizer.view]
414-
withAbsolutePosition:[recognizer locationInView:recognizer.view.window]
415-
withTranslation:[recognizer translationInView:recognizer.view.window]
416-
withVelocity:[recognizer velocityInView:recognizer.view.window]
417-
withNumberOfTouches:recognizer.numberOfTouches
418-
withPointerType:_pointerType];
458+
RNBetterPanGestureRecognizer *panRecognizer = (RNBetterPanGestureRecognizer *)recognizer;
459+
460+
return [RNGestureHandlerEventExtraData
461+
forPan:[recognizer locationInView:recognizer.view]
462+
withAbsolutePosition:[recognizer locationInView:recognizer.view.window]
463+
withTranslation:[recognizer translationInView:recognizer.view.window]
464+
withVelocity:[recognizer velocityInView:recognizer.view.window]
465+
withNumberOfTouches:recognizer.numberOfTouches
466+
withPointerType:_pointerType
467+
withStylusData:[panRecognizer.stylusData toDictionary]]; // In Objective-C calling method on nil returns
468+
// nil, therefore this line does not crash.
419469
}
420470
#endif
421471

apple/RNGHStylusData.h

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
//
2+
// RNGHStylusData.h
3+
// Pods
4+
//
5+
// Created by Michał Bert on 18/09/2024.
6+
//
7+
8+
#ifndef RNGHStylusData_h
9+
#define RNGHStylusData_h
10+
11+
@interface RNGHStylusData : NSObject
12+
13+
@property (atomic, assign) double tiltX;
14+
@property (atomic, assign) double tiltY;
15+
@property (atomic, assign) double altitudeAngle;
16+
@property (atomic, assign) double azimuthAngle;
17+
@property (atomic, assign) double pressure;
18+
19+
- (NSDictionary *)toDictionary;
20+
21+
@end
22+
23+
static CGPoint ghSpherical2tilt(double altitudeAngle, double azimuthAngle)
24+
{
25+
CGPoint tilts = {.x = 0.0, .y = 0.0};
26+
27+
const double radToDeg = 180 / M_PI;
28+
const double eps = 0.000000001;
29+
30+
if (altitudeAngle < eps) {
31+
// the pen is in the X-Y plane
32+
if (azimuthAngle < eps || fabs(azimuthAngle - 2 * M_PI) < eps) {
33+
// pen is on positive X axis
34+
tilts.x = M_PI_2;
35+
}
36+
if (fabs(azimuthAngle - M_PI_2) < eps) {
37+
// pen is on positive Y axis
38+
tilts.y = M_PI_2;
39+
}
40+
if (fabs(azimuthAngle - M_PI) < eps) {
41+
// pen is on negative X axis
42+
tilts.x = -M_PI_2;
43+
}
44+
if (fabs(azimuthAngle - 3 * M_PI_2) < eps) {
45+
// pen is on negative Y axis
46+
tilts.y = -M_PI_2;
47+
}
48+
if (azimuthAngle > eps && fabs(azimuthAngle - M_PI_2) < eps) {
49+
tilts.x = M_PI_2;
50+
tilts.y = M_PI_2;
51+
}
52+
if (fabs(azimuthAngle - M_PI_2) > eps && fabs(azimuthAngle - M_PI) < eps) {
53+
tilts.x = -M_PI_2;
54+
tilts.y = M_PI_2;
55+
}
56+
if (azimuthAngle - M_PI > eps && fabs(azimuthAngle - 3 * M_PI_2) < eps) {
57+
tilts.x = -M_PI_2;
58+
tilts.y = -M_PI_2;
59+
}
60+
if (fabs(azimuthAngle - 3 * M_PI_2) > eps && fabs(azimuthAngle - 2 * M_PI) < eps) {
61+
tilts.x = M_PI_2;
62+
tilts.y = -M_PI_2;
63+
}
64+
} else {
65+
const double tanAlt = tan(altitudeAngle);
66+
67+
tilts.x = atan(cos(azimuthAngle) / tanAlt);
68+
tilts.y = atan(sin(azimuthAngle) / tanAlt);
69+
}
70+
71+
tilts.x = round(tilts.x * radToDeg);
72+
tilts.y = round(tilts.y * radToDeg);
73+
74+
return tilts;
75+
}
76+
77+
#endif /* RNGHStylusData_h */

apple/RNGHStylusData.m

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
//
2+
// RNGHStylusData.m
3+
// DoubleConversion
4+
//
5+
// Created by Michał Bert on 18/09/2024.
6+
//
7+
8+
#import "RNGHStylusData.h"
9+
#import <Foundation/Foundation.h>
10+
11+
@implementation RNGHStylusData
12+
13+
- (instancetype)init
14+
{
15+
if (self = [super init]) {
16+
self.tiltX = 0;
17+
self.tiltY = 0;
18+
self.altitudeAngle = M_PI_2;
19+
self.azimuthAngle = 0;
20+
self.pressure = 0;
21+
}
22+
23+
return self;
24+
}
25+
26+
- (NSDictionary *)toDictionary
27+
{
28+
return @{
29+
@"tiltX" : @(_tiltX),
30+
@"tiltY" : @(_tiltY),
31+
@"altitudeAngle" : @(_altitudeAngle),
32+
@"azimuthAngle" : @(_azimuthAngle),
33+
@"pressure" : @(_pressure),
34+
};
35+
}
36+
37+
@end

apple/RNGestureHandlerEvents.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
#import <Foundation/Foundation.h>
44

5+
#import "RNGHStylusData.h"
56
#import "RNGHTouchEventType.h"
67
#import "RNGHUIKit.h"
78
#import "RNGestureHandlerState.h"
@@ -29,7 +30,8 @@
2930
withTranslation:(CGPoint)translation
3031
withVelocity:(CGPoint)velocity
3132
withNumberOfTouches:(NSUInteger)numberOfTouches
32-
withPointerType:(NSInteger)pointerType;
33+
withPointerType:(NSInteger)pointerType
34+
withStylusData:(NSDictionary *)stylusData;
3335
+ (RNGestureHandlerEventExtraData *)forForce:(CGFloat)force
3436
forPosition:(CGPoint)position
3537
withAbsolutePosition:(CGPoint)absolutePosition

apple/RNGestureHandlerEvents.m

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -63,8 +63,9 @@ + (RNGestureHandlerEventExtraData *)forPan:(CGPoint)position
6363
withVelocity:(CGPoint)velocity
6464
withNumberOfTouches:(NSUInteger)numberOfTouches
6565
withPointerType:(NSInteger)pointerType
66+
withStylusData:(NSDictionary *)stylusData
6667
{
67-
return [[RNGestureHandlerEventExtraData alloc] initWithData:@{
68+
NSMutableDictionary *data = [@{
6869
@"x" : @(position.x),
6970
@"y" : @(position.y),
7071
@"absoluteX" : @(absolutePosition.x),
@@ -74,8 +75,15 @@ + (RNGestureHandlerEventExtraData *)forPan:(CGPoint)position
7475
@"velocityX" : SAFE_VELOCITY(velocity.x),
7576
@"velocityY" : SAFE_VELOCITY(velocity.y),
7677
@"numberOfPointers" : @(numberOfTouches),
77-
@"pointerType" : @(pointerType)
78-
}];
78+
@"pointerType" : @(pointerType),
79+
} mutableCopy];
80+
81+
// Add the stylusData to the dictionary only if necessary
82+
if (stylusData != nil) {
83+
data[@"stylusData"] = stylusData;
84+
}
85+
86+
return [[RNGestureHandlerEventExtraData alloc] initWithData:data];
7987
}
8088

8189
+ (RNGestureHandlerEventExtraData *)forForce:(CGFloat)force

0 commit comments

Comments
 (0)