Haptic Feedback in SwiftUI: Der komplette iOS 26 Guide mit CoreHaptics

Ein Mobile-UX-Guide zu haptischem Feedback in SwiftUI: vom .sensoryFeedback-Modifier über CoreHaptics-Pattern bis zu AHAP-Dateien, Accessibility-Regeln und Timing-Werten, die sich tatsächlich richtig anfühlen.

SwiftUI Haptic Feedback iOS 26 Guide

Aktualisiert: 2. September 2026

Haptisches Feedback in SwiftUI wird ab iOS 17 primär über den .sensoryFeedback-Modifier ausgelöst und ab iOS 26 durch das CoreHaptics-Framework für benutzerdefinierte Muster ergänzt. Zusammen bilden sie zwei klar getrennte Ebenen einer API-Landschaft. Ich baue seit Jahren Interfaces, die sich richtig anfühlen sollen, und in diesem Guide zeige ich dir, welche der beiden Ebenen wann greift, welche Timing-Werte sich in echten Apps bewährt haben (Spoiler: response: 0.4) und wo Xcode 26 stille Fallen für Team-Projekte aufstellt.

  • .sensoryFeedback ist der Standard für 90 % aller Fälle: deklarativ, plattformübergreifend, kostenlos in der Batterie.
  • CoreHaptics brauchst du nur für benutzerdefinierte Rhythmen, Continuous Events oder synchronisiertes Audio-Haptik-Pairing.
  • AHAP-Dateien lassen sich in Xcode 26 direkt im Editor vorschauen. Kein Simulator-Roundtrip mehr nötig.
  • iPad, Mac und tvOS spielen KEIN haptisches Feedback ab. Der Modifier ist dort ein No-Op, kein Fehler.
  • Respektiere System-Haptik-Einstellungen und Reduce Motion: Overriding gilt als Bug, nicht als Feature.
  • Faustregel für Timing: Feedback muss innerhalb von 100 ms nach dem Trigger-Event feuern, sonst wirkt es entkoppelt.

Der .sensoryFeedback-Modifier im Detail

Der .sensoryFeedback-Modifier ist Apples deklarative Antwort auf das UIKit-Chaos aus UIImpactFeedbackGenerator, UISelectionFeedbackGenerator und UINotificationFeedbackGenerator. Statt drei Generator-Typen manuell zu instanziieren, zu „prepare“-en und dann zu triggern, hängst du einen einzigen Modifier an eine View, gibst ihm einen Feedback-Typ und ein beobachtetes Trigger-Value. SwiftUI erledigt den Rest, inklusive Preparation, Latenz-Optimierung und automatischem Skippen auf Plattformen ohne Haptik-Motor.

Ein typisches Beispiel: Ein Toggle, das beim Umschalten ein .success-Feedback auslöst. Der Trigger ist der Zustand selbst. Sobald sich der Wert ändert, feuert der Modifier genau einmal. Kein onChange-Boilerplate, kein manuelles prepare(), keine Sorge um Retention-Zyklen des Generators.

import SwiftUI

struct FavoriteToggle: View {
    @State private var isFavorite = false

    var body: some View {
        Button {
            isFavorite.toggle()
        } label: {
            Image(systemName: isFavorite ? "heart.fill" : "heart")
                .foregroundStyle(isFavorite ? .pink : .secondary)
                .contentTransition(.symbolEffect(.replace))
        }
        .sensoryFeedback(.success, trigger: isFavorite) { oldValue, newValue in
            // Nur feuern, wenn wir AKTIVIEREN, nicht beim Deaktivieren.
            newValue == true
        }
    }
}

Die dritte Form mit dem Closure ist der Trick, den Apples Dokumentation stiefmütterlich behandelt: Du kannst die Feuerbedingung an oldValue und newValue koppeln. Damit vermeidest du das häufigste Anfängerproblem, nämlich Haptik bei jeder Zustandsänderung, obwohl nur ein Übergang designerisch gewünscht ist.

Alle SensoryFeedback-Varianten im Vergleich

