Skip to content
ExponaBLOG
SWIFT, SWIFTUI & IOS APPS · DAY 092 / 100

Haptics in iOS: concepts, trade-offs and production lessons

Subtle feedback that feels native.

Expona Admin6 min read

Haptics in iOS: concepts, trade-offs and production lessons

Most developers focus on visuals and sound when polishing an app's user experience, but there's a third sense we often overlook: touch. Haptic feedback—those subtle vibrations your phone produces in response to user actions—creates a layer of tactile confirmation that users expect from native iOS apps, yet many of us deploy it inconsistently or incorrectly. This guide covers the practical concepts, real trade-offs we've faced in production, and concrete lessons from shipping haptics across multiple platforms.

What is sensoryFeedback modifier in SwiftUI?

SwiftUI's sensoryFeedback modifier (introduced in iOS 17) is the modern, declarative way to trigger haptic feedback. It replaces the older UIImpactFeedbackGenerator and UINotificationFeedbackGenerator approach with a cleaner syntax that integrates directly into your view hierarchy.

Here's the basic pattern:

swift
Button("Confirm") {
    // action logic
}
.sensoryFeedback(.success, trigger: confirmPressed)

The modifier takes two arguments: a trigger (which can be a boolean, a value conforming to Equatable, or nothing at all) and a feedback type. When the trigger changes, the haptic fires automatically. This declarative approach means you don't need to manually instantiate generators or manage lifecycle—the system handles it.

If you need to support iOS 16 or earlier, you'll still reach for the older imperative API:

swift
let impact = UIImpactFeedbackGenerator(style: .medium)
impact.prepare()
impact.impactOccurred()

We recommend targeting iOS 17+ in new projects if your user base allows it. The sensoryFeedback modifier is more ergonomic and less error-prone—there's no way to forget the prepare() call.

Success, warning and selection feedback types

iOS provides several built-in feedback types, each with distinct meanings to the user. Using the right type is crucial; the wrong one breaks the user's mental model of what just happened.

Success feedback

.success is a three-phase haptic pattern: light, medium, light. Users have learned that this pattern means "task completed" or "positive outcome." Use it when a payment processes, a message sends, or a form validates successfully.

swift
Button("Send Payment") {
    task {
        do {
            try await paymentService.process()
            paymentConfirmed = true
        } catch {
            // handle error
        }
    }
}
.sensoryFeedback(.success, trigger: paymentConfirmed)

Warning feedback

.warning is a double-hit pattern: medium, pause, medium. It signals something negative or risky—a deletion, expiration, or connection loss. We've learned to use this sparingly. In one ride-hailing app, we initially fired warning haptics too frequently during network reconnections, and users reported it as annoying rather than informative.

swift
Button(role: .destructive) {
    showDeleteConfirmation = true
}
.sensoryFeedback(.warning, trigger: showDeleteConfirmation)

Selection feedback

.selection is a single, light tap. It's for non-transactional events—scrolling, swiping, or changing a picker value. It's the most subtle and the most commonly appropriate for interactive elements.

swift
Picker("Priority", selection: $priority) {
    ForEach(Priority.allCases, id: \.self) { p in
        Text(p.name).tag(p)
    }
}
.sensoryFeedback(.selection, trigger: priority)

We've found that .selection is overused in early designs. Many interactions don't need haptic confirmation at all—the visual feedback is enough.

Impact feedback and style variations

Beyond notification types, there's also .impact, which comes in three styles: .light, .medium, and .heavy. Impact feedback is more mechanical, mimicking the feel of collision or weight. Use it for physical metaphors—dropping an item, collapsing a menu, or starting a recording.

swift
Button("Start Recording") {
    recordingActive = true
}
.sensoryFeedback(.impact(weight: .heavy), trigger: recordingActive)

In production, we've used .light for rapid-fire interactions (like toggling a favorite in a list) and .heavy for deliberate, high-consequence actions. .medium is the sensible default.

Use haptics sparingly—when and when not to fire

This is where production reality diverges from design ideals. Every haptic you add sounds reasonable in isolation, but cumulatively they become exhausting. Users will disable haptics system-wide if your app overwhelms them with feedback.

