-
Notifications
You must be signed in to change notification settings - Fork 28
Drawing Events and Palm Rejection
Palm rejection involves filtering out any touch on the screen that isn’t coming from the stylus. In order for the SDK to accomplish this task, it needs to inspect each touch at the sendEvent
level. Once we have identified a stylus on screen we send our own JotStroke
events via the JotStrokeDelegate
.
The first integration point is to make sure events are being delivered to the new AdonitTouchTypeIdentifier
engine, which is used to detect stylus generated touches as early in the event delivery flow as possible. The easiest way to do this is to subclass JotDrawingApplication
, which includes built-in support for this event delivery. In your main.m
, replace the normal call to UIApplicationMain
with:
#import <AdonitSDK/AdonitSDK.h>
int main(int argc, char *argv[])
{
@autoreleasepool {
return UIApplicationMain(argc, argv, NSStringFromClass([JotDrawingApplication class]), NSStringFromClass([AppDelegate class]));
}
}
If you do not want to subclass JotDrawingApplication
, you can implement your own event delivery in your own UIApplication
subclass by overriding the sendEvent
method. Below is the code used in JotDrawingApplication
.
#import "JotDrawingApplication.h"
#import "AdonitTouchTypeIdentifier.h"
#import "JotStylusManager.h"
@interface JotDrawingApplication ()
@property (nonatomic) AdonitTouchTypeIdentifier *touchTypeIdentifier;
@end
@implementation JotDrawingApplication
- (AdonitTouchTypeIdentifier *)touchTypeIdentifier
{
if (!_touchTypeIdentifier) {
_touchTypeIdentifier = [JotStylusManager sharedInstance].touchTypeIdentifier;
}
return _touchTypeIdentifier;
}
- (void)sendEvent:(UIEvent *)event
{
[self.touchTypeIdentifier classifyAdonitDeviceIdentificationForEvent:event];
[super sendEvent:event];
}
@end
The critical code is to make sure you call classifyAdonitDeviceIdentificationForEvent:
before super
's sendEvent:
.
Next you need to assign a view or viewController to our `JotStrokeDelegate’
[JotStylusManager sharedInstance].jotStrokeDelegate = yourDrawingView;
[[JotStylusManager sharedInstance] enable];
As a stylus moves on the screen you will get specific JotStroke
events that serve you a JotStroke object with location and pressure.
- (void)jotStylusStrokeBegan:(nonnull JotStroke *)stylusStroke;
- (void)jotStylusStrokeMoved:(nonnull JotStroke *)stylusStroke;
- (void)jotStylusStrokeEnded:(nonnull JotStroke *)stylusStroke;
- (void)jotStylusStrokeCancelled:(nonnull JotStroke *)stylusStroke;
- (void)jotStylusStrokeMoved:(JotStroke *)stylusStroke
{
CGPoint location = [stylusStroke locationInView:self];
CGFloat pressure = [stylusStroke.pressure];
//... Rest of your stroke and draw code
}
By default our SDK will track a stylus everywhere on screen, but you may want to register a specific view or several views to only receive stylus events that begin in those views. You can specify which views have stylus interaction by registering your views with the JotStylusManager
.
[[JotStylusManager sharedInstance] registerView:yourDrawingView];
You can always unregister a view to stop receiving stylus events that begin in them. If you have no views registered the SDK will resume serving stylus events for the entire screen. Unregistering a view is very similar to registering it.
[[JotStylusManager sharedInstance] unregisterView:yourDrawingView];
You can also use the JotStylusManager to find out if a particular view is registered at any point in time.
BOOL isViewRegistered = [[JotStylusManager sharedInstance] isViewRegistered:yourDrawingView];
Palm rejection allows a user to rest their palm on the screen for a more comfortable writing and drawing experience. There are two parts to successful Palm Rejection.
The first part is only having the stylus mark the screen. To do this drive all drawing from our JotStrokeDelegate
events when a stylus is connected. You should also disable the drawing logic that is UITouch driven in your views or gestures once a stylus is connected.
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
// Only draw from JotStrokeDelegate stylus events if a stylus connected.
if ([JotStylusManager sharedInstance].isStylusConnected) { return; }
// rest of finger driven drawing code...
}
The second part of palm rejection is not triggering zoom, pan, and rotate gestures with a resting palm. To prevent a palm from triggering these gestures during a stylus stroke, you can disable/enable gestures using the JotStrokeDelegate
suggestion methods around disabling and enabling gestures.
/** Suggest to disable gestures when the pen is down to prevent conflict
*/
- (void)jotSuggestsToDisableGestures;
/** Suggest to enable gestures when the pen is not down as there are no potential conflicts
*/
- (void)jotSuggestsToEnableGestures;
The jotSuggestsToEnableGestures
has a delay timer that prevents it from enabling gestures between sequential fast strokes while writing or drawing. You can customize this duration.
// default delay is 1 second.
[JotStylusManager sharedInstance].suggestionToEnableGesturesDelay = 0.35;
Preventing gestures from firing when a palm is resting before a drawn stroke or after a drawn stroke is a bit more nuanced and complicated. It may take separate approaches per gesture to be most effective. One step you can do to quickly get your app part way there is to cut off any gestures that where triggered from our stylus, or that have a touch radius larger than a typical finger.
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch
{
if (touch.adonitTouchIdentification == AdonitTouchIdentificationTypeStylus) {
// NSLog(@"stylus touch avoided in shouldReceiveTouch");
return NO;
}
if ([touch respondsToSelector:@selector(majorRadius)]){
if (touch.majorRadius / touch.majorRadiusTolerance > 13.0) {
//NSLog(@"palm detected in shouldReceiveTouch");
return NO;
}
}
return YES;
}
For more information see our example app.
Copyright (c) 2021 Adonit. All rights reserved.