iOS 17 hat die API eingeführt, iOS 18 ergänzte .pathComplete, und iOS 26 hat zwei subtile Änderungen bei Continuous-Feedback gebracht. Ich benutze die Varianten regelmäßig unterschiedlich stark. Hier ist die Tabelle, die ich mir bei jedem neuen Projekt an die Wand hänge:

VariantePhysisches GefühlSemantikWann verwenden
.successWeicher, aufsteigender DoppelklickAktion erfolgreichSpeichern, Bestätigen, Favorisieren
.warningKurzer, mittlerer Impact + PauseVorsicht, aber nicht destruktivFormvalidierung, Limit-Warnung
.errorDrei-Ton-VibrationAktion fehlgeschlagenLogin fehlgeschlagen, Netzwerkfehler
.selectionSehr subtiles TickenDiskrete Auswahl-ÄnderungPicker, SegmentedControl, Slider-Snaps
.impact(.light)Leichter TapKleine UI-InteraktionKollisionen, Toggle-Feedback
.impact(.medium)Deutlicher TapStandard-ImpactButton-Press mit Gewicht
.impact(.heavy)Kräftiger TapWichtiges EreignisModal geschlossen, Sheet geöffnet
.impact(.soft)Weicher, längerer ImpactWeich abgefederter KontaktSnap-to-Grid, sanftes Andocken
.impact(.rigid)Scharfer, kurzer ImpactFester KontaktBarriere erreicht, Ende der Liste
.increase / .decreaseAufsteigend / absteigendWertänderung mit RichtungStepper, Zoom, Volume-Slider
.pathCompleteAbgeschlossener LoopRoute/Loop beendetGesture-Loop, Pull-to-Refresh-Ende
.alignmentKlare Snap-EmpänzungAusrichtung erreichtKamera-Level, Snap-Guides

CoreHaptics: Custom Patterns für differenziertes Feedback

Wenn du eine Herzschlag-Animation für ein Fitness-Feature baust oder einen Klaviersaiten-Zupf-Effekt für ein Musikinstrument brauchst, reicht der SensoryFeedback-Modifier nicht mehr. Hier kommt CoreHaptics ins Spiel. Das Framework arbeitet auf zwei Primitiven: CHHapticEvent (transient oder continuous) und CHHapticDynamicParameter (modifiziert laufende Events in Echtzeit).

Das Minimalbeispiel: eine Engine erzeugen, ein Pattern aus zwei transienten Events bauen, abspielen. Die Engine ist ein teures Objekt und sollte im ViewModel oder als @Observable-Service leben, nicht in einer transient View. Für den grundlegenden Aufbau von Observable-Services siehe unseren Guide zum Observable-Makro in UIKit.

import CoreHaptics
import SwiftUI

@Observable
final class HapticEngine {
    private var engine: CHHapticEngine?

    init() {
        guard CHHapticEngine.capabilitiesForHardware().supportsHaptics else { return }
        do {
            engine = try CHHapticEngine()
            engine?.resetHandler = { [weak self] in
                try? self?.engine?.start()
            }
            engine?.stoppedHandler = { reason in
                print("Haptic engine stopped: \(reason)")
            }
            try engine?.start()
        } catch {
            print("Haptic engine failed: \(error)")
        }
    }

    func playHeartbeat() {
        guard let engine else { return }
        let sharp = CHHapticEvent(
            eventType: .hapticTransient,
            parameters: [
                .init(parameterID: .hapticIntensity, value: 0.9),
                .init(parameterID: .hapticSharpness, value: 0.7)
            ],
            relativeTime: 0
        )
        let dull = CHHapticEvent(
            eventType: .hapticTransient,
            parameters: [
                .init(parameterID: .hapticIntensity, value: 0.7),
                .init(parameterID: .hapticSharpness, value: 0.3)
            ],
            relativeTime: 0.15  // 150 ms später, klassischer Herzschlag-Rhythmus
        )
        do {
            let pattern = try CHHapticPattern(events: [sharp, dull], parameters: [])
            let player = try engine.makePlayer(with: pattern)
            try player.start(atTime: CHHapticTimeImmediate)
        } catch {
            print("Failed to play pattern: \(error)")
        }
    }
}

