TAP

iOS SDK

Native iOS input via TAPKit. Pair in Settings first — TAPKit does not scan.

  1. Package
  2. Install
  3. Hello Tap
  4. TAPKitDelegate
  5. TAPInputMode
  6. Combinations
  7. Haptics
  8. Raw sensors
  9. TapXR Spatial Control
  10. Device protocols
  11. TapXR features
  12. Logging

Package

Install

Clone the repo, open TAPKit.xcodeproj, build the TAPKit scheme, then embed TAPKit.framework (Embed & Sign). The Xcode project also ships TAPKit-Example and TAPKitUnityBridge.

Add to your app’s Info.plist:

Info.plist
<key>NSBluetoothAlwaysUsageDescription</key>
<string>Bluetooth is required to connect to TAP devices.</string>
Import
import TAPKit

Objective-C: #import <TAPKit/TAPKit-Swift.h>.

Hello Tap

Call start() once (typically when your main screen appears). Add one or more TAPKitDelegate implementations before or after start().

HelloTapViewController.swift
import UIKit
import TAPKit

class HelloTapViewController: UIViewController, TAPKitDelegate {
    override func viewDidLoad() {
        super.viewDidLoad()
        // Device must already be paired in iOS Settings → Bluetooth.
        TAPKit.log.disableAllEvents()          // optional
        TAPKit.sharedKit.addDelegate(self)
        TAPKit.sharedKit.setDefaultTAPInputMode(.controller(), immediate: true)
        TAPKit.sharedKit.setDefaultTAPXRState(TAPXRState.airMouse(), applyImmediate: true)  // TapXR only
        TAPKit.sharedKit.start()
    }

    override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)
        TAPKit.sharedKit.removeDelegate(self)
    }

    func tapConnected(withIdentifier identifier: String, name: String) {
        print("Connected: \(name) (\(identifier))")
    }

    func tapped(identifier: String, combination: UInt8, multitap: UInt8) {
        let fingers = TAPCombination.toFingers(combination)
        print("Tap from \(identifier): \(fingers), multitap: \(multitap)")
    }

    func moused(identifier: String, velocityX: Int16, velocityY: Int16, isMouse: Bool) {
        guard isMouse else { return }
        print("mouse", velocityX, velocityY)
    }
}

TAPKitDelegate

All methods are optional. Multiple delegates are supported. Register with addDelegate. tapped now includes multitap (1–3 when applicable). Hold events fire only in Tap Hold mode. Serial number and standby apply on TapXR with the new developer firmware.

TAPKitDelegate
@objc public protocol TAPKitDelegate : class {
    @objc optional func tapConnected(withIdentifier identifier: String, name: String)
    @objc optional func tapDisconnected(withIdentifier identifier: String)
    @objc optional func tapFailedToConnect(withIdentifier identifier: String, name: String)

    @objc optional func tapped(identifier: String, combination: UInt8, multitap: UInt8)
    @objc optional func moused(identifier: String, velocityX: Int16, velocityY: Int16, isMouse: Bool)
    @objc optional func rawSensorDataReceived(identifier: String, data: RawSensorData)

    @objc optional func tapChangedAirGesturesState(identifier: String, isInAirGesturesState: Bool)
    @objc optional func tapAirGestured(identifier: String, gesture: TAPAirGesture)
    @objc optional func tapXRAirGestured(identifier: String, gesture: TAPXRAirGesture)

    @objc optional func tapDidReadHardwareVersion(identifier: String, hw: Int)
    @objc optional func tapDidReadFirmwareVersion(identifier: String, fw: Int)
    @objc optional func tapDidReadBatteryLevel(identifier: String, batteryLevel: Int)

    @objc optional func tapHoldStarted(identifier: String, combination: UInt8)
    @objc optional func tapHoldEnded(identifier: String, combination: UInt8)

    @objc optional func tapDidChangeOrientation(roll: Int, pitch: Int, yaw: Int)

    @objc optional func tapChangedStandbyState(identifier: String, isInStandby: Bool)
    @objc optional func tapDidReadSerialNumber(identifier: String, serialNumber: String)
}
swift
TAPKit.sharedKit.addDelegate(self)
TAPKit.sharedKit.start()
// …
TAPKit.sharedKit.removeDelegate(self)

Connected taps: TAPKit.sharedKit.getConnectedTaps() → dictionary of identifier → display name. Version and battery reads run on connect; you can also call readHardwareVersion(), readFirmwareVersion(), and readBatteryLevel(). HW/FW integers are MMmmbb (example: 30200 → 3.2.0).

TAPInputMode

  • Controller (default) — tapped / moused
  • Text — HID keyboard; SDK tap callbacks are not fired
  • Controller with Mouse HID — controller + system mouse (AssistiveTouch cursor on iOS 13+)
  • Controller with Full HID — full HID controller mode
  • Tap Hold — short tap vs long press via tapHoldStarted / tapHoldEnded
  • Raw Sensor — gyro / accelerometer stream
  • Debug (TapXR) — enables TapXR features at once (raw IMU + model detection + IMU motion + air-gesture vision stream)
swift
TAPKit.sharedKit.setTAPInputMode(TAPInputMode.text(), forIdentifiers: nil)
TAPKit.sharedKit.setTAPInputMode(TAPInputMode.controller(), forIdentifiers: nil)
TAPKit.sharedKit.setTAPInputMode(TAPInputMode.tapHold(), forIdentifiers: nil)
TAPKit.sharedKit.setDefaultTAPInputMode(TAPInputMode.controller(), immediate: true)
let mode = TAPKit.sharedKit.getTAPInputMode(identifier: uuid)

