Files
Charles Wiltgen 3105b1118b fix(axiom-media): correct what reviewing the fixes found, and sweep the docs pages
An independent review of the audit's own fixes found defects the fixes had
introduced or left behind, and the hand-written docs pages — which restate many
of the same claims and are not generated from the skills — had not followed the
corrections.

Fixes that were wrong or incomplete:

- The paused-player elapsed fix was correct only where it was measured.
  `playerTime(forNodeTime:)` returns nil whenever the player is not playing —
  paused, stopped, or never started — and falling back to the lock screen's
  dictionary republished the *previous item's* elapsed on a track change. It now
  keeps an app-owned value, reset when a new item loads.
- "Other request types, hand-pose among them, still succeed" is false: measured
  across eleven Vision requests, only hand-pose and text recognition run.
  Barcodes, animal, body pose, human rectangles, saliency and feature print all
  fail to build an inference context, and an order-reversed control on a second
  device showed the failure is request-specific, not context exhaustion.
- `teardownCommands()` was left defined and called from nowhere. Two
  `isEnabled = true` writes contradicted the file's own rule that registering a
  target enables a command by default. The Bluetooth option legend that produced
  a recording-recipe straddle still omitted that A2DP is output-only routing.
- Two citations pointed at the wrong guide page, one range omitted the page
  holding its section's last step, and one quoted a sentence the guide does not
  contain.

The docs sweep found fifteen stale claims across the pages mirroring this suite,
including the automatic-passthrough mechanism the audit disproved, a fabricated
`prepare()` timing figure, and a fabricated set of diagnostic percentages.

`photo-library`'s PNG/JPEG/HEIC split is now measured rather than asserted:
`Image.importedContentTypes()` and `exportedContentTypes()` are both
`["public.jpeg", "public.png"]`, with HEIC and HEIF absent.

Cursor, Codex, inlined-auditor and MCP distributions regenerated.

Verified: npm test (static validation clean).
2026-09-17 09:45:56 -07:00

5.9 KiB
Raw Permalink Blame History

name, description, skill_type, version
name description skill_type version
haptics Use when implementing haptic feedback, Core Haptics patterns, audio-haptic synchronization, or debugging haptic issues - covers UIFeedbackGenerator, CHHapticEngine, AHAP patterns, and Apple's Causality-Harmony-Utility design principles reference 1.0

Haptics & Audio Feedback

Comprehensive guide to implementing haptic feedback on iOS with Core Haptics and UIFeedbackGenerator. Based on WWDC 2021 session 10278 (Practice audio haptic design).

Overview

This reference covers haptic feedback implementation from simple patterns to advanced audio-haptic synchronization:

  • Design Principles Causality, Harmony, Utility framework from WWDC 2021
  • UIFeedbackGenerator Simple haptic feedback for common interactions (iOS 10+)
  • Core Haptics Custom haptic patterns and audio-haptic synchronization (iOS 13+)
  • AHAP Files Apple Haptic Audio Pattern JSON format
  • Testing & Debugging Simulator limitations and device-specific behavior

System Requirements

  • iOS 10+ for UIFeedbackGenerator (basic haptics)
  • iOS 13+ for Core Haptics (CHHapticEngine)
  • iPhone 8+ for Core Haptics hardware support

Simulator Limitation: Haptics only work on physical devices. Always test on hardware.


Design Principles (WWDC 2021/10278)

Causality — What caused the feedback?

Haptic feedback should have a clear relationship to what triggered it.

Good: Button tap generates haptic when finger touches screen Poor: Haptic triggered half a second after interaction

Harmony — Senses work together

Combine visual, audio, and haptic feedback that reinforce each other.

Good: Sound + haptic for success confirmation Poor: Haptic alone with no visual/audio context

Utility — Provide clear value

Haptics should enhance understanding or provide information the user needs.

Good: Different haptic patterns for success vs error Poor: Same haptic for everything


Quick Start

Simple Haptics with UIFeedbackGenerator

For basic interactions, use UIFeedbackGenerator:

class HapticButton: UIButton {
    let impactGenerator = UIImpactFeedbackGenerator(style: .medium)

    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        super.touchesBegan(touches, with: event)
        impactGenerator.prepare()
    }

    override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
        super.touchesEnded(touches, with: event)
        impactGenerator.impactOccurred()
    }
}

Custom Haptics with Core Haptics

For complex patterns and audio-haptic synchronization:

import CoreHaptics

var engine: CHHapticEngine?

func initializeHaptics() {
    guard CHHapticEngine.capabilitiesForHardware().supportsHaptics else {
        return
    }

    do {
        engine = try CHHapticEngine()
        try engine?.start()
    } catch {
        print("Failed to create haptic engine: \(error)")
    }
}

Common Patterns

Button Tap

let impactGenerator = UIImpactFeedbackGenerator(style: .medium)
impactGenerator.impactOccurred()

Selection Change (Picker, Segmented Control)

let selectionGenerator = UISelectionFeedbackGenerator()
selectionGenerator.selectionChanged()

Success/Error/Warning

let notificationGenerator = UINotificationFeedbackGenerator()
notificationGenerator.notificationOccurred(.success)  // or .error, .warning

When to Use Core Haptics

Use Core Haptics when you need:

  1. Custom haptic patterns beyond basic impact/selection/notification
  2. Audio-haptic synchronization for games or creative apps
  3. Looping haptic patterns for continuous feedback
  4. Fine control over intensity, sharpness, and timing

Otherwise, stick with UIFeedbackGenerator for simplicity.


Troubleshooting

Haptics not working

Check: Are you testing on a physical device (iPhone 8+)?

  • Simulator doesn't support haptics
  • Some older devices don't have Taptic Engine

Check: Is the ringer/silent switch on?

  • Device must not be in silent mode (check Settings → Sounds & Haptics → System Haptics)

Engine fails to start

Solution: Handle engine stopped/reset events:

engine?.stoppedHandler = { reason in
    switch reason {
    case .audioSessionInterrupt, .applicationSuspended:
        // The cause is still in effect, so a restart would fail —
        // restart with the next user-initiated playback instead
        print("Waiting for user-initiated playback to restart")
    case .idleTimeout, .systemError:
        self.restartEngine()
    default:
        break
    }
}

engine?.resetHandler = {
    print("Engine reset")
    self.restartEngine()
}

Haptics feel weak or inconsistent

Check: Did you call prepare() before triggering?

  • Call prepare() shortly before the expected use — the Taptic Engine stays prepared for a short period (typically seconds), so call it again if more feedback is imminent
  • Reduces latency and ensures consistent response

WWDC Sessions

  • haptics Complete reference with AHAP patterns, advanced Core Haptics, audio synchronization