Die zwei Parameter, die 95 % der Wahrnehmung ausmachen: Intensity (wie stark der Tap ist) und Sharpness (wie kantig er sich anfühlt). Sharpness 1.0 fühlt sich wie ein Stiftklick an, Sharpness 0.0 eher wie ein weicher Herzstoß. Ich habe für jede Design-Kritik einen kleinen Prototyp mit einem Slider für beide Parameter dabei. Designer sollen selbst fühlen, was 0.3 vs. 0.7 bedeutet, statt in einer Figma-Beschreibung zu raten.

AHAP-Dateien: Haptik-Design als Asset

AHAP steht für Apple Haptic and Audio Pattern, ein JSON-Format, in dem du komplexe Haptik-Sequenzen bis zu 30 Sekunden Länge deklarativ beschreiben kannst. Der große Vorteil: Designer können die Datei selbst bearbeiten, ohne Swift zu berühren. In Xcode 26 gibt es endlich einen visuellen AHAP-Editor mit Waveform-Ansicht und Preview-Button. Der Umweg über Third-Party-Tools ist damit passé.

Ein einfaches Beispiel für einen „Coin Collected“-Sound in einer Casual-Game-App:

{
  "Version": 1.0,
  "Metadata": { "Project": "CoinPickup", "Created": "2026-09-02" },
  "Pattern": [
    {
      "Event": {
        "Time": 0.0,
        "EventType": "HapticTransient",
        "EventParameters": [
          { "ParameterID": "HapticIntensity", "ParameterValue": 0.6 },
          { "ParameterID": "HapticSharpness", "ParameterValue": 0.9 }
        ]
      }
    },
    {
      "Event": {
        "Time": 0.08,
        "EventType": "HapticContinuous",
        "EventDuration": 0.25,
        "EventParameters": [
          { "ParameterID": "HapticIntensity", "ParameterValue": 0.4 },
          { "ParameterID": "HapticSharpness", "ParameterValue": 0.5 }
        ]
      }
    }
  ]
}

Speichere die Datei als CoinPickup.ahap in deinem Bundle, und lade sie:

func playAHAP(named name: String) {
    guard let engine,
          let url = Bundle.main.url(forResource: name, withExtension: "ahap") else { return }
    do {
        try engine.playPattern(from: url)
    } catch {
        print("AHAP playback failed: \(error)")
    }
}

Der Vorteil dieser Trennung: Du kannst Haptik-Assets versionieren, in Pull Requests reviewen, und in verschiedenen Locales unterschiedlich ausspielen. Für Games gilt: Ein Ordner voller AHAP-Dateien, ein zentraler Player-Service, klare Naming Convention (ui.button.primary.ahap, game.explosion.large.ahap).

Accessibility-Regeln, die du nicht brechen darfst

Das System stellt drei Einstellungen bereit, die deine Haptik-Entscheidungen dominieren müssen. Ich sehe in fast jedem Code-Review, dass mindestens eine davon ignoriert wird, meistens die erste.

System-Haptik-Einstellungen respektieren

Unter Einstellungen → Töne & Haptik → System-Haptik kann der Nutzer alle Systemhaptik deaktivieren. Die gute Nachricht: .sensoryFeedback respektiert diese Einstellung automatisch. Die schlechte: CHHapticEngine-Patterns tun das NICHT. Wenn du CoreHaptics benutzt, musst du selbst prüfen:

import CoreHaptics

var systemHapticsEnabled: Bool {
    // Ab iOS 17 verfügbar
    return CHHapticEngine.capabilitiesForHardware().supportsHaptics
        && UIDevice.current.userInterfaceIdiom == .phone
}

Zusätzlich solltest du in deinen App-Settings eine eigene Option „Haptic Feedback“ anbieten. Nutzer, die für alle Apps Haptik wollen, aber für dein Musikinstrument nicht, sollen granulare Kontrolle haben.

Reduce Motion beachten

Wenn UIAccessibility.isReduceMotionEnabled aktiv ist, solltest du auch aufwendige Haptik-Sequenzen reduzieren, nicht nur Animationen. Continuous Haptics, die 2+ Sekunden vibrieren, sind für Nutzer mit vestibulären Störungen unangenehm. Fallback: einzelner transienter Impact statt Sequenz.

@Environment(\.accessibilityReduceMotion) private var reduceMotion

