← Back to list

Haptic Feedback: The Secret to Apps That Feel Premium

Tactile responses that elevate user experience

Chandra Welim · 2026-03-04 02:01 · 0 claps · 3.7 min read paywalled
#ios-development #ux-design #swift #mobile-development #haptic-feedback
Open on Medium ↗
Wiki topics: UX · UI/UX Design 📱 · Mobile Development

Photo by Rich Smith on Unsplash

Photo by Rich Smith on Unsplash

Haptic Feedback: The Secret to Apps That Feel Premium

Tactile responses that elevate user experience

You tap a button in a well-made app and feel it. Not see it. Feel it. A subtle vibration confirms your action. The app feels responsive, alive, premium.

That’s haptic feedback. It’s what separates “okay” apps from “wow” apps.

Apple puts incredible haptic hardware in iPhones. Most developers ignore it. Don’t be most developers.

Why Haptics Matter

Physical confirmation:

Users don’t always see the screen when tapping. Haptics confirm the tap registered.

Emotional response:

Haptics create delight. That satisfying “click” when toggling a switch. The “thunk” when an action completes.

Reduced cognitive load:

Instead of watching for visual feedback, users feel it. They can focus on their task.

Perceived quality:

Apps with good haptics feel more polished, more expensive, more professional.

The Three Haptic Generators

UIKit provides three feedback generators, each for different purposes:

UIImpactFeedbackGenerator:

For physical impacts and collisions.

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

Styles:

  • .light — Subtle tap
  • .medium — Standard tap
  • .heavy — Strong tap
  • .soft — Gentle, cushioned
  • .rigid — Sharp, precise

UISelectionFeedbackGenerator:

For selection changes.

let selection = UISelectionFeedbackGenerator()
selection.selectionChanged()

Use when:

  • Changing a picker value
  • Scrolling through options
  • Moving between items

UINotificationFeedbackGenerator:

For task outcomes.

let notification = UINotificationFeedbackGenerator()
notification.notificationOccurred(.success)

Types:

  • .success — Task completed successfully
  • .warning — Attention needed
  • .error — Task failed

When to Use Each

Impact:

// Button tap
@objc func buttonTapped() {
    UIImpactFeedbackGenerator(style: .light).impactOccurred()
    // Perform action
}

// Heavy action (delete, send)
@objc func deleteConfirmed() {
    UIImpactFeedbackGenerator(style: .heavy).impactOccurred()
    deleteItem()
}

// Toggle switch
@objc func switchToggled(_ sender: UISwitch) {
    UIImpactFeedbackGenerator(style: .rigid).impactOccurred()
}

Selection:

// Picker scrolling
func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
    UISelectionFeedbackGenerator().selectionChanged()
}

// Segment control
@objc func segmentChanged(_ sender: UISegmentedControl) {
    UISelectionFeedbackGenerator().selectionChanged()
}

Notification:

// Form submission
func submitForm() async {
    do {
        try await api.submit(form)
        UINotificationFeedbackGenerator().notificationOccurred(.success)
        showSuccessMessage()
    } catch {
        UINotificationFeedbackGenerator().notificationOccurred(.error)
        showErrorMessage(error)
    }
}

// Validation warning
func validateInput(_ text: String) {
    if text.count < 8 {
        UINotificationFeedbackGenerator().notificationOccurred(.warning)
        showWarning("Password too short")
    }
}

Preparing Generators

Generators need time to spin up the haptic engine. For responsive feedback, prepare them:

class ButtonViewController: UIViewController {

    private let impactGenerator = UIImpactFeedbackGenerator(style: .medium)

    override func viewDidLoad() {
        super.viewDidLoad()
        impactGenerator.prepare()
    }

    @objc func buttonTouchDown() {
        // Prepare on touch down for instant feedback on release
        impactGenerator.prepare()
    }

    @objc func buttonTapped() {
        impactGenerator.impactOccurred()
    }
}

When to prepare:

  • On touchDown if expecting touchUpInside
  • On viewDidAppear for frequently used buttons
  • Before starting animations that end with haptics

Building a Haptic Manager

Centralize haptic logic:

enum HapticManager {

    // MARK: - Impact

    static func impact(_ style: UIImpactFeedbackGenerator.FeedbackStyle) {
        let generator = UIImpactFeedbackGenerator(style: style)
        generator.impactOccurred()
    }

    static func lightImpact() {
        impact(.light)
    }

    static func mediumImpact() {
        impact(.medium)
    }

    static func heavyImpact() {
        impact(.heavy)
    }

    // MARK: - Selection

    static func selection() {
        UISelectionFeedbackGenerator().selectionChanged()
    }

    // MARK: - Notification

    static func success() {
        UINotificationFeedbackGenerator().notificationOccurred(.success)
    }

    static func warning() {
        UINotificationFeedbackGenerator().notificationOccurred(.warning)
    }

