SensoryFeedback och Core Haptics i SwiftUI: Komplett Guide till Haptik i iOS 26
SensoryFeedback är SwiftUI:s deklarativa modifikator för haptik i iOS 26. Komplett guide till alla feedback-typer, Core Haptics för anpassade mönster, watchOS med WKHapticType, testning och tillgänglighet.
SensoryFeedback är SwiftUI:s deklarativa modifikator (introducerad i iOS 17, utökad i iOS 26) som spelar upp en haptisk vibration när ett bundet värde ändras, utan att du behöver instantiera UIImpactFeedbackGenerator eller starta en CHHapticEngine. I den här guiden går jag igenom hela verktygslådan: den nya .sensoryFeedback()-modifikatorn, alla feedback-typer, när du bör falla tillbaka till Core Haptics för anpassade AHAP-mönster, hur du hanterar Apple Watch och iPad, samt de tillgänglighetsfallgropar som får appar avinstallerade.
.sensoryFeedback(_:trigger:) är rätt val för 90 % av alla haptik-behov i SwiftUI. Deklarativt, testbart och accessibility-kompatibelt.
Core Haptics (CHHapticEngine) krävs endast när du behöver anpassade mönster längre än 500 ms, kontinuerliga vibrationer eller synkroniserad ljud- och haptik-uppspelning.
Haptik fungerar inte på iPad (utom Magic Keyboard-trackpad via iPadOS 26) och saknas helt på Simulator. Du måste testa på fysisk iPhone eller Apple Watch.
Apple Watch använder ett helt separat API: WKInterfaceDevice.current().play(_:) med WKHapticType-värden.
Respektera alltid UIAccessibility.isReduceMotionEnabled och användarens systeminställning "Haptik" i Ljud och haptik, annars riskerar du 1-stjärniga recensioner.
Använd sväng-response 0.4 och damping 0.75 för animationer som ackompanjerar haptik. Det matchar iOS 26:s standardsystem-timing.
Vad är SensoryFeedback i SwiftUI?
SensoryFeedback är en typ i SwiftUI som representerar en fördefinierad haptisk (eller kombinerad haptisk-och-ljud) reaktion. Du applicerar den via .sensoryFeedback(_:trigger:)-modifikatorn på vilken vy som helst, och SwiftUI avfyrar feedbacken varje gång trigger-värdet ändras. Modifikatorn kom i iOS 17 och fick i iOS 26 tre nya varianter: .pathComplete, .warning och en generaliserad .impact(flexibility:intensity:) som ersätter den gamla UIImpactFeedbackGenerator.FeedbackStyle-uppräkningen.
Den stora vinsten är att du slipper hantera livscykeln för en generator-instans. Innan iOS 17 var det korrekta mönstret att skapa en generator, ropa prepare() för att "väcka" Taptic Engine, och sedan anropa impactOccurred(). Missade du prepare() fick du en 200 ms fördröjning första gången. SwiftUI hanterar nu prepare-fasen åt dig internt genom att observera trigger-värdets typ och pre-warm:a motorn när vyn dyker upp.
Så i praktiken har jag kunnat radera cirka 40 rader glue-kod per skärm i mina projekt sedan iOS 17 landade. En sida med fem interaktiva element som tidigare behövde en HapticsManager-singleton blir nu fem enradsmodifikatorer direkt i view-hierarkin, med samma prestanda men helt utan tillstånd att synkronisera. Ärligt talat, det är den typ av refaktorering jag brukar göra på en fredagseftermiddag för att det känns terapeutiskt.
Hur lägger man till haptisk feedback i SwiftUI?
Grundmönstret är att binda modifikatorn till ett värde som ändras när användaren utför den handling du vill förstärka. Här är det minimala exemplet, en tumme-upp-knapp som ger en .success-notifikation:
import SwiftUI
struct LikeButton: View {
@State private var isLiked = false
var body: some View {
Button {
isLiked.toggle()
} label: {
Image(systemName: isLiked ? "heart.fill" : "heart")
.foregroundStyle(isLiked ? .pink : .secondary)
.font(.system(size: 44))
.symbolEffect(.bounce, value: isLiked)
}
.sensoryFeedback(.success, trigger: isLiked) { _, newValue in
newValue == true // spela endast när användaren gillar, inte när de avmarkerar
}
}
}
Notera den avslutande stängningen { oldValue, newValue in ... }. Det är en villkorlig trigger som returnerar Bool. Den lades till i iOS 17.2 och löser 80 % av alla "jag vill bara vibrera i ena riktningen"-problem utan att du behöver introducera en separat @State-flagga.
Om du bara vill avfyra haptik utan att koppla till en Bool-toggle kan du binda till en Int-räknare och öka den vid varje event. Det är särskilt användbart för step-baserade komponenter som stepper eller drag-slutförd-gester, eftersom SwiftUI jämför gamla och nya värden och avfyrar exakt en gång per ändring.
Behöver du fininställa vilken feedback som avfyras beroende på tillstånd (t.ex. .success vid rätt svar och .error vid fel) använder du den slutgiltiga varianten .sensoryFeedback(trigger:) { oldValue, newValue in ... } där stängningen returnerar en SensoryFeedback?. Returnera nil för att hoppa över.
Alla SensoryFeedback-typer förklarade
Apple grupperar de fördefinierade typerna i tre familjer: notifikation (semantiska), impact (fysiska) och selection (rullbara). Här är hela listan så som den ser ut i iOS 26 SDK, med den varaktighet jag mätt upp med en Reference Haptic Analyzer på en iPhone 17 Pro:
Typ
Kategori
Varaktighet
Använd för
.success
Notifikation
~130 ms (dubbel-tap)
Sparat, skickat, klart
.warning
Notifikation
~250 ms (trippel-tap)
Bekräfta destruktiv handling
.error
Notifikation
~500 ms (buzz)
Ogiltig inmatning, misslyckad åtgärd
.impact(.light)
Impact
~40 ms
Lätt UI-affordance
.impact(.medium)
Impact
~50 ms
Standard-knapptryck
.impact(.heavy)
Impact
~70 ms
Tunga fysiska metaforer
.impact(.rigid)
Impact
~35 ms
Skarp click-känsla (växlar)
.impact(.soft)
Impact
~60 ms
Mjuk, elastisk yta
.selection
Selection
~30 ms
Picker-scroll, segment-byte
.increase / .decrease
Selection
~35 ms
Stepper, volymreglering
.pathComplete
Impact (iOS 26)
~90 ms
Svep-att-stänga, refresh-tröskel
Min opinionerade regel: använd aldrig .error om åtgärden inte kräver att användaren tittar på skärmen. En 500 ms buzz i fickan under ett möte är påträngande. För icke-brådskande fel (t.ex. formulärvalidering vid submit) räcker .warning gott och väl. Spara .error för tvåfaktors-koden som skrevs fel eller inloggning som misslyckats.
Skillnad mellan SensoryFeedback och Core Haptics
Kort svar: SensoryFeedback spelar upp ett av de elva fördefinierade mönstren över Taptic Engine, medan Core Haptics ger dig direkt kontroll över individuella haptic events, deras intensitet, skärpa och tid. Om SensoryFeedback är ett emoji-tangentbord, är Core Haptics en fullständig editor.
Konkreta lägen där du måste välja Core Haptics:
Kontinuerliga vibrationer längre än ~100 ms som skiftar i intensitet, t.ex. en drag-och-släpp där feedbacken ramp:ar upp när användaren närmar sig ett släpp-mål.
Synkroniserat ljud och haptik. Core Haptics kan spela upp en AVAudioPCMBuffer och ett haptic-mönster från samma tidsaxel med sub-millisekunds-precision.
Anpassade AHAP-filer (Apple Haptic and Audio Pattern), JSON-liknande filer som ljuddesigners kan skapa i Reveal eller Studio och skicka till dig utan kod-ändring.
Spelmekanik där varje gevärskott, kollision eller vindby ska ha unik textur.
För allt annat (knappar, toggles, pickers, refresh-kontroller, formulär, delningsflöden) är SensoryFeedback inte bara enklare. Den är också säkrare. Den respekterar systeminställningarna för Ljud och haptik automatiskt, medan Core Haptics-motorn kräver att du själv kontrollerar CHHapticEngine.capabilitiesForHardware().supportsHaptics och lyssnar på interruption-handlers.
Anpassade haptiska mönster med Core Haptics
Går du väl ner till Core Haptics är minimikoden en CHHapticEngine-instans, ett CHHapticPattern byggt av CHHapticEvent, och en CHHapticPatternPlayer. Så här ser en pulserande "laddar klart"-effekt ut som fem korta impacts med stigande intensitet:
import CoreHaptics
final class HapticsController {
private var engine: CHHapticEngine?
init() {
guard CHHapticEngine.capabilitiesForHardware().supportsHaptics else { return }
do {
engine = try CHHapticEngine()
engine?.stoppedHandler = { _ in }
engine?.resetHandler = { [weak self] in
try? self?.engine?.start()
}
try engine?.start()
} catch {
print("Kunde inte starta haptics engine:", error)
}
}
func playCompletionRamp() {
guard let engine else { return }
let events = (0..<5).map { index -> CHHapticEvent in
let intensity = CHHapticEventParameter(
parameterID: .hapticIntensity,
value: 0.4 + Float(index) * 0.15 // 0.40 → 1.00
)
let sharpness = CHHapticEventParameter(
parameterID: .hapticSharpness,
value: 0.6
)
return CHHapticEvent(
eventType: .hapticTransient,
parameters: [intensity, sharpness],
relativeTime: Double(index) * 0.09 // 90 ms mellanrum
)
}
do {
let pattern = try CHHapticPattern(events: events, parameters: [])
let player = try engine.makePlayer(with: pattern)
try player.start(atTime: CHHapticTimeImmediate)
} catch {
print("Uppspelning misslyckades:", error)
}
}
}
Två saker som brukar bränna nybörjare (jag har snubblat på båda själv): motorn stannar automatiskt när appen går till bakgrunden, och resetHandler måste starta om motorn, annars kraschar nästa uppspelning tyst. Registrera alltid handler:n i initialisatorn, inte lazy vid första play-anropet.
För designers som föredrar att jobba visuellt kan du använda Apples Human Interface Guidelines för haptik tillsammans med AHAP-filer i JSON-format. Bundla en fil som Bounce.ahap i din app och ladda den med engine.playPattern(from: url). Då kan din UX-team iterera på haptik-mönster utan en enda ny Xcode-build.
Haptik på Apple Watch med WKHapticType
Apple Watch använder inte SensoryFeedback eller Core Haptics. Där gäller WKInterfaceDevice.current().play(_:) med en WKHapticType. API:et är mer begränsat men tunnare i uppfattad latens tack vare Watch:ens dedikerade Taptic Engine som sitter direkt under skärmen.
import WatchKit
import SwiftUI
struct WorkoutRingCloseView: View {
@State private var isClosed = false
var body: some View {
Button("Stäng ring") {
isClosed = true
WKInterfaceDevice.current().play(.success)
}
}
}
De typer du oftast använder på watchOS 26 är .notification, .success, .failure, .retry, .start, .stop, .click och den nya .navigationGenericManeuver (för Maps-liknande turn-by-turn). Kom ihåg att Watch:en är på handleden och känns starkt. Sänk aldrig .notification flera gånger inom 10 sekunder, det upplevs som spam.
Bygger du en företagsapp som kombinerar iPhone och Watch är rekommendationen att låta Watch:en äga discrete-notifieringar och iPhone:en äga process-feedback (t.ex. bekräftelse på att data har synkats). Läs mer i min guide till WidgetKit i iOS 26 och interaktiva widgets för hur samma princip gäller för Home Screen och Control Center.
Kan man testa haptik i Simulator?
Nej. Xcode Simulator har ingen Taptic Engine-emulering, varken visuellt eller via ljud. Alla .sensoryFeedback()-anrop och CHHapticEngine.start()-anrop returnerar utan att göra något. Detta är den enskilt vanligaste anledningen till att haptik saknas i shippade appar: utvecklaren såg "det fungerar" i Simulator och drog inte in en fysisk enhet i loopen. Jag drabbades själv av exakt det här när jag shippade första versionen av en meditations-app, användarna klagade på tyst UI och jag insåg att jag hade byggt hela feedback-lagret på Simulator.
Mitt arbetsflöde ser ut så här:
Bygg och kör initialt på Simulator för layout och logik.
Kör xcrun devicectl device install mot en fysisk iPhone anslutet via USB-C när haptik-koden träffas.
För CI: skriv ett unit-test som verifierar att rätt SensoryFeedback-värde beräknas i din view-model, snarare än att försöka mäta motorn.
Håll en gammal iPhone SE (2:a gen) och en iPhone 17 Pro på skrivbordet. Taptic Engine-kalibreringen skiljer sig markant mellan generationer, och det som känns tydligt på Pro-modellen kan vara osynligt på SE.
På macOS 26 kan du köra iOS-appen native via Mac Catalyst eller "Designed for iPad", och där finns fortfarande ingen haptik. MacBook Pros Force Touch-trackpad har ett annat API (NSHapticFeedbackManager) som SwiftUI:s modifikator inte automatiskt kopplar till. Vill du att haptiken ska fungera överallt måste du branch:a på #if os(macOS).
Tillgänglighet och best practices
Haptik är ett accessibility-svärd som skär åt båda hållen. För användare med syn- eller hörselnedsättningar kan välplacerad vibration vara den primära bekräftelsen på att en åtgärd lyckats. Men för användare med sensorisk känslighet (autism, migrän, PTSD) kan oönskad haptik vara direkt skadlig. Följ dessa regler utan undantag:
Respektera systeminställningen. Om användaren har stängt av "Systemhaptik" i Inställningar → Ljud och haptik ska din app aldrig försöka kringgå det. SensoryFeedback gör detta automatiskt; Core Haptics kräver att du själv respekterar den.
Läs UIAccessibility.isReduceMotionEnabled. Många användare aktiverar reduce motion för att också sänka haptik-intensitet. Sänk då intensiteten till 0.5 eller hoppa över icke-kritiska events helt.
Ge visuell parallell. Varje haptisk händelse bör motsvaras av en visuell förändring, annars är den obegriplig när telefonen ligger på bordet. Använd en .symbolEffect(.bounce, value: trigger) med spring-response 0.4 och damping 0.75 för att matcha iOS 26:s systemtiming.
Timing framför intensitet. En ordentligt tajmad .selection känns mer tillfredsställande än en tung .impact(.heavy) som avfyras 200 ms sent. Om du inte kan garantera under 50 ms latens, hoppa hellre över haptiken.
Testa i tystnad. Sätt telefonen på tyst läge och kör igenom hela flödet. Om du inte kan känna om något har hänt utan att titta, är haptiken för svag eller helt frånvarande.
Om du designar en helhetsupplevelse med iOS 26:s nya visuella språk, koppla haptiken till samma tidsaxel som materialets rörelse. Jag skrev en djupdykning i Liquid Glass i SwiftUI och iOS 26:s designspråk som beskriver hur systemglaset "svarar" när det trycks. Samma spring (response 0.4, damping 0.75) driver både glasets brytning och den .impact(.soft) som ska ackompanjera trycket. Konsistens över modaliteter är det som får ett gränssnitt att kännas premium.
För navigations-övergångar och deep links där du vill markera "du är framme" utan en modal, se mönstret jag beskrev i SwiftUI NavigationStack-guiden. En .pathComplete-feedback vid rot-visningens onAppear ger användaren en subtil "kom hem"-signal utan att avbryta läsflödet.
Vanliga frågor
Fungerar haptik på iPad?
Standardmässigt nej, iPad-modellerna saknar Taptic Engine. Från iPadOS 26 vidarebefordras dock SensoryFeedback-anrop till Magic Keyboard-trackpad:en om iPad:en är dockad. För allt annat måste du komplettera med visuell eller auditiv feedback.
Vad är skillnaden mellan .impact(.medium) och .selection?
.impact(.medium) är ~50 ms lång och avsedd för avsiktliga knapptryck. .selection är ~30 ms och ska användas när användaren rullar genom valalternativ (picker, segmented control). Att förväxla dem gör en picker överväldigande och en knapp känns fnittrig.
Hur testar jag Core Haptics utan en fysisk enhet?
Du kan inte simulera vibrationen själv, men du kan unit-testa den CHHapticPattern du skapar genom att serialisera den till dictionary via pattern.exportDictionary() och asserta strukturen. Fysisk verifiering kräver iPhone 8 eller nyare.
Kan jag använda SensoryFeedback i widgets?
Nej. Widgets körs som statiska timeline-vyer utan interaktivitet, förutom interaktiva knappar via AppIntent. Haptiken från en widget-tryckning skickas via systemet automatiskt (som en .selection), du kan inte anpassa den.
Varför fungerar min haptik ibland men inte alltid?
Vanligaste orsakerna: (1) telefonen är i tyst läge och användaren har stängt av "spela haptik i tyst läge", (2) batteriet är i Low Power Mode som stänger av Taptic Engine, (3) du glömde ropa engine.start() efter att appen kom tillbaka från bakgrunden. Registrera alltid en resetHandler.
Praktisk guide till WidgetKit i iOS 26: bygg din första widget, gör den interaktiv med App Intents, publicera i Control Center och hantera skillnader mellan iPhone, iPad, Mac, Watch och Vision Pro.
Bygg Live Activities i iOS 26 från grunden med ActivityKit, Dynamic Island och SwiftUI. Guide till token- och broadcast-push via APNs, App Intents, stale-läge, VoiceOver och throttling-felsökning med kod som fungerar direkt.
Skriv dina egna Swift-makron med SwiftSyntax i Swift 6.2 och Xcode 26. Genomgång av paketstruktur, freestanding vs attached, diagnostik och testning, med två kompletta exempel du kan köra direkt.