Pass nil for forIdentifiers to apply to all connected devices. Newly connected devices receive the default set via setDefaultTAPInputMode(_:immediate:).

Combinations

combination is UInt8 in 1…31. Bit0 = thumb … bit4 = pinky. Example: 5 → thumb + middle.

swift
let fingers = TAPCombination.toFingers(combination)
let combo = TAPCombination.fromFingers(thumb, index, middle, ring, pinky)
let names = TAPCombination.combinationSpeakableString(for: combination)

Haptics

Up to 18 durations (ms) as haptic, pause, haptic, pause… Null/missing identifiers = all connected taps.

swift
TAPKit.sharedKit.vibrate(durations: [500, 100, 500], forIdentifiers: nil)

Raw sensors

Five 3-axis finger accelerometers + thumb IMU (Tap Strap 2). Stream ~200 messages/minute. Sensitivities: deviceAccelerometer 1–4, imuGyro 1–4, imuAccelerometer 1–5. On TapXR you can also change IMU sensitivity on the fly with setIMUSensitivity without re-entering the mode.

swift
let sensitivity = TAPRawSensorSensitivity(
    deviceAccelerometer: 2, imuGyro: 2, imuAccelerometer: 3)
TAPKit.sharedKit.setTAPInputMode(
    TAPInputMode.rawSensor(sensitivity: sensitivity), forIdentifiers: nil)

func rawSensorDataReceived(identifier: String, data: RawSensorData) {
    switch data.type {
    case .Device:
        if let thumb = data.getPoint(for: RawSensorData.iDEV_THUMB) {
            print("Thumb accel:", thumb.x, thumb.y, thumb.z)
        }
    case .IMU:
        if let gyro = data.getPoint(for: RawSensorData.iIMU_GYRO) {
            print("Gyro:", gyro.x, gyro.y, gyro.z)
        }
    default:
        break
    }
}

TapXR Spatial Control

TAPXRState is separate from input mode: userControl() (user switches air-mouse / tapping), airMouse(), tapping(), or dontSend(). applyImmediate: true applies the default to already-connected devices.

swift
TAPKit.sharedKit.setDefaultTAPXRState(TAPXRState.userControl(), applyImmediate: true)
TAPKit.sharedKit.setTAPXRState(TAPXRState.airMouse(), forIdentifiers: ["device-uuid"])

TapXR on the new developer firmware supports the full gesture set (taps, thumb-to-finger pinches, pinch-and-drag, thumb swipes, fist). Raw air gestures (swipes, pinches, fist) are available in Python, Web and Android (Android via onAirMouseInputReceived). iOS delivers TapXR air gestures through tapXRAirGestured. Spatial gestures arrive on tapXRAirGestured as TAPXRAirGesture: click / drag / potential-drag per finger, drop, fist begin/end, and swipe left/right/up/down. Prefer this over the XR cases on the legacy TAPAirGesture enum. Spatial Control states 100–102 may require authorization on some firmware — request access.

Device protocols

The Tap SDK detects the device on connect — no configuration needed. The same delegate callbacks and TAPInputMode / TAPXRState calls apply; TAPKit translates under the hood.

Tap StrapClassic BLE characteristics. Hardware: Tap Strap and Tap Strap 2.
TapXRTapXR on the new developer firmware. Adds device features, vision-sensor control, standby, serial number, keepalive.

TapXR-only APIs (below) are ignored on Tap Strap (a warning is logged). To the developer there is one Tap SDK; the same callbacks apply.

TapXR features

Direct TapXR primitives. Input modes also write these features under the hood — use the direct APIs for fine-grained control or read-back. Getters take an optional timeout: (default 2s) and run completions on the main queue.

  • .rawIMUData — raw IMU packets via rawSensorDataReceived
  • .modelDetection — on-device tap / air-gesture models
  • .imuMotionData — IMU motion (moused, tapDidChangeOrientation)
  • .standbyGestureDetection — wake-gesture while in standby
TapXR
TAPKit.sharedKit.setFeature(.modelDetection, enabled: true, forIdentifiers: [uuid])
TAPKit.sharedKit.getFeature(.modelDetection, forIdentifier: uuid) { enabled in
    print("modelDetection:", String(describing: enabled))  // nil on timeout
}

TAPKit.sharedKit.setVisionSensorOpMode(.stream, forIdentifiers: [uuid])   // .trigger / .streamOnTrigger / .stream
TAPKit.sharedKit.setVisionSensorModel(.airGesture, forIdentifiers: [uuid]) // .tapping / .airGesture

TAPKit.sharedKit.setIMUSensitivity(gyro: 2, accelerometer: 1, forIdentifiers: [uuid])  // gyro 0–5, accel 0–4
TAPKit.sharedKit.setStandbyState(true, forIdentifiers: [uuid])
TAPKit.sharedKit.readSerialNumber(forIdentifiers: nil)

Background: sendModeInBackground defaults to false (SDK switches to text mode + user-control XR on background). A keepalive is sent automatically while the app is active. set* commands and the TapXR enums are Obj-C visible; get* read-backs are Swift-only.

Logging

Levels: .warning, .error, .info, .fatal.

swift
TAPKit.log.enableAllEvents()
TAPKit.log.enable(event: .error)
TAPKit.log.disableAllEvents()