.sensoryFeedback(reduceMotion ? .impact(.light) : .success, trigger: didSave)

VoiceOver-Nutzer nicht überfluten

VoiceOver hat eigene Sounds und Haptik. Wenn du bei jeder View-Änderung zusätzlich haptisches Feedback triggerst, konkurriert das mit der Screenreader-Ausgabe. Prüfe UIAccessibility.isVoiceOverRunning und reduziere Haptik auf semantisch wichtige Events (Erfolg, Fehler).

Plattform-Unterschiede: iPhone, Watch, iPad, Mac

Der .sensoryFeedback-Modifier ist plattformübergreifend definiert und kompiliert überall. Aber das Verhalten ist drastisch unterschiedlich:

  • iPhone (7 und neuer): Voller Support via Taptic Engine. Alle Feedback-Varianten funktionieren wie erwartet.
  • Apple Watch: Eigene, deutlich stärkere Haptik. Die Varianten werden auf die Watch-eigenen Muster gemappt (notification, directionUp, directionDown). Verwende zusätzlich WKInterfaceDevice.current().play(.click) für UI-Interaktionen.
  • iPad: Keine Haptik. Der Modifier ist ein No-Op, also kein Crash, aber auch keine Vibration. Baue KEINE Fallback-Sounds, das ist eine bewusste Design-Entscheidung Apples.
  • Mac: Nur MacBooks mit Force-Touch-Trackpad reagieren, und auch das nur auf sehr wenige Gesten. Für Standard-Buttons: nichts.
  • tvOS / visionOS: Kein Support. Auf Vision Pro gibt es stattdessen räumliches Audio-Feedback als äquivalente Modalität.

Haptik-Anti-Patterns aus der Praxis

Die häufigsten Bugs, die ich in Code-Reviews sehe, und warum sie sich furchtbar anfühlen:

1. Haptik in einem ScrollView-onScroll-Callback

Sieht harmlos aus, schmerzt in der Hand. Der Scroll-Callback feuert 60 bis 120 mal pro Sekunde. Deine Taptic Engine wird zum Vibrationsschlagbohrer. Wenn du Haptik beim Scrollen willst, triggere nur bei semantischen Grenzen (Section-Header, Snap-Points).

2. Feedback OHNE korrespondierende visuelle Änderung

Haptik ohne Optik ist verwirrend. Der Nutzer spürt eine Vibration, weiß aber nicht warum. Immer beides: Sichtbare State-Änderung UND Haptik. Wenn du keine visuelle Änderung anzeigen kannst, lass die Haptik weg.

3. .error für nicht-fehlerhafte Events

Ich sehe .error ständig für „Suche brachte keine Ergebnisse“ oder „Ende der Liste erreicht“. Das ist kein Fehler, das ist ein neutrales Event. Verwende .impact(.rigid) oder .alignment.

4. Doppel-Trigger durch UIKit + SwiftUI

In hybriden Apps sehe ich, dass Entwickler in einem UIViewRepresentable nochmal manuell einen UIImpactFeedbackGenerator feuern, obwohl der Parent-View bereits .sensoryFeedback hat. Ergebnis: doppelte Vibration. Entscheide dich für eine Ebene.

Timing zwischen Motion Design und Haptik

Das ist der Teil, wo Mobile-UX-Engineering und Motion Design zusammenkommen, und wo die meisten Apps schlampen. Die Regel, die ich aus zwei Jahren Fitness-App-Arbeit destilliert habe: Haptik muss auf dem visuellen Peak der Animation sitzen, nicht am Anfang.

Wenn du eine Spring-Animation mit .spring(response: 0.4, dampingFraction: 0.7) nutzt, ist der visuelle Peak bei etwa 40 % der Response-Zeit, also ~160 ms nach Start. Wenn du die Haptik synchron zum Trigger auslöst, kommt sie 160 ms vor dem visuellen Peak. Das fühlt sich falsch an. Lösung: Haptik minimal verzögern.

Button("Kaufen") {
    withAnimation(.spring(response: 0.4, dampingFraction: 0.7)) {
        isPurchased = true
    }
    Task {
        try? await Task.sleep(for: .milliseconds(80))
        // Manueller Trigger für präzises Timing
        UIImpactFeedbackGenerator(style: .medium).impactOccurred()
    }
}

