Haptic feedback with UIFeedbackGenerator

Sofia Martinez Jan 2026
1 tab
import UIKit

class HapticFeedbackManager {
    static let shared = HapticFeedbackManager()

    private let impactLight = UIImpactFeedbackGenerator(style: .light)
    private let impactMedium = UIImpactFeedbackGenerator(style: .medium)
    private let impactHeavy = UIImpactFeedbackGenerator(style: .heavy)
    private let notification = UINotificationFeedbackGenerator()
    private let selection = UISelectionFeedbackGenerator()

    private init() {}

    // MARK: - Impact Feedback
    func lightImpact() {
        impactLight.prepare()
        impactLight.impactOccurred()
    }

    func mediumImpact() {
        impactMedium.prepare()
        impactMedium.impactOccurred()
    }

    func heavyImpact() {
        impactHeavy.prepare()
        impactHeavy.impactOccurred()
    }

    // MARK: - Notification Feedback
    func success() {
        notification.prepare()
        notification.notificationOccurred(.success)
    }

    func warning() {
        notification.prepare()
        notification.notificationOccurred(.warning)
    }

    func error() {
        notification.prepare()
        notification.notificationOccurred(.error)
    }

    // MARK: - Selection Feedback
    func selectionChanged() {
        selection.prepare()
        selection.selectionChanged()
    }
}

// MARK: - Usage in View Controller
class InteractiveViewController: UIViewController {
    private let haptics = HapticFeedbackManager.shared

    @IBAction func buttonTapped(_ sender: UIButton) {
        // Light impact for button tap
        haptics.lightImpact()

        // Perform action
        performAction()
    }

    @IBAction func deleteButtonTapped(_ sender: UIButton) {
        // Heavy impact for destructive action
        haptics.heavyImpact()

        showDeleteConfirmation()
    }

    private func performAction() {
        // Simulate async action
        DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
            // Success feedback on completion
            self.haptics.success()
        }
    }

    private func showDeleteConfirmation() {
        let alert = UIAlertController(title: "Delete", message: "Are you sure?", preferredStyle: .alert)

        alert.addAction(UIAlertAction(title: "Cancel", style: .cancel) { _ in
            // No haptic for cancel
        })

        alert.addAction(UIAlertAction(title: "Delete", style: .destructive) { _ in
            // Error haptic for deletion
            self.haptics.error()
        })

        present(alert, animated: true)
    }
}

// MARK: - Gesture with Haptics
class HapticSlider: UISlider {
    private let haptics = HapticFeedbackManager.shared
    private var lastValue: Float = 0

    override init(frame: CGRect) {
        super.init(frame: frame)
        setupGesture()
    }

    required init?(coder: NSCoder) {
        super.init(coder: coder)
        setupGesture()
    }

    private func setupGesture() {
        addTarget(self, action: #selector(valueChanged), for: .valueChanged)
        lastValue = value
    }

    @objc private func valueChanged() {
        let step: Float = 0.1
        let newValue = (value / step).rounded() * step

        if abs(newValue - lastValue) >= step {
            haptics.selectionChanged()
            lastValue = newValue
        }
    }
}

// MARK: - Pull to Refresh with Haptics
class HapticRefreshControl: UIRefreshControl {
    private let haptics = HapticFeedbackManager.shared

    override func beginRefreshing() {
        super.beginRefreshing()
        haptics.mediumImpact()
    }

    func endRefreshingWithSuccess() {
        haptics.success()
        endRefreshing()
    }

    func endRefreshingWithError() {
        haptics.error()
        endRefreshing()
    }
}
1 file · swift Explain with highlit

Haptic feedback provides tactile responses that enhance user experience. iOS offers three feedback generators: UIImpactFeedbackGenerator for physical impacts with light, medium, or heavy intensities; UINotificationFeedbackGenerator for success, warning, or error outcomes; and UISelectionFeedbackGenerator for selection changes. I call prepare() before triggering feedback to reduce latency—the system spins up the Taptic Engine. Then impactOccurred(), notificationOccurred(), or selectionChanged() triggers the feedback. Haptics complement visual and audio feedback but shouldn't replace them. Overuse diminishes impact and drains battery. I reserve haptics for meaningful interactions like button presses, gestures, and state changes.