    static func error() {
        UINotificationFeedbackGenerator().notificationOccurred(.error)
    }
}

// Usage
HapticManager.lightImpact()
HapticManager.success()

With preparation support:

class HapticManager {

    static let shared = HapticManager()

    private let lightImpact = UIImpactFeedbackGenerator(style: .light)
    private let mediumImpact = UIImpactFeedbackGenerator(style: .medium)
    private let heavyImpact = UIImpactFeedbackGenerator(style: .heavy)
    private let selection = UISelectionFeedbackGenerator()
    private let notification = UINotificationFeedbackGenerator()

    func prepare(_ type: HapticType) {
        switch type {
        case .lightImpact: lightImpact.prepare()
        case .mediumImpact: mediumImpact.prepare()
        case .heavyImpact: heavyImpact.prepare()
        case .selection: selection.prepare()
        case .notification: notification.prepare()
        }
    }

    func fire(_ type: HapticType) {
        switch type {
        case .lightImpact: lightImpact.impactOccurred()
        case .mediumImpact: mediumImpact.impactOccurred()
        case .heavyImpact: heavyImpact.impactOccurred()
        case .selection: selection.selectionChanged()
        case .notification(let notificationType):
            notification.notificationOccurred(notificationType)
        }
    }

    enum HapticType {
        case lightImpact
        case mediumImpact
        case heavyImpact
        case selection
        case notification(UINotificationFeedbackGenerator.FeedbackType)
    }
}

SwiftUI Integration

struct HapticButton: View {

    let title: String
    let action: () -> Void

    var body: some View {
        Button(title) {
            UIImpactFeedbackGenerator(style: .light).impactOccurred()
            action()
        }
    }
}

// Or with sensory feedback (iOS 17+)
struct ModernHapticButton: View {

    let title: String
    let action: () -> Void

    var body: some View {
        Button(title, action: action)
            .sensoryFeedback(.impact(flexibility: .soft), trigger: true)
    }
}

iOS 17 sensory feedback:

// Success
.sensoryFeedback(.success, trigger: isComplete)

// Selection
.sensoryFeedback(.selection, trigger: selectedIndex)

// Impact
.sensoryFeedback(.impact, trigger: tapCount)

Best Practices

1. Don’t overdo it:

Not every interaction needs haptics. Too many vibrations become annoying noise.

Good candidates:

  • Important actions (send, delete, confirm)
  • State changes (toggle, select)
  • Errors and warnings
  • Success confirmations

Skip haptics for:

  • Every button tap
  • Scrolling (unless snapping)
  • Minor UI changes
  • Background events

2. Match intensity to importance:

// Minor action — light
UIImpactFeedbackGenerator(style: .light).impactOccurred()

// Normal action - medium
UIImpactFeedbackGenerator(style: .medium).impactOccurred()

// Important/destructive action - heavy
UIImpactFeedbackGenerator(style: .heavy).impactOccurred()

3. Test on real devices:

Simulator doesn’t have haptics. Always test on real hardware.

4. Respect system settings:

iOS lets users disable haptics (Settings → Sounds & Haptics → System Haptics). UIKit respects this automatically.

5. Combine with audio:

For maximum impact, pair haptics with subtle sounds. iOS system sounds include haptic components.

The Bottom Line

Haptics add a physical dimension to digital interfaces. They confirm actions, provide feedback, and create delight.

Use them intentionally. Match intensity to importance. Test on real devices.

Your app will feel alive.

Quick Reference

// Impact
UIImpactFeedbackGenerator(style: .light).impactOccurred()
UIImpactFeedbackGenerator(style: .medium).impactOccurred()
UIImpactFeedbackGenerator(style: .heavy).impactOccurred()

// Selection
UISelectionFeedbackGenerator().selectionChanged()

// Notification
UINotificationFeedbackGenerator().notificationOccurred(.success)
UINotificationFeedbackGenerator().notificationOccurred(.warning)
UINotificationFeedbackGenerator().notificationOccurred(.error)

// Prepare for responsive feedback
generator.prepare()

// ... then later ...
generator.impactOccurred()

Further reading:

*iOS 26 Programming for Beginners is a clear, project-based path from zero to building apps with Swift 6 and Xcode 26. [Mastering Swift 6](https://amzn.to/4rtvbEy)* is the reference I point people to on concurrency, performance, and modern Swift patterns.

References


메타데이터
post_id
7463fdc1ccca
slug
haptic-feedback-the-secret-to-apps-that-feel-premium-7463fdc1ccca
url
https://medium.com/@chandra.welim/haptic-feedback-the-secret-to-apps-that-feel-premium-7463fdc1ccca
canonical_url
https://medium.com/@chandra.welim/haptic-feedback-the-secret-to-apps-that-feel-premium-7463fdc1ccca
author_url
https://medium.com/@chandra.welim
status
ok
fetched_at
2026-08-09 23:29:40