Framework for integrating with motorized camera stands and gimbals that physically track subjects by rotating the iPhone. DockKit handles motor control, subject detection, and framing so camera apps get 360-degree pan and 90-degree tilt tracking with no additional code. Apps can override system tracking to supply custom observations, control motors directly, or adjust framing. iOS 17+, Swift 6.3.
Contents
- Workflow
- Route by Task
- Core Decisions
- Common Mistakes
- Review Checklist
- References
Workflow
- Gate the feature on DockKit availability and a connected compatible accessory; plan physical-device verification.
- Start and retain the accessory session, observe lifecycle events, and choose system tracking or custom tracking.
- For custom tracking, provide observations at the supported cadence with valid camera geometry and confidence.
- Disable system tracking before direct motor commands, enforce limits, and restore the intended mode afterward.
- Verify disconnect/reconnect, tracking loss, subject changes, thermal/battery state, and cancellation.
Route by Task
- Read core implementation details for accessory discovery, system/custom tracking, framing, motor control, animations, events, and battery state.
- Read extended DockKit patterns for Vision integration, service architecture, observation pipelines, and custom animations.
Core Decisions
- Do not mix system subject tracking and direct motor control concurrently.
- Treat camera intrinsics, orientation, and observation coordinates as explicit inputs.
- Rate-limit tracking observations and motor/animation commands.
- Restore safe tracking state after lifecycle transitions and failures.
Common Mistakes
DON'T: Control motors without disabling system tracking
// WRONG -- system tracking fights manual commands
try await accessory.setAngularVelocity(velocity)
// CORRECT -- disable system tracking first
try await DockAccessoryManager.shared.setSystemTrackingEnabled(false)
try await accessory.setAngularVelocity(velocity)
DON'T: Assume tracking state persists across lifecycle events
// WRONG -- state may have reset after backgrounding
func applicationDidBecomeActive() {
// Assume custom tracking is still active
}
// CORRECT -- re-set tracking state on foreground
func applicationDidBecomeActive() {
Task {
try await DockAccessoryManager.shared.setSystemTrackingEnabled(false)
}
}
DON'T: Call track() outside the recommended rate
// WRONG -- calling once per second is too slow
try await accessory.track(observations, cameraInformation: cameraInfo)
// (called at 1 fps)
// CORRECT -- call at 10-30 fps
// Hook into AVCaptureVideoDataOutputSampleBufferDelegate for per-frame calls
DON'T: Spam orientation or animation calls
DockKit can throw .frameRateTooHigh if animate(motion:) or
setOrientation(_:duration:relative:) is called more than twice per second.
Set a trajectory, observe its Progress, and avoid tight command loops.
DON'T: Forget to restore tracking after animations
// WRONG -- tracking stays disabled after animation
try await DockAccessoryManager.shared.setSystemTrackingEnabled(false)
let progress = try await accessory.animate(motion: .kapow)
// CORRECT -- restore tracking when animation completes
try await DockAccessoryManager.shared.setSystemTrackingEnabled(false)
let progress = try await accessory.animate(motion: .kapow)
while !progress.isFinished && !progress.isCancelled {
try await Task.sleep(for: .milliseconds(100))
}
try await DockAccessoryManager.shared.setSystemTrackingEnabled(true)
DON'T: Use DockKit in Simulator
DockKit requires a physical DockKit-compatible accessory. Guard initialization and provide fallback behavior when no accessory is available.
Review Checklist
-
import DockKitpresent where needed - Subscribed to
accessoryStateChangesto detect dock/undock events - Handled both
.dockedand.undockedstates - System tracking disabled before custom tracking or motor control
- System tracking restored after animations complete
- Custom observations supplied at 10-30 fps
-
animateandsetOrientationcommands limited to 2 calls per second - Observation
rectuses normalized coordinates (lower-left origin) - Camera information is built inline from the active
AVCaptureDeviceand current sample buffer - Observation type choice names
.humanFace,.humanBody, and.object -
@unknown defaulthandled in all switch statements over DockKit enums - Motion limits set if restricting accessory range of motion
- Tracking state re-applied after app returns to foreground
-
accessoryEventsguarded with#available(iOS 17.4, *) -
trackingStatesandbatteryStatesguarded with#available(iOS 18.0, *) - Battery UI preserves
BatteryState.namefor multi-battery docks - No DockKit code paths executed in Simulator builds
References
- Extended patterns (Vision integration, service architecture, custom animations): references/dockkit-patterns.md
- Error handling and unit testing: references/dockkit-errors-and-testing.md
- DockKit framework
- DockAccessoryManager
- DockAccessory
- Controlling a DockKit accessory using your camera app
- Track custom objects in a frame
- Modify rotation and positioning programmatically
- Integrate with motorized iPhone stands using DockKit -- WWDC23
- What's new in DockKit -- WWDC24
- Core implementation details -- setup, API wiring, and focused implementation recipes moved out of the entrypoint.