Skip to main content

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.

Published 3 minute read

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.