Dieser Beitrag ist eine Fortsetzung bzw. Teil 2 meines vorherigen Beitrags „Foundation Models – die Zukunft der iOS-Entwicklung“. Ich möchte hier einen konkreten Anwendungsfall unter Verwendung des Foundation Models-Frameworks vorstellen. Ich habe mich entschieden, ein Beispiel für die Erstellung einen Prompt zu erstellen, die einen wöchentlichen Trainingsplan generiert. Es handelt sich also nicht um eine Chat-Bubble-Anwendung, in der man mit der Apple Intelligence diskutieren kann, sondern um eine vordefinierter Prompt, der die Foundation Models auffordert, nützliche Informationen zu generieren, gesteuert durch Swift-Makros wie @Generable und @Guide.

Für wen es gedacht ist

Wenn Sie ein iOS-Entwickler sind und konkrete Anwendungsbeispiele für Apple Intelligence und LLM sehen möchten, sind Sie hier genau richtig. Gleichzeitig werde ich versuchen, die Sache so weit wie möglich zu vereinfachen, damit jeder die wesentlichen Punkte und den Kontext verstehen kann.

Motivation

Wenn du iOS-Entwickler bist und konkrete Beispiele dafür suchst, wie Apple Intelligence und LLM eingesetzt werden können, wirst du auf deine Kosten kommen. Gleichzeitig werde ich versuchen, die Dinge so weit wie möglich zu vereinfachen, damit jeder die wichtigsten Punkte und den Kontext versteht.

Erstellung eines wöchentlichen Trainingsplans

Die Architektur auf einen Blick

Was umfasst die iOS-App-Lösung?

  • SwiftUI (PlansHomeView / WorkoutPlannerView / WeeklyPlanDetailView)
  • Reine Präsentation + Benutzeraktionen. Keine KI-Logik, keine Persistenzlogik.
  • WorkoutPlannerVM (ViewModel, bekannt aus dem MVVP-Muster)
    Die einzige Stelle, die mit dem Modell kommuniziert. Erstellt die Eingabeaufforderung, ruft LanguageModelSession auf, validiert, ordnet Wochentage den tatsächlichen Daten zu und speichert/lädt über SwiftData.
  • FoundationModels (LanguageModelSession, Prompt, @Generable)
    Erzeugt ein typisiertes [WorkoutSchedule] anstelle von Freitext.
  • SwiftData (WeeklyPlan → WorkoutDay → Exercise)
    Normalisierte Speicherung; steuert die Listen- und Detailansichten über @Query.

Warum diese Form?
Die KI-Variabilität bleibt auf die VM (das ViewModel) beschränkt, während UI und Storage deterministisch und testbar bleiben.

Das gleiche Muster lässt sich auf Ernährungspläne, Lernpläne, Reisen – kurz gesagt auf jeden „Erstellen → Bearbeiten → Speichern“-Workflow – übertragen.

Das Schema: @Generable

Der @Generable-Typ fungiert als Contract, der das Model in die Pflicht nimmt. Anstatt freien Text anzufordern, fragst du nach einem konkreten Array von WorkoutSchedule, und das Framework parst bzw. validiert den LLM-Output direkt in diesen Typ. Falls das Parsing fehlschlägt, merkst du das sofort – kein fehleranfälliges „String-Wrangling“ mit unsauberem JSON.

Wie die Felder die Generierung steuern

  • day: Ein String mit @Guide(.anyOf([...])) fixiert den Wert auf Montag–Sonntag. Das macht es trivial, ihn einem tatsächlichen Datum innerhalb der gewählten Woche zuzuordnen (keine vagen „Leg Day“-Labels, die man mühsam dekodieren müsste).
  • dayTitle: Ein String als nutzerfreundliche Zusammenfassung (z. B. „Upper Push + Core“). Er ist unbeschränkt, wird aber durch die Description dazu angeregt, kurz und thematisch passend zu bleiben.
  • exercises: [String] ist der eigentliche Payload: exakt 5 präzise Namen pro Tag. Wir erzwingen dies zusätzlich in einem kleinen Post-Processing-Schritt (Deduplizierung + Padding), da Business-Logik in deine App gehört und nicht allein in den Prompt.

Warum Schema-First besser ist als Freitext

  • Standardmässig typisiert: Du erhältst ein [WorkoutSchedule], keinen Datenklumpen. Das ist einfacher zu rendern, zu editieren und zu speichern.
  • Geringere Fehleranfälligkeit: Weniger halluzinierte Felder oder Keys; der Parser lehnt Output ab, der nicht dem Schema entspricht.
  • Komponierbar: Du kannst das Modell weiterentwickeln (z. B. später intensity: Int hinzufügen), ohne die UI neu schreiben zu müssen.

Praktische Tipps

  • Halte Feldnamen konkret und im Singular („Exercise“-Strings statt ganzer Absätze).
  • Nutze @Guide(.anyOf(...)) oder Ranges, um kritische Punkte einzuschränken (Enums, Zähler, einfache Formate).
  • Betrachte Prompts als Hinweise, nicht als absolute Absicherung: Behalte einen einfachen Validator (wie Deduplizierung + exakte Anzahl) in deiner VM.

Code und Implementierung

Schauen wir uns an, wie das im Praxiseinsatz funktioniert. Ich werde versuchen, die wichtigsten Teile der Implementierung zu erklären:

1. Prompt-Strategie (typisierter Output)

Ich würde sagen, dass dies der wichtigste Teil bei der Nutzung von Foundation-Modellen ist: das Prompting richtig einzusetzen. Der Nutzer muss exakt das anfordern, was er benötigt. Spezifische Constraints sollten im Prompt erwähnt werden, um ein präziseres Ergebnis im Output zu erzielen.

