Implementing Core Haptics for iOS App
Core Haptics appeared in iOS 13 and works on iPhone 8 and newer. Unlike UIImpactFeedbackGenerator (canned system patterns), Core Haptics allows creating arbitrary tactile sensations: rise shape, duration, combination of vibration and audio tone through one engine — CHHapticEngine.
How CHHapticEngine Works
The engine works with events of two types: CHHapticEvent.EventType.hapticTransient (short "click", like button press) and CHHapticEvent.EventType.hapticContinuous (sustained vibration). Each event has parameters attached: intensity (hapticIntensity), sharpness (hapticSharpness).
import CoreHaptics
class HapticsManager {
private var engine: CHHapticEngine?
func prepareEngine() {
guard CHHapticEngine.capabilitiesForHardware().supportsHaptics else { return }
do {
engine = try CHHapticEngine()
engine?.playsHapticsOnly = true
try engine?.start()
} catch {
print("CoreHaptics engine error: \(error)")
}
// Recovery after interruption (call, another app)
engine?.resetHandler = { [weak self] in
try? self?.engine?.start()
}
engine?.stoppedHandler = { reason in
print("Haptic engine stopped: \(reason)")
}
}
}
playsHapticsOnly = true — if synchronized audio tone is not needed. Without this flag, CHHapticEngine also manages audio through CoreAudio, which requires audio session setup.
Creating Patterns
Complex pattern is an array of CHHapticEvent with time stamps:
func playSuccessPattern() throws {
guard let engine = engine else { return }
let events: [CHHapticEvent] = [
// Quick click
CHHapticEvent(
eventType: .hapticTransient,
parameters: [
CHHapticEventParameter(parameterID: .hapticIntensity, value: 0.5),
CHHapticEventParameter(parameterID: .hapticSharpness, value: 0.8)
],
relativeTime: 0
),
// Rising vibration
CHHapticEvent(
eventType: .hapticContinuous,
parameters: [
CHHapticEventParameter(parameterID: .hapticIntensity, value: 1.0),
CHHapticEventParameter(parameterID: .hapticSharpness, value: 0.3)
],
relativeTime: 0.1,
duration: 0.4
),
// Final click
CHHapticEvent(
eventType: .hapticTransient,
parameters: [
CHHapticEventParameter(parameterID: .hapticIntensity, value: 0.8),
CHHapticEventParameter(parameterID: .hapticSharpness, value: 1.0)
],
relativeTime: 0.55
)
]
let pattern = try CHHapticPattern(events: events, parameters: [])
let player = try engine.makePlayer(with: pattern)
try player.start(atTime: CHHapticTimeImmediate)
}
hapticSharpness — subjective "sharpness" of vibration: 1.0 is sharp click-like impulse, 0.0 is soft deep buzz. Combining these two parameters over time gives the "character" of the sensation.
Dynamic Parameter Modification
CHHapticDynamicParameter allows changing pattern in real-time — for example, increase vibration as slider is pressed:
func updateIntensity(_ value: Float) {
let dynamicParam = CHHapticDynamicParameter(
parameterID: .hapticIntensityControl,
value: value,
relativeTime: 0
)
try? continuousPlayer?.sendParameters([dynamicParam], atTime: 0)
}
This is key feature for games and interactive interfaces: feedback changes in sync with user action.
AHAP Files
Apple Haptic and Audio Pattern (.ahap) — JSON format for describing patterns:
{
"Version": 1.0,
"Pattern": [
{
"Event": {
"Time": 0.0,
"Type": "HapticTransient",
"Parameters": [
{ "ParameterID": "HapticIntensity", "ParameterValue": 1.0 },
{ "ParameterID": "HapticSharpness", "ParameterValue": 0.5 }
]
}
}
]
}
Load from file: engine?.playPattern(from: url). Designer can edit .ahap without code changes. Xcode has built-in Core Haptics Composer for visual pattern creation.
Typical Issues
Engine stops when app transitions to background — CHHapticEngine.stoppedHandler is called with .applicationSuspended. On return to foreground, you need to recreate or restart engine. resetHandler must be set before startup.
Simulator doesn't support Core Haptics — test only on real iPhone 8+ device. CHHapticEngine.capabilitiesForHardware().supportsHaptics returns false on simulator and iPad.
Delay on first start — CHHapticEngine initialization takes ~50–100 ms. Call prepareEngine() beforehand, not at event time.
Timeline Benchmarks
Basic patterns (2–3 types) with correct engine initialization and interrupt handling — 1 day. Dynamic patterns with real-time parameter changes, AHAP files, game event integration — 2–3 days.