Für einfache Fälle reicht der Modifier ohne Delay. Das menschliche Wahrnehmungssystem toleriert bis zu 100 ms Offset. Aber bei „Signature Moments“ (Purchase Confirmation, Level Complete, Erstes Öffnen) lohnt sich das Delay-Feintuning. Kombiniere das mit den Timing-Prinzipien aus unserem Guide zu SwiftUI KeyframeAnimator für orchestrierte Multi-Property-Choreografien.

Ein zweiter Praxis-Wert: Bei .spring(response: 0.3) (schnelle Reaktion, z. B. Toggle-Feedback) reicht die synchrone Haptik. Bei .spring(response: 0.6+) (weiche Animation, z. B. Sheet-Präsentation) IMMER 120 bis 180 ms Delay einbauen. Der Rest ist Ohren, Finger, iterieren. Für stilistische Konsistenz mit dem neuen iOS 26 Design-System siehe auch unseren Liquid Glass Guide. Haptik und Material-Physik sollten dieselbe „Weichheit“ kommunizieren.

Häufig gestellte Fragen

Wann sollte ich .sensoryFeedback statt CoreHaptics verwenden?

In 90 % der Fälle. .sensoryFeedback ist der richtige Weg für alle Standard-UI-Interaktionen: Buttons, Toggles, Picker, Slider-Snaps, Sheet-Präsentationen. CoreHaptics brauchst du nur, wenn du benutzerdefinierte Rhythmen (Herzschlag, Musik), Continuous Feedback über 200 ms, oder synchronisiertes Audio-Haptik-Pairing implementierst.

Warum funktioniert Haptic Feedback nicht auf dem iPad?

Kein iPad hat eine Taptic Engine. Der .sensoryFeedback-Modifier ist auf iPadOS ein bewusster No-Op. Er kompiliert, macht aber nichts. Das ist kein App-Bug, sondern eine Hardware-Limitierung. Baue keine Sound-Fallbacks: Apples HIG rät explizit davon ab, weil es die plattformübergreifende Erwartung bricht.

Wie oft darf ich haptisches Feedback triggern, ohne den Nutzer zu nerven?

Als Faustregel: Nicht öfter als einmal pro Sekunde bei aktiver Interaktion und maximal 3 bis 4 Mal pro logische Aktion. Continuous Scrolls, Wischen durch Listen und Text-Eingabe sollten KEINE Haptik triggern. Nutze Haptik als semantischen Akzent, nicht als Dauerbegleitung.

Verbraucht CoreHaptics viel Batterie?

Transiente Events sind praktisch kostenlos (unter 0,01 % pro Trigger). Continuous Events über 500 ms verbrauchen messbar mehr, vergleichbar mit einem Vibrationsanruf. Für Spiele mit intensiver Haptik solltest du eine In-Game-Option bereitstellen, die es reduziert oder deaktiviert. Der SwiftUI-Modifier ist immer der batteriesparendste Weg.

Kann ich haptisches Feedback im Simulator testen?

Nein. Der iOS-Simulator hat keine Haptik-Simulation. Alle Aufrufe werden als No-Op verarbeitet, ohne Fehler oder Log-Meldung. Für Haptik-Design MUSST du auf einem physischen Device testen, idealerweise mit unterschiedlichen Modellen, da die Taptic Engine zwischen iPhone SE, Standard iPhone und iPhone Pro merklich unterschiedlich reagiert.

Wie erstelle ich benutzerdefinierte AHAP-Patterns in Xcode 26?

Neue Datei → „Haptic Pattern (AHAP)“ in Xcode 26. Der neue visuelle Editor zeigt die Timeline mit Waveform-Ansicht; Events lassen sich per Drag & Drop platzieren. Der Preview-Button spielt das Muster direkt auf einem verbundenen iPhone ab, ohne Build-Roundtrip. Für ältere Xcode-Versionen musst du die JSON-Struktur manuell editieren.

Diana Kowalski
Über den Autor Diana Kowalski

Mobile UX engineer translating design intent into pixel-perfect SwiftUI. Has strong opinions about haptics.