Haptic Feedback: The Secret to Apps That Feel Premium
Tactile responses that elevate user experience
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
touchDownif expectingtouchUpInside - On
viewDidAppearfor 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