iOS SDK
Native iOS input via TAPKit. Pair in Settings first — TAPKit does not scan.
- Package
- Install
- Hello Tap
- TAPKitDelegate
- TAPInputMode
- Combinations
- Haptics
- Raw sensors
- TapXR Spatial Control
- Protocol versions
- V2 configuration
- Logging
Package
| Source | TapWithUs/tap-ios-sdk |
|---|---|
| License | Apache 2.0 |
| Status | Official |
| Language | Swift (Obj-C supported) |
| Requires | iOS 11.2+ (15.6+ recommended for the example app) |
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:
<key>NSBluetoothAlwaysUsageDescription</key>
<string>Bluetooth is required to connect to TAP devices.</string>import TAPKitObjective-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().
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 are V2 devices.
@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)
}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
- V2 debug — enables all V2 features at once (raw IMU + model detection + IMU motion + air-gesture vision stream)
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.
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.
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 V2 devices you can also change IMU sensitivity on the fly with setIMUSensitivity without re-entering the mode.
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.
TAPKit.sharedKit.setDefaultTAPXRState(TAPXRState.userControl(), applyImmediate: true)
TAPKit.sharedKit.setTAPXRState(TAPXRState.airMouse(), forIdentifiers: ["device-uuid"])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 may require authorization on some firmware — request access.
Protocol versions
TAP firmware speaks one of two BLE protocols. TAPKit detects which one per device on connect — no configuration needed. The same delegate callbacks and TAPInputMode / TAPXRState calls apply; TAPKit translates under the hood.
| Legacy (v1) | Classic characteristic-per-event. Hardware: Tap Strap, Tap Strap 2, TapXR. |
|---|---|
| V2 (framed) | One framed read/write pair. Hardware: TapXR with newer firmware. Adds device features, vision-sensor control, standby, serial number, keepalive. |
V2-only APIs (below) are ignored on legacy devices (a warning is logged). This split matches tap-python-sdk v1 (TapSDK) vs v2 (TapSDK2).
V2 configuration
Direct V2 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 viarawSensorDataReceived.modelDetection— on-device tap / air-gesture models.imuMotionData— IMU motion (moused,tapDidChangeOrientation).standbyGestureDetection— wake-gesture while in standby
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). V2 keepalive is sent automatically while the app is active. set* commands and the V2 enums are Obj-C visible; get* read-backs are Swift-only.
Logging
Levels: .warning, .error, .info, .fatal.
TAPKit.log.enableAllEvents()
TAPKit.log.enable(event: .error)
TAPKit.log.disableAllEvents()