We follow this rule: fire haptics only for actions that the user initiated or that represent a significant state change. Don't fire haptics for:

  • Animations or transitions without user input
  • List scrolling (unless it's a picker snap-to-position)
  • Push notifications or background state changes
  • Minor form validation (like a field gaining focus)
  • Routine data fetches or silent refreshes

In a travel app we shipped, the initial design had haptics on every search filter click, every date picker interaction, and every favorite toggle. Users complained about a "buzzing phone" within hours. We cut it back to success/warning for critical outcomes (booking confirmation, payment failure) and selection for direct picker interactions only. Engagement metrics improved and support complaints dropped.

A good heuristic: if the user would expect a sound, add a haptic. If the user wouldn't expect a sound, skip it.

Respecting system settings and accessibility

iOS allows users to disable haptics globally (Settings → Sound & Haptics → toggle off) or per-app. The system framework respects this automatically—if a user has disabled haptics, sensoryFeedback will silently not fire. You don't need to check any settings yourself.

However, there are accessibility considerations worth understanding:

  • Haptics and vibration perception: Haptics are primarily for able-bodied users with typical tactile sensation. Users with certain disabilities may not perceive or benefit from haptic feedback. Never rely on haptics as the sole feedback mechanism.
  • Haptics + audio + visual: Always pair haptics with visual or audio feedback. In a SaaS app, a form submission success requires not just a haptic but also a toast message or status indicator.
  • Low-power mode: When the device is in Low Power Mode, haptics may be reduced or disabled. Your app should still function correctly; the haptic is just a bonus.
swift
// Good: haptic is supplementary, not primary
struct ConfirmationAlert: View {
    @State var showSuccess = false
    
    var body: some View {
        VStack {
            if showSuccess {
                Image(systemName: "checkmark.circle.fill")
                    .foregroundColor(.green)
                    .font(.system(size: 48))
                Text("Payment confirmed")
                    .font(.headline)
            }
        }
        .sensoryFeedback(.success, trigger: showSuccess)
    }
}

Common mistakes we've made and fixed

Firing the same haptic twice in rapid succession: If you trigger haptic feedback from multiple sources (a button tap and a state change), you might fire two haptics 100ms apart, which feels jarring. Use a debounce or a single, authoritative trigger point.

swift
// Bad: double-fire risk
Button("Submit") {
    formSubmitted = true
    Task {
        await submitForm()
    }
}
.sensoryFeedback(.success, trigger: formSubmitted)

// Good: single trigger from the async task
Button("Submit") {
    Task {
        let success = await submitForm()
        if success {
            formSubmitted = true
        }
    }
}
.sensoryFeedback(.success, trigger: formSubmitted)

Using haptics without visual feedback: We shipped a confirmation dialog that had only a haptic for deletion. Users didn't understand if the action worked. Always pair haptics with UI changes.

Forgetting to prepare generators (older API): If you use UIImpactFeedbackGenerator, calling prepare() beforehand reduces latency. Skip this, and the haptic fires late or feels sluggish.

Firing haptics on background state changes: A notification arriving shouldn't trigger in-app haptics—the system notification haptic is already firing. It creates a confusing double-tap sensation.

Key takeaways

  • Use sensoryFeedback modifier in iOS 17+ for declarative, ergonomic haptic feedback; fall back to UIImpactFeedbackGenerator for earlier versions.
  • .success, .warning, and .selection types map to user expectations; use the right type for the right context.
  • Fire haptics only for user-initiated actions or significant state changes; overuse leads to users disabling haptics system-wide.
  • Never rely on haptics as the sole feedback mechanism; always pair with visual or audio cues.
  • The system respects user settings and Low Power Mode automatically; you don't need manual checks.
  • Avoid double-fires by consolidating feedback to a single, authoritative trigger.
  • Test with haptics both enabled and disabled to ensure your UI is comprehensible either way.
ShareXLinkedInReddit

Found this useful? Support the writing.

Buy me a coffee
Written by
Expona Admin
Site administrator

Comments

Loading comments…

Leave a comment

Comments are reviewed before they appear. Be kind and stay on topic.

Related articles