Fünf Apple-Plattformen, drei gemeinsame Dateien: Wie Return plattformübergreifendes SwiftUI wirklich ausliefert
Return, mein Meditationstimer, läuft auf fünf Apple-Plattformen: iPhone, iPad, Mac, Apple Watch und Apple TV.1 Die Codebasis umfasst 40 Swift-Dateien (Tests ausgenommen). Drei davon werden von allen fünf Plattformen gemeinsam genutzt. Der Rest verteilt sich auf getrennte Xcode-Targets, die Konzepte wie TimerManager, AudioManager und ContentView duplizieren, statt sie über bedingte Kompilierung mit #if os(...) zu teilen.
Die Teilungsquote liegt bei rund 7,5 % — und das ist Absicht.
Dieser Essay handelt davon, wie das Ausliefern einer plattformübergreifenden SwiftUI-App im Jahr 2026 tatsächlich aussieht, warum aggressives Code-Sharing überschätzt wird und was die drei Dateien gemeinsam haben, die es doch geschafft haben.
Die fünf Plattformen, die Return bedient, so wie Apple sie auf developer.apple.com präsentiert. Jede ist in Xcode ein eigenständiges Plattform-Target, kein Zweig zur Laufzeit.
Kurzfassung
- Return: 18 Swift-Dateien im Haupt-Target (iOS + iPadOS + macOS), 10 Dateien im tvOS-Target, 7 im watchOS-Target, 2 Widget-Dateien (Live Activities) und 3 wirklich plattformübergreifende Dateien in
Return/Shared/. Zusammen 40. - Die drei gemeinsamen Dateien sitzen alle nah an der Persistenz:
MeditationSession,SessionStore,SessionHistoryView. Zustand, der über iCloud reist — keine UI, die sich an die Plattform anpasst. - tvOS und watchOS sind eigene Xcode-Targets, keine
#if os(tvOS)-Zweige im Haupt-Target. Die Bedienmodelle liegen zu weit auseinander, um in eine ContentView zu passen. - Selbst innerhalb des Haupt-Targets für iOS/iPadOS/macOS wuchern
#if os-Blöcke: 10 inContentView.swift, 8 inLiveActivityManager.swift, 8 inVideoBackgroundView.swift, 6 inAudioManager.swift. - Die ehrliche Einschätzung: Aggressives Teilen über fünf Apple-Plattformen hinweg ist eine Wartungslast. Ein kleiner gemeinsamer Kern (die Persistenzschicht) plus getrennte, plattformspezifische Oberflächen liefert schneller aus und geht seltener kaputt als eine riesige, mit
#ifdurchsetzte Datei.
Die plattformspezifischen Begleitartikel: Apple-Plattformmatrix, watchOS-Laufzeitvertrag und Liquid-Glass-Muster in SwiftUI.
Die Zahlen
So sieht die Codebasis nach Swift-Dateien aus, Tests und UI-Tests herausgerechnet:
Return/ 18 files (iPhone + iPad + Mac, single target)
├── Shared/ 3 files ← cross-platform truth
│ ├── MeditationSession.swift
│ ├── SessionStore.swift
│ └── SessionHistoryView.swift
├── ContentView.swift (10 #if os branches)
├── TimerManager.swift (2 #if os branches)
├── AudioManager.swift (6 #if os branches)
├── HealthKitManager.swift
├── LiveActivityManager.swift (8 #if os branches, iOS-only)
├── ThemeManager.swift
├── VideoBackgroundView.swift (8 #if os branches)
├── GlassTextShape.swift (Liquid Glass, see prior post)
├── GlassTimerText.swift
└── … (settings, theme, audio assets, etc.)
ReturnTV/ 10 files (tvOS, separate target)
├── TVContentView.swift
├── TVTimerManager.swift ← duplicates main TimerManager
├── TVAudioManager.swift ← duplicates main AudioManager
├── TVDurationPicker.swift
├── TVFocusModifier.swift ← tvOS button styles for focus
├── TVSettingsView.swift
└── …
ReturnWatch Watch App/ 7 files (watchOS, separate target)
├── WatchContentView.swift
├── WatchTimerManager.swift ← duplicates main TimerManager
├── WatchAudioManager.swift ← duplicates main AudioManager
├── WatchHealthKitManager.swift ← duplicates main HealthKitManager (mostly)
├── WatchSettingsView.swift
└── …
ReturnWidgets/ 2 files (Live Activity + bundle)
├── ReturnLiveActivity.swift
└── ReturnWidgetsBundle.swift
Fünf Plattformen, drei gemeinsame Dateien, zwei getrennte Plattform-Targets plus ein Widget-Target, dazu reichlich bedingte Kompilierung im Haupt-Target. Die Teilungsquote beträgt etwa 7,5 %. Die meisten Anleitungen zu „Multi-Plattform-SwiftUI“ empfehlen das Gegenteil: eine einzige ContentView, die sich über @Environment(\.horizontalSizeClass) und #if os(...) an jede Plattform anpasst.2 Bei zwei Plattformen (iPhone + iPad) funktioniert das. Bei fünf bricht es zusammen.
Was die drei gemeinsamen Dateien verbindet
Return/Shared/MeditationSession.swift definiert den Wertetyp im Umfeld von SwiftData:3
struct MeditationSession: Codable, Identifiable, Equatable {
let id: UUID
let startDate: Date
let endDate: Date
let durationSeconds: Int
let sourceDevice: DeviceType
var syncedToHealthKit: Bool
enum DeviceType: String, Codable, CaseIterable {
case iPhone, iPad, mac, appleTV, appleWatch
}
}
Der Kommentar im Dateikopf trägt echte Last: // Add this file to: Return, ReturnTV, ReturnWatch Watch App targets. Dieselbe Quelldatei wird von allen drei Xcode-Targets referenziert — nicht als Symlink, nicht als Swift-Package. Apples Build-System kompiliert eine Datei klaglos in drei Binaries.
SessionStore.swift bildet die Persistenzschicht: eine dünne Hülle um NSUbiquitousKeyValueStore (Apples iCloud Key-Value Store), die MeditationSession-Arrays liest und schreibt. Diese Wahl hat Folgen: Die Synchronisation über den KV-Store liefert Return eine geräteübergreifende Sitzungshistorie, ohne dass ein CloudKit-Container bereitgestellt werden muss — zum Preis, dass der gesamte Speicher auf 1 MB begrenzt ist.12 Für eine Liste von Meditationssitzungen mit je ein paar hundert Bytes ist das Limit reichlich bemessen. SessionHistoryView.swift ist eine SwiftUI-Liste, die diese Sitzungen darstellt. Beide werden von den Targets für iPhone, iPad, Mac, Watch und TV identisch genutzt.
Was diese drei Dateien verbindet: Sie beschreiben Zustand, keine Interaktion. Eine MeditationSession ist auf jedem Gerät dasselbe Konzept. Die Liste vergangener Sitzungen liest sich auf jedem Gerät gleich. Keine der beiden berührt eine Bedienoberfläche, einen Fenstermanager, eine Entscheidung über das Audio-Routing, eine Fokus-Engine oder eine Digital Crown. Sobald eine Datei wissen muss, auf welcher Plattform sie läuft, ist sie nicht mehr teilbar.
Warum der Rest nicht geteilt wurde
Nehmen wir TimerManager. Die Fassung für iOS/iPadOS/macOS verwendet Timer.publish(every: 1, ...) und leitet Benachrichtigungen über UserNotifications. Die tvOS-Fassung (TVTimerManager) behandelt den Fall, dass der Benutzer über die Siri Remote pausiert hat und der Bildschirmschoner anspringt. Die watchOS-Fassung (WatchTimerManager) delegiert an eine WKExtendedRuntimeSession (über WatchSessionManager), damit das System die App reaktionsfähig hält, während das Display gedimmt wird, und nimmt Eingaben über die Digital Crown entgegen statt über Berührung. Drei Plattformen, drei grundverschiedene Timer-Verhaltensweisen.
Man könnte sie als class TimerManager { #if os(watchOS) ... #elif os(tvOS) ... } zusammenlegen. Herauskäme eine Klasse mit drei Betriebsarten zu je vierzig Zeilen #if-abgeschirmtem Code, bei der jeder Eingriff in den iOS-Pfad den watchOS-Pfad zerlegen kann. Ein Wartungsalbtraum.
Drei getrennte Klassen in drei Dateien bedeuten mehr Code auf der Festplatte und weniger Code im Kopf. Lesbare Duplizierung schlägt unverständliche Abstraktion.
Dieselbe Logik gilt für:
ContentViewgegenüberTVContentViewundWatchContentView: Die Navigationsmodelle unterscheiden sich (Push-basiert auf dem iPhone, fokusbasiert auf dem TV, listenbasiert auf der Watch).AudioManagergegenüberTVAudioManagerundWatchAudioManager: Die Kategorien der Audiositzung unterscheiden sich, watchOS hat strengere Regeln für Hintergrund-Audio, tvOS routet anders zu AirPlay.VideoBackgroundViewenthält im Haupt-Target 8#if os(iOS)-Zweige (mit einem#elseif os(macOS)-Gegenstück) für unterschiedliche Video-Assets (fire_phone.mp4gegenüberfire_mac.mp4), unterschiedliche Layer-Typen und unterschiedliche Seitenverhältnisse.4
Der Vollständigkeit halber: Das Haupt-Target Return/ fasst iOS, iPadOS und macOS sehr wohl zusammen. Diese drei Plattformen haben mehr gemeinsamen Code als Unterschiede. SwiftUIs NavigationStack funktioniert auf allen dreien. .glassEffect() funktioniert auf allen dreien. Die Unterschiede im Fenstermanagement sind real, aber innerhalb eines Targets beherrschbar. Bei tvOS und watchOS habe ich die Grenze zum eigenen Target gezogen.
Der Fall tvOS: Warum die Fokus-Engine ein eigenes Target erzwang
Die Navigation auf dem Apple TV ist um die Fokus-Engine herum gebaut.5 Jedes bedienbare UI-Element meldet sich als fokussierbar an; die Richtungstasten der Siri Remote bewegen den Fokus zwischen den Elementen; ein Druck auf Auswählen aktiviert das fokussierte Element. SwiftUI legt das auf tvOS über .focusable(), .focusEffect und eigene ButtonStyle-Typen offen, die auf @Environment(\.isFocused) reagieren — für jenen Parallaxe-Kippeffekt, den Apples eigene Apps verwenden. Produktivcode aus TVFocusModifier.swift:6
struct TVCapsuleButtonStyle: ButtonStyle {
var accentColor: Color = .white
@Environment(\.isFocused) private var isFocused
func makeBody(configuration: Configuration) -> some View {
configuration.label
.colorMultiply(isFocused ? focusedTextColor : accentColor)
.background(
Capsule().fill(isFocused
? AnyShapeStyle(accentColor)
: AnyShapeStyle(.ultraThinMaterial))
)
.clipShape(Capsule())
.scaleEffect(isFocused ? 1.1 : 1.0)
.scaleEffect(configuration.isPressed ? 0.95 : 1.0)
.shadow(color: .black.opacity(isFocused ? 0.3 : 0.1),
radius: isFocused ? 20 : 5, y: isFocused ? 10 : 2)
.animation(.easeInOut(duration: 0.2), value: isFocused)
}
}
Dieselbe Datei definiert außerdem TVCircleButtonStyle für quadratische und runde Bedienelemente. Beide Stile kehren Farbe und Transparenz bei Fokus um: Unfokussierte Schaltflächen sitzen auf .ultraThinMaterial, fokussierte füllen sich mit der Akzentfarbe und legen bei Skalierung und Schatten zu. Für diese App ist das Muster strukturell tvOS-spezifisch. @Environment(\.isFocused) steht zwar auf iOS, iPadOS, macOS, watchOS und tvOS zur Verfügung,13 doch nur auf tvOS ist die fokusgetriebene Navigation das primäre Interaktionsmodell — dort erzeugt die Siri Remote weder Zeiger- noch Berührungsereignisse. Auf dem iPhone oder iPad wird das entsprechende Bedienelement per Tippen getroffen, auf dem Mac überfahren oder angeklickt. Die Schaltflächenstile in TVFocusModifier.swift setzen voraus, dass Fokus die zentrale Handlungsaufforderung ist, und richten die gesamte visuelle Reaktion daran aus. Es gibt keinen guten Weg, eine ContentView zu schreiben, die Berührung auf iOS, Hover auf dem Mac und fokusgetriebene Navigation auf tvOS an einer Stelle abbildet. Die Struktur der View ist schlicht eine andere: Eine tvOS-ContentView ist ein Graph fokussierbarer Zeilen, eine iOS-ContentView ein Stapel aus Tippen und Handeln.
Für den Dauer-Picker gilt dasselbe. Auf dem iPhone fährt er von unten herein und reagiert auf Antippen. Auf dem Apple TV ist er eine waagerechte Reihe fokussierbarer Zellen, durch die der Benutzer mit der Fernbedienung navigiert. TVDurationPicker.swift ist eine eigene Datei, weil das zellenbasierte Fokusdesign auf dem iPhone keine Entsprechung hat. Beide in eine Datei zu pressen hieße, zwei zusammenhanglose Oberflächen mit #if os(tvOS) aneinanderzukleben.
Der Fall watchOS: Erweiterte Laufzeitsitzungen, HealthKit und weniger Fläche
watchOS bringt zwei strukturelle Einschränkungen mit, die es auf den anderen Plattformen nicht gibt:
WKExtendedRuntimeSession, damit die App reaktionsfähig bleibt, während das Watch-Display abgedunkelt ist.8 Ohne sie setzt watchOS die App zwischen den Sekundentakten aggressiv aus, und der Timer läuft aus dem Tritt. Return deklariertWKBackgroundModes: mindfulnessin derInfo.plistdes watchOS-Targets, damit das System den Anwendungsfall erkennt und Laufzeitbudget gewährt; die Laufzeitsitzung selbst wird mit dem StandardinitialisiererWKExtendedRuntimeSession()erzeugt.- iCloud-Synchronisation über
NSUbiquitousKeyValueStore, nicht über WatchConnectivity.7 Returns Abgleich der Sitzungshistorie fährt auf demselben Key-Value Store mit, den auch die Targets für iPhone, iPad und Mac nutzen. Eine auf der Watch protokollierte Meditation taucht deshalb in der Historie des iPhones auf, ganz ohne direkten Nachrichtenaustausch zwischen Uhr und Telefon. WatchConnectivity wäre künftig eine Option für die Synchronisation von Live-Zuständen, doch Return hat sich für das einfachere Modell entschieden: Jedes Gerät schreibt in denselben iCloud-KV-Store, und der nächste Lesevorgang auf einem beliebigen Gerät sieht die Vereinigungsmenge.
WatchTimerManager.swift ist der Timer auf Watch-Seite; die Arbeit mit der erweiterten Laufzeit delegiert er an WatchSessionManager, der in ReturnWatchApp.swift als final class WatchSessionManager: NSObject, WKExtendedRuntimeSessionDelegate definiert ist. Der TimerManager unter iOS hat kein Gegenstück dazu, weil iOS-Apps im Vordergrund auch ohne explizite Laufzeitsitzung reaktionsfähig bleiben. Die Watch-Logik über #if os(watchOS) in den iOS-TimerManager zu schieben hieße: Der iOS-Codepfad importiert WatchKit-Symbole, die er nie verwendet, und der watchOS-Codepfad braucht Initialisierungswege, die der iOS-Pfad nicht kennt.
WatchHealthKitManager.swift ist eine abgespeckte Variante des Haupt-HealthKitManager. Achtsamkeitsminuten protokolliert er auf dieselbe Weise, doch die Benutzerführung bei der Berechtigungsabfrage unterscheidet sich (die Watch kann kein HealthKitPermissionSheet anzeigen). Die Watch-Klasse ist ungefähr halb so groß wie die im Haupt-Target.
Was im Haupt-Target für iOS/iPadOS/macOS passiert
Selbst innerhalb des Haupt-Targets ergibt sich Teilen nicht von allein. ContentView.swift enthält zehn #if os(macOS)- oder #if !os(macOS)-Blöcke, LiveActivityManager.swift acht, VideoBackgroundView.swift acht, AudioManager.swift sechs. Live Activities gibt es nur auf dem iPhone, also steckt der gesamte LiveActivityManager in einem #if os(iOS). Der Dauer-Picker verwendet auf dem iPhone ein anderes Layout als auf iPad und Mac, weshalb ContentView parallele Layout-Zweige führt.
Bewährt hat sich dieses Muster: #if os(...) für kleine Plattformunterschiede (abweichendes Tastaturverhalten, anderes Padding, fehlende API), ein eigenes Target für große strukturelle Unterschiede (Fokus statt Berührung, Trainingssitzung statt Timer). Als Schwelle hat sich bei mir „mehr als rund 10 Zeilen Verzweigung“ eingespielt. Darunter ist bedingte Kompilierung völlig in Ordnung. Darüber erledigt die Datei zwei Aufgaben zugleich — und die zweite gehört in ein anderes Target.
Wann man nicht auf allen fünf Plattformen ausliefern sollte
Die ehrliche Einschätzung.
Lassen Sie die Apple Watch aus, wenn Ihre App informationsdicht ist. Auf einem 46-mm-Display ist kein Platz für eine Liste mit 30 Einträgen, einen Dauer-Picker und eine Einstellungsseite. Return überlebt auf watchOS, weil die Kerninteraktion aus einer einzigen Schaltfläche besteht (Timer starten und stoppen). Eine Produktivitäts-App, eine Finanz-App oder eine medienlastige App schafft das nicht.
Lassen Sie das Apple TV aus, wenn Ihre App interaktiv ist. Der Fernseher ist für Nebenbei-Erlebnisse gedacht: ein Timer, der quer durch den Raum auf einem Bildschirm läuft, Musikwiedergabe. Alles, was häufige Eingaben verlangt, kämpft gegen die Plattform an. Return ist auf tvOS, weil „20 Minuten einstellen und aufs Feuer auf dem Bildschirm schauen“ genau der richtige Fall für die Nutzung nebenbei ist. Eine Notiz-App wäre dort eine Qual.
Lassen Sie den Mac aus, wenn Ihre App eine primär fürs Telefon gedachte Oberfläche hat. SwiftUI funktioniert auf dem Mac, doch das Push-Modell des NavigationStack wirkt neben einer echten Mac-Seitenleiste wie Spielzeug. Wenn die App auf dem Mac halbfertig wirken würde, liefern Sie lieber Catalyst aus (das die iPad-App konvertiert) — oder lassen Sie den Mac ganz weg, bis Sie eine Mac-native Oberfläche bauen können.
Lassen Sie das iPad aus, wenn Sie die Anpassung an Size Classes nicht gemacht haben. Eine auf iPad-Größe gedehnte iPhone-App wirkt billig. Das iPad braucht mindestens eine NavigationSplitView mit Seitenleiste, idealerweise ein echtes zweispaltiges Layout. Return nutzt auf dem iPad Split Views und auf dem iPhone Stacks. Der Code liegt im selben Target, die Oberfläche ist trotzdem eine grundlegend andere.
Die Regel, die ich aufgestellt habe: Liefern Sie auf einer Plattform aus, wenn die Kerninteraktion Ihrer App zum Eingabemodell dieser Plattform passt. Einen Meditationstimer auf der Apple Watch — ein Antippen genügt zum Start. Einen Meditationstimer auf dem Apple TV — einstellen und vergessen. Aber kein Kanban-Board auf einer der beiden Plattformen.
Was mühelos mitreist
Die drei Dinge, die in Return tatsächlich über alle fünf Plattformen hinweg geteilt werden:
- Das Datenmodell (
MeditationSession). Das Struct ist auf jeder Plattform identisch, wird überNSUbiquitousKeyValueStoresynchronisiert, und jede Plattform kann lesen, was eine andere geschrieben hat. - Die Ansicht der Sitzungshistorie (
SessionHistoryView). EineListvergangener Sitzungen rendert auf iPhone, iPad, Mac, Apple Watch und Apple TV identisch. SwiftUIsListgehört zu den wenigen Primitiven, die sich über alle fünf Formfaktoren sauber anpassen. - Die Persistenzhülle (
SessionStore). Lesen und Schreiben sind plattformunabhängig; der darunterliegende Speicher (NSUbiquitousKeyValueStore) ist überall dieselbe API.
Drei Konzepte: Zustand, Listendarstellung, Persistenz. Alles Zustandsbehaftete und Darstellende, das kein hardwarespezifisches Eingabemodell berührt, lässt sich teilen. Alles, was Eingabe, Fokus, Audio-Routing, Bildschirmgröße oder Hintergrundausführung berührt, nicht.
Dasselbe Muster taucht im Leitfaden zur iOS-Entwicklung mit Agenten auf, wo ich mit anderen Worten dasselbe argumentiert habe: Die Teile einer iOS-App, die ein Agent schreiben kann, teilen den Großteil ihres Codes mit den Teilen, die ein Mensch schreibt; die Teile, die menschliches Urteilsvermögen verlangen (Signierung, visueller Feinschliff, Performance), sind genau jene, die sich auch plattformübergreifend schlecht teilen lassen.9 Die beiden Grenzlinien fallen zusammen. In beiden Fällen geht es darum, ab wann Fachwissen zu zählen beginnt.
Was Multi-Plattform kostet
Der ROI verteilt sich ungleich. Ein iPad zu einer iPhone-App hinzuzunehmen kostet vielleicht 20 % mehr Code (Verzweigungen nach Size Class, hier und da eine Split View). Den Mac in dasselbe Target aufzunehmen kostet weitere 15 bis 20 % (#if os(macOS)-Zweige, Menüleiste, Fensterverwaltung). Jedes größere Target schlägt bei einer kleinen App mit etwa 10 Dateien zu Buche.
Teuer werden Apple Watch und Apple TV. watchOS zu Return hinzuzufügen erforderte 11 neue Dateien in einem eigenen Target, darunter dedizierte Manager für Audio, Timer und HealthKit. tvOS erforderte 10 neue Dateien in einem weiteren eigenen Target, darunter Fokusverwaltung und einen eigenen Dauer-Picker. Zusammen haben sie die Swift-Oberfläche fast verdoppelt — für etwas, das auf Ebene der Nutzerfunktionen dieselbe App ist.
Die Entscheidung, auf allen fünf auszuliefern, entsprang nicht dem Wunsch, um der Sache willen multi-plattform zu sein. Es waren mehrere getrennte Entscheidungen: die Apple Watch, weil Meditationstimer wirklich ans Handgelenk gehören; das Apple TV, weil das Format des Umgebungsbildschirms zu langen Sitzungen im Raum passt; der Mac, weil manche Leute zwischen zwei Meetings am Schreibtisch meditieren. Jede Plattform hat sich ihr Target durch einen echten Anwendungsfall verdient.
Verdient eine Funktion ihr Target nicht, ist es günstiger, die Plattform auszulassen und dort nachzulegen, wo die App herausragend ist.
Was das für Ihre App bedeutet
Drei Erkenntnisse zum Mitnehmen.
- Standardmäßig ein Target für jede große Plattformgruppe. iOS + iPadOS + macOS in einem Target funktioniert, weil die Kerninteraktion (Berührung plus Cursor) ähnlich ist. tvOS in ein eigenes Target. watchOS in ein eigenes Target. Jedes zusätzliche Target kostet rund 10 Dateien, erspart Ihnen aber eine Gottklasse mit
#if-Zweigen, die unbegrenzt wuchern. - Teilen Sie beherzt Zustand, nicht Interaktion. Codable-Modell-Structs, Persistenzhüllen und
List-Darstellungen reisen praktisch kostenlos mit. Timer-Manager, Audio-Manager und Content Views nicht. - Jede Plattform muss sich ihr Target verdienen. Liefern Sie nicht auf watchOS aus, nur weil es geht. Liefern Sie aus, wenn die Kerninteraktion Ihrer App zum Eingabemodell der Plattform passt. Den Rest lassen Sie weg.
Dieses Muster greift neben den drei anderen Oberflächen, über die ich für dieselbe App-Familie geschrieben habe: typisierte App Intents für Apple Intelligence, MCP-Server für Agenten über verschiedene LLMs hinweg, Liquid Glass für den Menschen am Gerät. Die äußerste Schicht desselben Stacks ist die Plattform: auf welchen Bildschirmen die App überhaupt läuft. Wählen Sie sie so bewusst, wie Sie die KI-Oberfläche wählen.
FAQ
Warum kein Swift-Package für gemeinsamen Code?
Ich habe es erwogen. Bei drei Dateien bringt ein Swift-Package mehr Zeremonie mit, als es einspart. Apples Build-System in Xcode 26 kompiliert eine Quelldatei anstandslos in mehrere Targets, sobald Sie die Häkchen bei der Target-Zugehörigkeit setzen. Ein Package fügt eine separate Package.swift hinzu, ein separates Test-Target und eine Indirektionsstufe, die jedes Refactoring durchqueren muss. Für einen kleinen gemeinsamen Kern gewinnt die einfachere Antwort.10
Funktioniert SwiftData auf watchOS und tvOS?
SwiftData ist ab iOS 17, macOS 14, watchOS 10 und tvOS 17 verfügbar und deckt damit jede Plattform ab, die Return bedient.11 Das Struct MeditationSession ist schlichtes Codable und kein @Model, weil Return die Sitzungshistorie über NSUbiquitousKeyValueStore synchronisiert statt über einen SwiftData-Container. Für @Model-Typen funktioniert das Muster genauso: Die Modelldatei wird geteilt, der Persistenzcontainer unterscheidet sich pro Plattform, falls er es muss.
Sollte ich Mac Catalyst oder ein natives Mac-Target verwenden?
Catalyst ist das richtige Werkzeug, wenn die iPad-App gut genug ist, damit sich die per Catalyst neu gebaute Mac-Fassung nativ anfühlt. Returns Haupt-Target ist ein echtes Multi-Plattform-Target (kein Catalyst), mit SwiftUI für iOS, iPadOS und macOS in einem Binary gebaut. Die Mac-Oberfläche stellt über #if os(macOS) anders dar als das iPad: Seitenleiste statt Sheet, Tastenkürzel auf Schaltflächen und so weiter. Catalyst wäre einfacher gewesen, doch die Mac-Oberfläche hätte nach einer iPad-App auf einem Mac ausgesehen — genau der Schwachpunkt, für den Catalyst bekannt ist.
Lohnt sich Apple TV für eine kleine App?
Wahrscheinlich nicht. Apple-TV-Apps haben sehr spezifische Anwendungsfälle: Nutzung nebenbei, Medien, Gelegenheitsspiele. Passt Ihre App in keinen davon, ist das Publikum der Plattform zu klein, um die 10 zusätzlichen Swift-Dateien zu rechtfertigen. Return bedient tvOS gerade deshalb, weil lange Meditationssitzungen auf einem Bildschirm quer durch den Raum zu den wenigen produktivitätsnahen Anwendungsfällen gehören, die zur Plattform passen.
Wie lange dauert es, auf allen fünf Plattformen auszuliefern?
Eine präzise Zahl ist schwer zu nennen; es hängt von der App ab. Return kam von Tag eins an plattformübergreifend heraus, statt Plattformen nachträglich zu ergänzen — das geht schneller als jede Nachrüstung. Als grobe Faustregel: Ein MVP nur fürs iPhone plus iPad-Unterstützung plus Mac-Unterstützung entspricht etwa dem 1,5-fachen der Zeit für die reine iPhone-Fassung. Die Apple Watch kommt mit dem 0,5-fachen dazu. Das Apple TV mit weiteren 0,5. Eine Erstveröffentlichung für fünf Plattformen liegt damit ungefähr beim 2,5-fachen des reinen iPhone-Aufwands — mit der Einschränkung, dass hier ein agentengestützter Build vorlag, bei dem der größte Teil des duplizierten Codes von Claude Code per Massenbearbeitung geschrieben und nicht von Hand getippt wurde.
Referenzen
-
Return des Autors, eine Meditationstimer-App, die am 21. April 2026 im App Store erschienen ist. Native Targets: iOS 26+, iPadOS 26+, macOS 26+, watchOS 26+, tvOS 26+. Durchgehend SwiftUI.
NSUbiquitousKeyValueStorefür die geräteübergreifende Sitzungshistorie. ↩ -
Apple Developer, “Configuring a Multi-Platform App” sowie der Vortrag “SwiftUI essentials” auf der WWDC 2024. Apples Standardempfehlung tendiert zu einem einzigen Target mit umgebungsgesteuerter Anpassung; der Weg über mehrere Targets, den dieser Artikel beschreibt, weicht bewusst davon ab. ↩
-
Produktivcode in
Return/Return/Shared/MeditationSession.swift,SessionStore.swift,SessionHistoryView.swift. Der Kommentar im Dateikopf vonMeditationSession.swiftlautet: „Add this file to: Return, ReturnTV, ReturnWatch Watch App targets.“ ↩ -
Produktivcode in
Return/Return/VideoBackgroundView.swift(8#if os(iOS)-Zweige plus ein#elseif os(macOS)-Zweig),Return/Return/ContentView.swift(10#if os-Zweige),Return/Return/AudioManager.swift(6#if os-Zweige),Return/Return/LiveActivityManager.swift(8#if os-Zweige, Datei ist iOS-only). Die Zweigzahlen stammen ausgrep -Ec '^\s*#if os\\(' <file>. ↩ -
Apple Developer, “Focus interactions”, Human Interface Guidelines. Die Fokus-Engine von tvOS ist ein grundlegend anderes Navigationsmodell als Berührung auf iOS oder Zeigersteuerung auf dem Mac. ↩
-
Produktivcode in
Return/ReturnTV/TVFocusModifier.swift. Definiert zweiButtonStyle-Typen (TVCapsuleButtonStyleundTVCircleButtonStyle), die@Environment(\.isFocused)einbinden, um Farbe und Transparenz bei Fokus umzukehren und Skalierung plus Schatten anzuwenden. ↩ -
Apple Developer, “WatchConnectivity”. Das Framework für die Kommunikation zwischen gekoppeltem iPhone und Watch; Return verwendet es nicht für den Sitzungsabgleich, sondern setzt auf den iCloud Key-Value Store. ↩
-
Apple Developer, “WKExtendedRuntimeSession” und der Info.plist-Schlüssel “WKBackgroundModes”. Der Wert
mindfulnessist dokumentiert als: „Enables extended runtime sessions for silent meditation“ — passgenau für einen Meditationstimer. Return erzeugt eine Standard-WKExtendedRuntimeSession()und deklariertWKBackgroundModes: mindfulnessin derInfo.plistdes watchOS-Targets. Produktivcode:Return/ReturnWatch Watch App/ReturnWatchApp.swiftdefiniertWatchSessionManager: NSObject, WKExtendedRuntimeSessionDelegate;WatchTimerManager.swiftdelegiert die Arbeit mit der erweiterten Laufzeit dorthin. ↩ -
Analyse des Autors in iOS-Apps mit KI-Agenten bauen, dem Praxisleitfaden zur agentengestützten iOS-Entwicklung über 8 Produktiv-Apps hinweg. ↩
-
Apple Developer, “Configuring a Multi-Platform App”. Die Target-Zugehörigkeit erlaubt es, eine Quelldatei ohne Swift-Package in mehrere Targets zu kompilieren. Das richtige Werkzeug für kleine gemeinsame Kerne. ↩
-
Apple Developer, “SwiftData” Plattformverfügbarkeit. Verfügbar ab iOS 17, iPadOS 17, macOS 14, watchOS 10, tvOS 17 und visionOS 1 — damit sind alle fünf Apple-Plattformfamilien abgedeckt. ↩
-
Apple Developer, “NSUbiquitousKeyValueStore”. Apples iCloud Key-Value Store zum Synchronisieren kleiner Zustandsmengen über die Geräte eines Benutzers hinweg. Die Gesamtgröße des Speichers ist laut Apples veröffentlichten Grenzwerten über alle Schlüssel hinweg auf 1 MB begrenzt. Produktivcode:
Return/Return/Shared/SessionStore.swift. ↩ -
Apple Developer,
EnvironmentValues.isFocused. Verfügbar ab iOS 14, iPadOS 14, macOS 11, tvOS 14 und watchOS 7. Die API ist plattformübergreifend; unterschiedlich ist nur, ob Fokus die primäre Navigationshilfe des Benutzers darstellt. ↩