iOS developers
Core Haptics beyond UIFeedbackGenerator
Choose system feedback for familiar interactions, then use Core Haptics when you need a custom rhythm, texture or audio cue.
Start with the interaction
A changed selection usually needs selection feedback. A completed operation may need a success notification. Neither requires you to design a new pattern.
UIKit’s concrete feedback generators cover impact, selection, notification and canvas interactions. Core Haptics is useful when the interaction needs a rhythm or evolving texture you author yourself. Keep both approaches available; adopting Core Haptics does not mean replacing the feedback on every button. UIFeedbackGenerator
| Requirement | Starting point |
|---|---|
| An item becomes selected | System selection feedback |
| A task succeeds, warns or fails | System notification feedback |
| A visual object collides or snaps | System impact feedback |
| A drawing-specific interaction | Canvas feedback where supported |
| A custom rhythm or changing sustained sensation | Core Haptics |
| Audio and tactile events in one authored sequence | Core Haptics |
Let SwiftUI watch a meaningful change
SwiftUI’s sensoryFeedback(_:trigger:) plays feedback when an equatable trigger changes. This small custom control moves to the next marker; the changed marker is the trigger, so merely redrawing the view does not replay the cue. Sensory feedback
import SwiftUI
struct MarkerStepper: View {
@State private var marker = 1
var body: some View {
VStack {
Text("Marker \(marker)")
Button("Next marker") {
marker += 1
}
}
.sensoryFeedback(.selection, trigger: marker)
}
}
This is a standalone view for a SwiftUI app, using APIs available from iOS 17. Feedback availability depends on the platform and device. When adapting the example to a standard control, first check the feedback it already provides so you do not add a second cue for the same change.
For UIKit, retain a generator for the interaction and call prepare() when you can anticipate the event. Preparation needs lead time and is temporary; it is not a permanent low-latency mode. Preparing a generator
Author a custom event
Here is a complete pattern-building function. It creates a short rising continuous haptic. It deliberately returns data rather than creating an engine on every interaction.
import CoreHaptics
func makeRisingTexture() throws -> CHHapticPattern {
let event = CHHapticEvent(
eventType: .hapticContinuous,
parameters: [
CHHapticEventParameter(parameterID: .hapticIntensity, value: 0.6),
CHHapticEventParameter(parameterID: .hapticSharpness, value: 0.3)
],
relativeTime: 0,
duration: 0.4
)
let intensity = CHHapticParameterCurve(
parameterID: .hapticIntensityControl,
controlPoints: [
.init(relativeTime: 0, value: 0.2),
.init(relativeTime: 0.3, value: 1),
.init(relativeTime: 0.4, value: 0)
],
relativeTime: 0
)
return try CHHapticPattern(events: [event], parameterCurves: [intensity])
}
The event’s base intensity is 0.6. The intensity curve multiplies it: the authored intensity starts at 0.12, reaches 0.6, then falls to zero. The values are an editable example, not a device-independent calibration. Curves apply across a pattern, not just to the event drawn beside them. Parameter curves, dynamic parameter semantics
For a designer-owned pattern, put the same data in an AHAP file. AHAP files explained includes downloads and complete file-playback examples.
Give the engine an owner
Pattern construction is only one part of integration. Your playback component should retain the engine and the players it needs, report failures and respect the app’s haptic preference. Check built-in support with CHHapticEngine.capabilitiesForHardware().supportsHaptics before creating the device engine.
Install stoppedHandler and resetHandler before starting. A stop can be a normal interruption; restart when playback is appropriate again. A media-server reset also invalidates players and registered audio, so rebuild those resources. Do not replay a stale success cue simply because the app returned to the foreground. Preparing your app to play haptics
Use a standard pattern player when start and stop are enough. Use an advanced player when you need pause, resume, seeking or looping, and handle creation errors. playPattern(from:) is convenient for one-shot playback, but it does not give you a player you can later stop. Advanced players, file playback
Test the lifecycle as well as the cue
Run on supported physical hardware. Check the first interaction after launch, a rapid repeat, return from the background and an audio interruption. Also test the unsupported-device path: visible feedback and the operation itself must still work.
If you need controller output, use a controller-created engine and query its capabilities separately; the built-in hardware check does not answer that question. Game feel through touch shows that distinction. For timing across sound and visuals, continue with Syncing haptics with audio and animation.
More to read
Design haptics in Haptrix
Free, for Mac, iPad and iPhone: the editor, export and publishing included.
New articles by email
Email updates are not switched on yet.