In unserem Fall enthält der Prompt diese Kernpunkte: 7 Tage, 5 Übungen pro Tag, keine Duplikate – und die Rückgabe als typisiertes Array mittels @Generable. Hier ist der vollständige Code:

let prompt = Prompt("""
You are a coach. 7 days (Mon–Sun), exactly 5 exercises/day, no duplicates.
Use concise names (e.g., "Goblet Squat"). Output: [WorkoutSchedule]
""")
let schedules: [WorkoutSchedule] = try await session
  .respond(to: prompt, generating: [WorkoutSchedule].self)
  .content

Warum:

Das Model gibt direkt [WorkoutSchedule] zurück (statt Fließtext), was das Rendering und die Validierung trivial macht. Indem du den Prompt kurz und imperativ hältst, bleibt er zudem leicht wartbar.

2. Post-Processing & Mapping auf Daten

Der nächste Teil ist klassisches Swift-Coding: Wir setzen die Business-Rules durch (Deduplizierung + Padding auf 5 Einträge) und lösen die Wochentag-Strings in echte Daten (Dates) innerhalb der ausgewählten Woche auf.

// Map Mon..Sun → dates in the selected week
let cal = Calendar.current
let monday = cal.monday(for: weekStartMonday)
let df = DateFormatter(); df.locale = .current; df.dateFormat = "EEEE"
let nameToDate = Dictionary(uniqueKeysWithValues: (0..<7).map { i in
  let d = cal.date(byAdding: .day, value: i, to: monday)!; return (df.string(from: d), d)
})

// Dedup + pad
var used = Set<String>()
func clean(_ xs: [String]) -> [String] {
  var out:[String]=[]
  for s in xs.map({ $0.trimmingCharacters(in:.whitespacesAndNewlines) }) where !s.isEmpty {
    let k = s.lowercased(); if !used.contains(k) { used.insert(k); out.append(s) }
    if out.count == 5 { break }
  }
  while out.count < 5 { out.append("Mobility Flow (out.count+1)") }
  return out
}

Warum: Prompts sind lediglich Hinweise, keine Garantien – daher werden Regeln direkt im Code erzwungen. Das Mapping von „Montag“ auf ein echtes Date macht den Plan erst für die UI und den Storage nutzbar.

3. UX-Flow (Generate → Edit → Save)

Wie sieht der UX-Flow aus? Für diesen Use-Case habe ich ein modernes iOS-Pattern gewählt: Eine Main-Home-Page mit einer Action in der Top-Right Navigation Bar, um die Generierung eines neuen wöchentlichen Trainingsplans zu starten. Beim Speichern nutzen wir Auto-Refresh, sodass der User nichts weiter tun muss, um die aktuellen Ergebnisse sofort zu sehen.

4. Persistenz mit SwiftData (Minimal Save)

In Apps wie dieser ist es enorm wichtig, die Daten zu speichern, damit sie dem User auch offline zur Verfügung stehen. Ich habe mich für einen modernen Persistenz-Ansatz mit SwiftData entschieden. Dabei werden SwiftData-Models aus den generierten und bereinigten Items erstellt und anschließend gespeichert.

Warum: Damit bleibt die Persistenz absolut simpel: Transformieren → Sortieren → Speichern. Normalisierte Models (WeeklyPlanWorkoutDayExercise) machen spätere Bearbeitungen und Analytics unkompliziert.

var days:[WorkoutDay]=[]
for item in schedules {
  guard let date = nameToDate[item.day] else { continue }
  let ex = clean(item.exercises).map { Exercise(name: $0) }
  days.append(WorkoutDay(date: date, dayTitle: item.dayTitle, exercises: ex))
}
days.sort { $0.date < $1.date }

let plan = WeeklyPlan(weekStart: monday, title: "Workout Plan", days: days)
context.insert(plan)
try context.save()

Fazit

Man braucht kein Chat-UI, um echten Mehrwert aus LLMs zu ziehen. Durch die Kombination von Foundation-Modellen mit einem Schema-First-Ansatz (@Generable), einer schlanken Post-Processing-Ebene und dem vertrauten „Generate → Edit → Save“-Flow lieferst du etwas, mit dem Nutzer tatsächlich arbeiten können: typisierte Daten, die sauber rendern, Änderungen überstehen und via SwiftData persistiert werden. Das ViewModel übernimmt die Variabilität; deine UI und der Storage bleiben vorhersagbar – und testbar.

Dieses Pattern lässt sich hervorragend generalisieren: Ernährungspläne, Lernpläne, Reise-Itineraries, Content-Kalender – eigentlich alles, was eine Listen- oder Zeitplan-Struktur hat. Beginne mit einem strikten Schema, halte die Prompts kurz, validiere im Code und lass SwiftUI den Rest erledigen.

Full Repo: https://bitbucket.org/n47/workout-planner-ios/src/main/

  • Hinweis: Zum Testen des Codes sind Xcode 26 und macOS Tahoe 26.0 oder neuer erforderlich.
  • Wichtig: Deine macOS Tahoe-Version sollte mit deiner Xcode- bzw. Simulator-Version übereinstimmen. Das bedeutet: Wenn du macOS Tahoe 26.4 nutzt, benötigst du auch Xcode 26.4 und die Simulator-Version 26.4.

Ausblick

  • „Regenerate“ pro Tag mit „Avoid-List“-Kontext
  • Filter für Equipment/Intensität (Prompt-Parameter)
  • Export als Markdown/PDF + Kalender-Erinnerungen
  • Optionaler Toggle für On-Device-Modelle und Caching

Danke fürs Lesen – wenn dir das geholfen hat, kannst du das Repo gerne forken, iterieren und zu deinem eigenen Projekt machen.

Leave a Reply


The reCAPTCHA verification period has expired. Please reload the page.