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()
}
}
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.