SwiftUI .sensoryFeedback modifier'ının tüm tipleri, Core Haptics ile özel titreşim desenleri ve gerçekten hissedilen bir arayüz için haptic UX kuralları, mobil UX mühendisinin gözünden.
SwiftUI'da haptic feedback, iOS 17 ile gelen .sensoryFeedback(_:trigger:) modifier'ı kullanılarak eklenir; bir state değişkenini izler ve değer değiştiğinde Taptic Engine üzerinden titreşim çalar. Bu modifier, UIKit çağındaki UIImpactFeedbackGenerator ve arkadaşlarının yerini alır: deklaratif, otomatik hazırlanan (prepare) ve accessibility ayarlarına saygı gösteren bir API sunar. Bu rehberde .sensoryFeedback'in tüm tiplerini, ne zaman Core Haptics'e düşmen gerektiğini ve gerçekten "hissedilen" bir arayüz için hangi UX kurallarına uyman gerektiğini adım adım anlatıyorum. Açıkçası bu konuyu son iki üretim uygulamamda çok fazla test ettim, ve düşündüğümden çok daha fazla ince nokta çıktı.
.sensoryFeedback iOS 17+ için varsayılan API'dir; UIKit'teki üç ayrı generator sınıfını tek bir deklaratif modifier'a birleştirir.
12 hazır tip vardır (.success, .impact, .selection, .increase, .pathComplete vb.). Her biri belirli bir UX anlamı taşır, süsleme için kullanılmaz.
Özel titreşim desenleri (ör. kilit açılışı, oyun geri bildirimi) için CHHapticEngine ve AHAP dosyaları kullanılır.
Haptic feedback yalnızca iPhone 8 ve sonrasında (Taptic Engine) çalışır; iPad'de .sensoryFeedback sessiz düşer, Apple Watch'ta ise WKInterfaceDevice.play(_:) gerekir.
Ayarlar > Sesler ve Titreşim > Sistem Haptikleri kapalıysa tüm haptic'ler otomatik olarak devre dışı kalır. Bunu bir bug sanıp override etmeye çalışma.
UX kuralı: her etkileşime titreşim koyma. Kullanıcının hedefe ulaştığını, bir sınırı aştığını veya kritik bir durumdan haberdar olması gerektiğini bildirmek için kullan.
Sensory Feedback nedir ve neden .sensoryFeedback kullanmalıyız?
Sensory feedback (Türkçe'de "duyusal geri bildirim"), kullanıcının bir eylemi tamamladığını veya sistemde bir şeyin gerçekleştiğini dokunma duyusu yoluyla iletme yöntemidir. iPhone 7'den itibaren tüm cihazlarda bulunan Taptic Engine, hassas ve kısa süreli titreşimler üretebilen doğrusal bir aktüatördür. Bu, Android telefonlarındaki geleneksel eksantrik motorlardan çok daha kontrollüdür, ve tam da bu yüzden Apple'ın haptic dili son derece ince ayarlıdır.
iOS 17'ye kadar SwiftUI'da haptic çalmak için UIKit'e düşmek gerekiyordu: UIImpactFeedbackGenerator, UISelectionFeedbackGenerator ve UINotificationFeedbackGenerator. Üç ayrı sınıf, prepare() çağırma zorunluluğu ve state ile senkronize etme sorumluluğu geliştiriciye aitti. .sensoryFeedback(_:trigger:) modifier'ı bu üç API'yi tek bir deklaratif noktada birleştirir, hazırlığı otomatik yönetir ve state değişimini trigger olarak alır. Yani SwiftUI'nın "veri kaynağı tek yerde, view onu takip eder" felsefesine tam uyar. Bu, @Observable makrosu ile durum yönetimi yaptığınız modern SwiftUI kod tabanlarında haptic'i bir side-effect değil, view'ın doğal bir çıktısı haline getirir.
Diğer bir avantaj: .sensoryFeedback, kullanıcının Ayarlar > Sesler ve Titreşim > Sistem Haptikleri tercihine otomatik saygı gösterir. UIKit generator'larıyla bu davranışı elle kontrol etmen gerekiyordu; şimdi framework bunu senin için hallediyor. Aynı şekilde Low Power Mode aktifken sistem haptikleri kısıtlar, ve modifier bunu şeffaf biçimde yönetir.
.sensoryFeedback modifier nasıl kullanılır?
Temel imzası basit. Bir SensoryFeedback tipi ve tetikleyici olarak izlenecek bir Equatable değer alır. Değer değiştiğinde titreşim çalar. En yaygın kullanım, bir @State Bool veya sayacı izlemek:
Modifier'ın üç aşırı yüklemesi (overload) vardır ve doğru olanı seçmek performansı ve kontrolü doğrudan etkiler:
Basit trigger:.sensoryFeedback(.success, trigger: didComplete). Trigger her değiştiğinde çalar.
Koşullu trigger:.sensoryFeedback(.selection, trigger: index) { old, new in new != old && new % 5 == 0 }. Closure true döndüğünde çalar. Her beşinci seçimde titretmek gibi filtreleme için idealdir.
Dinamik tip:.sensoryFeedback(trigger: score) { old, new in new > old ? .increase : .decrease }. Closure SensoryFeedback? döndürür; nil döndürürsen titreşim atlanır.
Modifier view hiyerarşisinin herhangi bir yerine yerleştirilebilir; genellikle Button, Toggle veya Slider gibi etkileşimli view'ların üzerine koymak en okunabilir sonucu verir. Ancak birden fazla haptic'i aynı anda tetiklemek istemezsen (kullanıcı avucunda "titreşim çamuru" hisseder), sadece bir tanesini üst seviyede tut ve orada koşullu closure ile karar ver.
Tüm SensoryFeedback tipleri ve ne zaman kullanılır?
Apple'ın sunduğu 12 hazır tip, rastgele "titreşim çeşitleri" değildir. Her biri belirli bir Human Interface Guidelines UX anlamı taşır. Yanlış tipi seçmek, kullanıcının hissettiği titreşim ile ekranda gördüğü olayın kopmasına neden olur ve arayüzü "ucuz" hissettirir.
Tip
Kuvvet
UX Anlamı
Örnek Kullanım
.success
Orta, iki vuruş
İşlem başarıyla tamamlandı
Ödeme onayı, form gönderimi
.warning
Orta, iki vuruş
Dikkat gerektiren durum
Konum servisleri kapalı, düşük şarj
.error
Güçlü, üç vuruş
İşlem başarısız
Yanlış şifre, ağ hatası
.selection
Çok hafif
Seçim değişti
Picker, segmented control, tarih seçici
.impact(weight:)
Light/medium/heavy
Genel dokunsal geri bildirim
Buton basımı, kart çevirme
.increase
Hafif, yükselen
Değer arttı
Ses seviyesi, zoom yakınlaştırma
.decrease
Hafif, alçalan
Değer azaldı
Ses seviyesi, zoom uzaklaştırma
.start
Belirgin
Sürekli bir eylem başladı
Kayıt başlatma, sürükleme
.stop
Belirgin
Sürekli bir eylem bitti
Kayıt durdurma, sürükleme bırakma
.alignment
Çok hafif
Bir hizalama noktasına oturdu
Snap-to-grid, drag & drop hizalama
.levelChange
Orta
Anlamlı bir eşik geçildi
Kilit açma, seviye atlama
.pathComplete
Belirgin, iOS 17.5+
Bir gesture yolu tamamlandı
Swipe-to-refresh sınırı, kart çekme
.impact tipi biraz farklıdır çünkü parametreler alır: weight (.light, .medium, .heavy, .soft, .rigid) ve intensity (0.0-1.0). Şu iki formu benim en çok kullandıklarım:
// Hafif dokunuş için: buton basımı, kart tap
.sensoryFeedback(.impact(weight: .light, intensity: 0.6),
trigger: buttonTapCount)
// Fiziksel bir olayı simüle etmek için: cam kırılma, ağır obje düşmesi
.sensoryFeedback(.impact(weight: .heavy, intensity: 1.0),
trigger: didDropItem)
.sensoryFeedback ve UIFeedbackGenerator arasındaki fark nedir?
Kısa cevap: iOS 17+ hedeflediğiniz herhangi bir SwiftUI projesinde .sensoryFeedback her zaman doğru seçimdir. UIKit generator'larını yalnızca şu iki durumda kullan: (a) iOS 16 ve öncesini desteklemeye devam eden legacy kod tabanları, veya (b) SwiftUI dışında (ör. bir UIViewController veya CADisplayLink loop içinde) haptic çalman gereken yerler. Aradaki farkları somut olarak görelim:
// UIKit yolu: 3 satır, prepare zorunlu, timing manuel
let generator = UIImpactFeedbackGenerator(style: .medium)
generator.prepare() // Taptic Engine'i uyandır (~200ms latency azaltır)
generator.impactOccurred() // Çal
// SwiftUI yolu: tek modifier, prepare framework tarafından yapılır
.sensoryFeedback(.impact(weight: .medium), trigger: someState)
Kritik farklar:
Prepare çağrısı:.sensoryFeedback, trigger değeri değişmeden önce view render'ında Taptic Engine'i uyandırır. UIKit'te bunu unutursan ilk titreşim ~200ms gecikebilir; bu, "hissettim ama olayı kaçırdım" hissi yaratır.
State bağlılığı: Modifier bir SwiftUI değerine bağlıdır; başka bir yerden aynı olayı tetiklerseniz otomatik senkron kalır. UIKit'te tetikleme ile UI güncellemesi arasındaki koordinasyon size aittir.
Bellek yönetimi: Generator instance'ını canlı tutman gerekir (deinit olursa Taptic Engine kapanır). Modifier bu ömrü framework yönetir.
Erişilebilirlik: Modifier, iOS ayarlarını ve Reduce Motion'a ek gelen haptic seçeneklerini otomatik dinler.
Legacy kod tabanınızı geçiriyorsanız Swift 6 strict concurrency geçişi sırasında haptic kodunu SwiftUI modifier'ına taşımak iyi bir fırsattır. Bir önceki refactor'umda tam olarak bunu yaptım: UIImpactFeedbackGenerator zaten @MainActor istiyor, ve trigger closure'ları SwiftUI'nın actor izolasyonuna dahildir. İkisini birlikte taşıyınca compile-time uyarıların büyük bölümü tek seferde temizleniyor. Ayrıca SwiftData ile veri kalıcılığı kullanan uygulamalarda kaydet-başarılı-titret akışını modifier üzerinden yürütmek işleri gerçekten sadeleştiriyor.
Core Haptics ile özel haptic desenleri nasıl oluşturulur?
Hazır 12 tip ihtiyacını karşılamıyorsa (örneğin bir oyunda ateş etme, bir müzik uygulamasında ritim, veya bir kilit uygulamasında mekanik takırtı simülasyonu), Core Haptics devreye girer. Core Haptics, CHHapticEngine üzerinden titreşim ve senkronize ses eventlerini milisaniye hassasiyetinde tanımlamana izin verir. İki temel event tipi vardır:
hapticTransient: Kısa, tek atımlık vuruş (0-100 ms). hapticIntensity (0-1) ve hapticSharpness (0-1) parametreleri alır.
hapticContinuous: Belirli bir duration boyunca süren titreşim. Envelope (attack, decay, release) parametreleriyle şekillendirilir.
İşte programatik olarak bir "üç kısa vuruş" pattern'i oluşturan minimal örnek:
import CoreHaptics
@MainActor
final class HapticsController {
private var engine: CHHapticEngine?
func prepare() throws {
guard CHHapticEngine.capabilitiesForHardware().supportsHaptics else {
return // iPad, eski iPhone: sessiz düş
}
engine = try CHHapticEngine()
try engine?.start()
// Engine reset callback: arka plandan dönünce yeniden başlat
engine?.resetHandler = { [weak self] in
try? self?.engine?.start()
}
}
func playTripleTap() throws {
var events: [CHHapticEvent] = []
for i in 0..<3 {
let event = CHHapticEvent(
eventType: .hapticTransient,
parameters: [
CHHapticEventParameter(parameterID: .hapticIntensity, value: 0.9),
CHHapticEventParameter(parameterID: .hapticSharpness, value: 0.7)
],
relativeTime: TimeInterval(i) * 0.12 // 120 ms aralık
)
events.append(event)
}
let pattern = try CHHapticPattern(events: events, parameters: [])
let player = try engine?.makePlayer(with: pattern)
try player?.start(atTime: 0)
}
}
Karmaşık pattern'ler için doğrudan Swift kodu yerine AHAP (Apple Haptic and Audio Pattern) JSON dosyaları kullanmanı öneririm. AHAP dosyaları versiyonlanabilir, tasarımcının Haptic Composer gibi bir araçta düzenleyebileceği ve build'de kaynak olarak paketlenebilecek deklaratif tanımlardır:
Apple Watch'ta haptic, iPhone'daki kadar deklaratif değildir. SwiftUI modifier'ı olarak .sensoryFeedback watchOS 10+ üzerinde de çalışır, ancak alt tabakada WKInterfaceDevice.play(_:) kullanır ve tip kümesi daha kısıtlıdır. Watch üzerinde en güvenilir yol hâlâ WatchKit API'sini doğrudan çağırmaktır:
Watch'ta desteklenen WKHapticType değerleri: .notification, .directionUp, .directionDown, .success, .failure, .retry, .start, .stop, .click. Bunların yoğunluk parametresi yoktur. Watch'ın Taptic Engine'i daha az granülerdir ama gerçek "bilekten uyarı" için optimize edilmiştir. Watch'ta özellikle dikkat edilmesi gereken bir nokta var: .click Digital Crown döndürme sırasında yerleşik olarak çaldığı için, kendi listenizin snap noktalarında .click çalmak çift titreşim algısı yaratır. Bunun yerine .directionUp/.directionDown tercih edilir.
Erişilebilirlik, kullanıcı ayarları ve cihaz desteği
Haptic tasarımının en çok gözden kaçırılan boyutu erişilebilirliktir. Şu üç noktayı üretime çıkmadan önce mutlaka doğrula:
Cihaz desteği kontrolü: iPad ve iPhone SE (1. nesil) Taptic Engine'e sahip değildir. Core Haptics kullanıyorsan CHHapticEngine.capabilitiesForHardware().supportsHaptics ile kontrol et. .sensoryFeedback modifier'ı desteklenmeyen cihazlarda sessizce hiçbir şey yapmaz. Bu iyi bir davranıştır, kullanıcıya hata mesajı gösterme.
Sistem ayarları:Ayarlar > Sesler ve Titreşim > Sistem Haptikleri kapalıysa hem .sensoryFeedback hem de UIKit generator'ları sessizdir. Kullanıcının bilinçli tercihidir, override etmeye çalışma.
Reduce Motion / Reduce Haptics: iOS 18'de Accessibility > Motion altına Reduce Haptics seçeneği eklendi. UIAccessibility.isReduceMotionEnabled ile bunu kontrol edip kritik olmayan titreşimleri devre dışı bırakabilirsin.
import SwiftUI
import UIKit
struct HapticButton: View {
@State private var tapped = false
@Environment(\.accessibilityReduceMotion) private var reduceMotion
var body: some View {
Button("Onayla") { tapped.toggle() }
.sensoryFeedback(trigger: tapped) { _, _ in
// Reduce Motion açıksa kritik olmayan haptic'i atla
reduceMotion ? nil : .impact(weight: .medium)
}
}
}
Ayrıca dikkat: Low Power Mode aktifken sistem haptik yoğunluğunu azaltır. Bu davranışı override edemezsin, ve etmemelisin çünkü pil ömrü kullanıcının önceliğidir. Navigasyon akışlarınızda haptic'i kritik olmayan bir dokunma katmanı olarak tasarla; olmadan da işlev tam çalışsın.
Haptic tasarımında en iyi uygulamalar (UX)
Teknik API'yi bildiğine göre şimdi asıl kritik kısım: ne zaman titretmemek gerektiği. Ben mobil UX mühendisi olarak titreşimin, motion tasarımı gibi, bilgi taşımadığı zaman gürültü olduğuna inanıyorum. Aşağıdaki kurallar üretim uygulamalarında tekrar tekrar doğruladığım kalıplardır. Ayrıntılı tasarım kılavuzu için Apple'ın Playing Haptics sayfasına da bakmanı tavsiye ederim.
Bir h2 kadar önemli: susma disiplini
Her scroll'da titretme. ScrollView'da her bir hedefe geldiğinde titreşim çalmak kısa sürede yorucu olur. Sadece snap noktalarında veya üstte/altta çarptığında tetikle.
Her keystroke'da titretme. Sistem klavyesi zaten hafif bir click çıkarır; üstüne kendi haptic'ini eklemek "titreme çamuru" yaratır.
Animation ile eş zamanlı çal. Görsel değişim (spring response 0.4, damping 0.7) tamamlanırken haptic tetiklenirse, "bir bütün olay" hissi güçlenir. Görsel bitmeden titretmek algısal kopukluk yaratır.
UX anlamıyla eşleş..success'i bir "beğen" butonu için kullanma; beğenme başarı değil bir seçimdir. .impact(.light) veya .selection daha doğrudur.
İyi haptic örnekleri
Pull-to-refresh eşiği geçildi:.impact(weight: .medium). Kullanıcı bırakırsa yenilenecek eşiğe ulaştı.
Toggle açıldı/kapatıldı:.selection. Hafif ve mekanik hissi güçlendirir.
Sepete ürün eklendi:.impact(weight: .light, intensity: 0.5). Göz köşesinde algılanacak kadar hafif.
Şifre yanlış girildi:.error. Kullanıcı ekrana bakmadan da anlar.
Alarm kurulumu tamamlandı:.success. Modal kapanırken gerçek bir onay.
Son bir tavsiye: @Observable ile state yönetimi yaptığınız view model'lerde haptic tetikleyiciyi ayrı bir @Published property olarak tutmayın. Bunun yerine mevcut state değişikliğini trigger olarak kullanın (ör. isFavorited). Böylece haptic bir side-effect değil, veri akışının doğal bir sonucu olur, ve test edilebilirlik korunur.
Sıkça Sorulan Sorular
iOS'ta haptic feedback nasıl eklenir?
iOS 17 ve sonrası için SwiftUI'da .sensoryFeedback(_:trigger:) modifier'ını kullanın. Örnek: .sensoryFeedback(.success, trigger: didComplete). UIKit'te veya iOS 16'da UIImpactFeedbackGenerator, UISelectionFeedbackGenerator ya da UINotificationFeedbackGenerator sınıflarını kullanın ve mutlaka önce prepare() çağırın.
Haptic feedback tüm iPhone'larda çalışır mı?
Hayır. Taptic Engine yalnızca iPhone 7 ve sonrasında bulunur; iPhone SE (1. nesil) ve iPad'ler .sensoryFeedback çağrılarına sessizce yanıt verir (titreşim çalmaz, hata da vermez). Core Haptics kullanıyorsanız CHHapticEngine.capabilitiesForHardware().supportsHaptics ile donanım desteğini kontrol etmelisiniz.
.sensoryFeedback ve UIImpactFeedbackGenerator arasındaki fark nedir?
.sensoryFeedback SwiftUI için deklaratif bir modifier'dır: state değişimini trigger olarak alır, Taptic Engine'i otomatik hazırlar ve sistem ayarlarına saygı gösterir. UIImpactFeedbackGenerator ise imperative UIKit sınıfıdır; prepare() ve impactOccurred() manuel çağrıları gerektirir ve instance ömrünü siz yönetirsiniz. iOS 17+ SwiftUI kodunda daima modifier'ı tercih edin.
SwiftUI'da özel bir haptic pattern nasıl oluşturulur?
CHHapticEngine, CHHapticEvent ve CHHapticPattern sınıflarını içeren Core Haptics framework'ünü kullanın. Basit örüntüler için Swift kodunda event dizisi oluşturun; karmaşık örüntüler için AHAP (Apple Haptic and Audio Pattern) JSON dosyalarını bundle'ınıza ekleyip engine.playPattern(from: url) ile oynatın. Uygulama arka plana geçince engine durur; resetHandler ile yeniden başlatmayı unutmayın.
Kullanıcı haptic'i kapattıysa uygulama nasıl davranmalı?
Hiçbir şey yapmayın: sistem otomatik olarak titreşimi çalmaz. Kullanıcı Ayarlar > Sesler ve Titreşim > Sistem Haptikleri'ni kapattıysa bu bilinçli bir tercihtir ve override edilemez. Uygulamanız haptic olmadan da tam işlevsel olmalı; haptic, kritik geri bildirim değil, ek bir dokunma katmanı olarak tasarlanmalıdır.
watchOS'ta hangi haptic tipleri desteklenir?
Apple Watch'ta WKInterfaceDevice.play(_:) ile şu WKHapticType değerleri kullanılabilir: .notification, .directionUp, .directionDown, .success, .failure, .retry, .start, .stop, .click. iOS'un aksine yoğunluk parametresi yoktur ve Digital Crown'un yerleşik .click haptik'i ile çakışmayı önlemek için özel listeler için .directionUp/.directionDown tercih edilmelidir.
SwiftUI NavigationStack ve NavigationPath ile tip güvenli navigasyon: Router deseni, derin bağlantı işleme, cold-launch durum geri yükleme ve VoiceOver erişilebilirliği için 2026 rehberi.
Swift Testing, XCTest'in yerini alan modern framework. @Test makrosu, #expect kontrolleri, paralel çalıştırma ve @Suite ile Swift 6 ve Xcode 16'da birim testlerin nasıl yazıldığını, XCTest'ten kademeli geçiş örnekleriyle görün.