flutter_apple_pencil

A Flutter plugin for detecting Apple Pencil interactions including double-tap and squeeze gestures.

Features

  • ✅ Detect Apple Pencil double-tap gestures (Apple Pencil 2nd generation and later)
  • ✅ Detect Apple Pencil squeeze gestures (Apple Pencil Pro)
  • ✅ Real-time event streaming
  • ✅ Support for iOS 12.1 and later

Platform Support

Platform Supported
iOS
Android
Web
macOS
Windows
Linux

Requirements

  • iOS 12.1 or later
  • Apple Pencil (2nd generation) for double-tap
  • Apple Pencil Pro for squeeze gesture
  • Flutter 3.3.0 or later

Installation

Add the dependency to your pubspec.yaml:

flutter pub add flutter_apple_pencil

Then run:

flutter pub get

Usage

Basic Setup

import 'package:flutter_apple_pencil/flutter_apple_pencil.dart';

// Create an instance
final applePencil = FlutterApplePencil();

// Check if Apple Pencil interactions are supported
final isSupported = await applePencil.isSupported();

if (isSupported) {
  // Initialize the plugin
  await applePencil.initialize();

  // Listen to double-tap events
  applePencil.onPencilDoubleTap.listen((PencilTap tap) {
    print('Double-tap detected at ${tap.timestamp}');
    if (tap.hoverPose != null) {
      print('Hover z-offset: ${tap.hoverPose!.zOffset}');
    }
  });

  // Listen to squeeze events (Apple Pencil Pro)
  applePencil.onPencilSqueeze.listen((PencilSqueeze squeeze) {
    print('Squeeze detected at ${squeeze.timestamp} (phase=${squeeze.phase})');
    if (squeeze.hoverPose != null) {
      print('Hover location: ${squeeze.hoverPose!.location}');
    }
  });

  // Read user preference values (optional)
  final preferredTap = await applePencil.preferredTapAction;
  final preferredSqueeze = await applePencil.preferredSqueezeAction;
  final pencilOnly = await applePencil.prefersPencilOnlyDrawing;
  final hoverPreview = await applePencil.prefersHoverToolPreview;
}

// When done
applePencil.dispose();

Complete Example

See the example directory for a complete sample application.

Cleanup

Don't forget to dispose of the plugin when you're done:

@override
void dispose() {
  applePencil.dispose();
  super.dispose();
}

API Reference

FlutterApplePencil

Core plugin class to detect Apple Pencil interactions and query user preferences.

Methods & getters

  • Future<void> initialize()
    • Initialize native listeners and start streaming events.
  • Future<bool> isSupported()
    • Returns true when the current device and OS support Apple Pencil interactions.
  • Future<void> dispose()
    • Stop listeners and free native resources. Call from dispose() in your widget.
  • Stream<PencilTap> get onPencilDoubleTap
    • Stream that emits PencilTap events when the Apple Pencil double-tap occurs.
  • Stream<PencilSqueeze> get onPencilSqueeze
    • Stream that emits PencilSqueeze events for Apple Pencil Pro squeeze gestures.

Preference getters (async)

  • Future<PencilPreferredAction?> get preferredTapAction
  • Future<PencilPreferredAction?> get preferredSqueezeAction
  • Future<bool> get prefersPencilOnlyDrawing
  • Future<bool> get prefersHoverToolPreview

Event classes and enums

PencilTap

  • DateTime timestamp — when the tap was received
  • PencilHoverPose? hoverPose — optional hover details (location, z-offset, angles)

PencilSqueeze

  • DateTime timestamp — when the squeeze occurred
  • PencilInteractionPhase phase — the squeeze phase (began/changed/ended/cancelled)
  • PencilHoverPose? hoverPose — optional hover details

PencilHoverPose

  • Offset location — position in logical pixels
  • double zOffset — Z distance from the screen
  • double altitudeAngle — altitude (radians)
  • double azimuthAngle — azimuth (radians)
  • double rollAngle — roll (radians), may be zero when not available

PencilPreferredAction (enum)

  • ignore
  • switchEraser
  • switchPrevious
  • showColorPalette
  • showInkAttributes
  • showContextualPalette
  • runSystemShortcut

PencilInteractionPhase (enum)

  • began
  • changed
  • ended
  • cancelled

How It Works

The plugin uses native iOS APIs to detect Apple Pencil interactions:

  • Double-tap: Uses UIPencilInteraction delegate methods:
    • iOS 17.5+: pencilInteraction(_:didReceiveTap:) (new API)
    • iOS 12.1-17.4: pencilInteractionDidTap(_:) (deprecated but supported for backward compatibility)
  • Squeeze: Uses pencilInteraction(_:didReceiveSqueeze:) on Apple Pencil Pro (iOS 17.5+)

The native code communicates with Flutter using Method Channels for initialization and Event Channels for streaming interaction events.

Limitations

  • Only works on iOS devices with Apple Pencil support
  • Double-tap requires Apple Pencil (2nd generation) or later
  • Squeeze requires Apple Pencil Pro
  • The app must be in the foreground to receive events

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

License

This project is licensed under the MIT License - see the LICENSE file for details.

Acknowledgments

Author

👤 Filippo Finke