Vor wenigen Monaten habe ich CrossCraft: Custom Crosswords veröffentlicht – eine komplett in SwiftUI geschriebene App, verfügbar auf iOS, iPadOS und macOS. Für den Launch der Vision Pro habe ich mir die Herausforderung gesetzt, sie auf die neue visionOS-Plattform zu portieren – allerdings habe ich mit der Migration erst 3 Tage vor dem Launch begonnen!
Die Frage war also, ob ich das in so kurzer Zeit schaffen würde. Aber zum Glück stellte sich heraus, dass es einfach genug war, und meine App war am Tag 1 bereit!

Die offizielle E-Mail von Apple, in der sie sich bei Day-1-App-Entwicklern bedanken.
Im Folgenden findest du alle meine Erkenntnisse, die dir bei der Migration deiner eigenen Apps helfen können!
Drittanbieter-Frameworks
Nachdem ich das „Apple Vision”-Ziel zu meinem Projekt hinzugefügt hatte, war mein erster Schritt, den „Apple Vision Pro”-Simulator auszuwählen und einen Build zu starten.

Wie erwartet schlug der Build fehl – denn nicht alle Frameworks unterstützen die visionOS-Plattform bereits. Aber grundlegende Unterstützung hinzuzufügen war einfach. Hier sind die 4 Schritte:

Schritt 2: Die Anpassung der Package.swift-Datei ist der wichtigste Schritt.
Forke die Dependency, entferne sie aus deinem Projekt und füge deinen Fork mit dem
main-Branch stattdessen hinzu.Öffne die
Package.swift-Datei im Fork, erhöhe die Swift-Tools-Version oben in der Datei auf5.9und füge.visionOS(.v1)zumplatforms-Array hinzu.Suche nach Vorkommen von
#if os(iOS)und ändere sie zu#if os(iOS) || os(visionOS), um den macOS-Pfad zu vermeiden und den iOS-Pfad zu bevorzugen.Wähle den „Apple Vision Pro”-Simulator und baue das Projekt, um es zu bestätigen.
Wenn du einen Fehler wegen fehlender APIs bekommst, stelle sicher, dass du #if !os(visionOS)-Checks an den richtigen Stellen einfügst. Die meisten APIs sollten aber verfügbar sein, da visionOS ein Fork von iPadOS ist, wie Apple offiziell bestätigt hat. Wenn ein Feature nicht vorhanden ist, wird es entweder bald kommen oder es macht auf der Plattform eben keinen Sinn.
In meinem Fall musste ich nur bei meiner eigenen ReviewKit-Bibliothek etwas Code rund um SKReviewController deaktivieren, der auf visionOS nicht verfügbar ist. Meine Bibliothek macht also effektiv nichts beim Build für visionOS, aber meine iOS- & Mac-Apps fordern weiterhin Nutzer auf, die App zu bewerten. Ich hätte das mit einer eigenen UI lösen können, aber ich habe mich entschieden, erstmal die diesjährige WWDC abzuwarten – in der Hoffnung, dass es dort schon verfügbar wird.
Vergiss nicht, einen Pull Request an das Original-Repo zu stellen, wenn du es geforkt hast, damit auch andere in der Community von deinem Fix profitieren können. Je mehr Leute das machen, desto weniger Dependencies musst du selbst mit Plattform-Support versehen.
Testen meiner App im Simulator
Nachdem ich alle Dependencies behoben hatte, baute ich meine App und es funktionierte!

Leider war ich noch nicht fertig. Zunächst fiel mir auf, dass beim Start der App kein App-Icon angezeigt wurde, obwohl ich eines im Projekt habe. Außerdem entdeckte ich nach dem Start sofort eine Reihe weiterer Probleme. Hier eine Übersicht:
Das App-Icon fehlte
Beim Bewegen des Cursors (= Blick) war die Hover-Form an manchen Stellen falsch
Das Layout & die Größen vieler Fenster, Modals und meiner UI-Elemente stimmten nicht
Meine Accent Color hatte keinen lesbaren Kontrast zum gläsernen Hintergrund
Alle diese Punkte betreffen jede einzelne App, die nach visionOS migriert wird. Für mich halfen kleine Anpassungen, um sie zu beheben. Lass mich meine Erkenntnisse nacheinander teilen.
App-Icon
Es stellt sich heraus, dass visionOS seinen eigenen App-Icon-Stil hat. Sie sind rund wie auf watchOS, bestehen aber aus mehreren Ebenen, um einen Tiefeneindruck zu erzeugen, wie auf tvOS. Du fügst ein visionOS-App-Icon hinzu, indem du auf den +-Button drückst und „visionOS App Icon” auswählst. Dann musst du mindestens ein „Front”- und ein „Back”-Ebenenbild in der Größe 1024 x 1024 bereitstellen. Die „Middle”-Ebene ist optional.

In meinem Fall bestand mein App-Icon bereits aus einer Hintergrundebene und einem Icon im Vordergrund, also war es kein großes Problem, sie separat zu exportieren. Ich musste nur die „Middle”-Ebene im rechten Panel entfernen. Aber weil mein Vordergrund-Icon einen Schatten hatte und die Human Interface Guidelines besagen, dass man „weiche oder ausgeblendete Kanten” bei Nicht-Hintergrund-Ebenen vermeiden soll, musste ich den Schatten entfernen. Das System fügt beim Hover automatisch einen leichten Schatten hinzu.
Apropos Hover: Xcode bietet oben eine Vorschau, wie dein App-Icon aussehen wird, und wenn du mit der Maus darüber fährst, simuliert es den 3D-Hover-Effekt, den Nutzer sehen, wenn sie auf der Vision Pro auf dein App-Icon schauen – richtig praktisch!

Xcodes Vorschau deines App-Icons.
Hover-Effekte
In visionOS gehören die richtigen Hover-Effekte zu den Dingen, die man im Simulator leicht übersieht, die aber beim tatsächlichen Verwenden des Geräts extrem wichtig sind. Da man Elemente auf dem Gerät mit den Augen auswählt, ist es wichtig, dass deine App Feedback darüber gibt, welches Element gerade ausgewählt ist. Das funktioniert bei String-basierten Control-APIs in SwiftUI wie Button("Click me") { ... } von allein.
Aber sobald du einen eigenen label-Parameter für einen Button bereitstellst oder sogar komplett eigene Controls hast, musst du dem System die genaue Form deines Controls mitteilen. Zum Beispiel verwende ich ein eigenes Control namens HPicker, das ich anstelle des Standard-Dropdown-Picker nutze, wenn ich nur 2–4 Optionen zur Auswahl habe. Es sah beim Hovern über eine Option so aus:

Mein eigener HPicker-View ohne jegliche Hover-Effekt-Anpassungen.
Die Anpassung, damit er der Form der Optionen folgt, war einfach genug:
Button {
// ...
} label: {
Label(option.description, systemImage: option.symbolSystemName)
// ...
.clipShape(.rect(cornerRadius: 12.5))
#if !os(macOS)
.contentShape(.hoverEffect, .rect(cornerRadius: 12.5))
.hoverEffect()
#endif
}Vereinfachte Version der Buttons in meinem HPicker-Komponenten.
Die .contentShape- und .hoverEffect-Modifier sind das, was ich für einen korrekten Hover-Effekt hinzugefügt habe. Ersetze .rect(cornerRadius: 12.5) durch die jeweilige Form deines eigenen Controls. Beachte, dass ich sie in einen #if !os(macOS)-Check eingeschlossen habe, da meine App macOS unterstützt, .hoverEffect dort aber nicht verfügbar ist. Außerdem wichtig: Diese Modifier außerhalb des Button zu platzieren hat bei mir nicht funktioniert – sie müssen innerhalb der Label-Definition stehen, um richtig zu funktionieren.
In manchen Situationen fällt dir vielleicht auf, dass du einen Hover-Effekt hast, wo du keinen erwartest. Bei mir war das der Fall, wenn ich einen Button innerhalb einer anderen View platziert hatte, die selbst schon als Control erkannt wird, wie diese DisclosureGroup:

Die gesamte „Show Clues”-Zeile ist ein Button, aber der Button darin ist ein weiterer Button.
Du kannst den Hover-Effekt abschalten, indem du einfach den .hoverEffectDisabled()-Modifier hinzufügst. In meinem Fall oben war der innere Button nur für macOS vorgesehen (weil eine DisclosureGroup auf dieser Plattform beim Klicken auf das Label nicht umschaltet). Meine Lösung war, nur auf macOS einen Button darin zu verwenden und sonst ein einfaches Label.
Alle Controls mit einem korrekten Hover-Effekt auszustatten war tatsächlich die zeitaufwendigste Aufgabe der Migration und dauerte etwa 40 Minuten. Es wäre wahrscheinlich viel schneller gegangen, wenn SwiftUI-Previews in meinem Projekt funktioniert hätten, aber aus irgendeinem Grund ließen sie sich bei mir nicht bauen, und wenn ich es versuchte, fing mein Mac an zu hängen.
Layoutsystem
Obwohl visionOS auf iPadOS basiert und daher Dinge wie Form-Views ähnlich wie auf dem iPad darstellt, ist es wichtig zu verstehen, dass es einen entscheidenden Unterschied beim Layoutsystem im Vergleich zu iOS/iPadOS gibt:

Das Sichtfeld einer Person in der Apple Vision Pro. Quelle: HIG
Auf Apple Vision werden Apps auf einer unendlichen Leinwand geöffnet – es gibt keine feste Bildschirmbreite oder -höhe, von der deine Views ihre Größe ableiten können. Das ist ein wesentlicher Unterschied, den du verstehen musst. Wenn du bereits Apps für macOS entwickelt hast, ist dir dieser Unterschied schon bekannt. In vieler Hinsicht ist das Layoutsystem dem von macOS viel ähnlicher, wo Monitore ebenfalls unterschiedliche Größen haben können und Fenster nur sehr selten im Vollbildmodus geöffnet werden, wie es auf iOS & iPadOS üblich ist.
Wenn deine App also bereits macOS unterstützt, kannst du einfach die #if os(macOS)-Branches nutzen, die du wahrscheinlich schon zahlreich hast, wenn es um Größenanpassungen oder Fensterverwaltung geht. Ersetze sie einfach durch #if os(macOS) || os(visionOS).
Der Hauptunterschied selbst zu macOS ist, dass Fenster abgerundete Ecken mit einem großen Corner Radius haben. Ich musste daher zusätzliches Padding oben und unten bei meinen Fenster-Root-Views hinzufügen, z. B. mit .padding(.vertical, 10).
Wenn deine App noch nicht für macOS optimiert ist, hier einige wichtige Erkenntnisse:
Du musst überall
.frame(minWidth: 400, minHeight: 300)für deine Views angeben, sonst könnten deine Fenster oder Modals Größen haben, die für deine UI nicht funktionieren. Prüfe sie alle und gib passende Werte an.Obwohl du
minWidthundminHeightfür deine Views angeben solltest, damit Nutzer sie nicht zu klein für deinen Inhalt machen können, möchtest du zusätzlich eine größere.defaultSize(width: 800, height: 600)auf deinerWindowGroup-Scene angeben, um standardmäßig eine größere Größe als das Minimum zu verwenden.Wenn du modale Views hast, die deinen gesamten Bildschirm abdecken und auch diesen Platz brauchen, solltest du erwägen, diese Modals in eigene Fenster auszulagern. Nutze die
@Environment(\.openWindow) var openWindow-Property, um neue Fenster auf visionOS (und macOS) zu öffnen, und definiere zusätzlicheWindowGroup-Views. Lies meinen Artikel über Fensterverwaltung in SwiftUI 4, um mehr zu erfahren.Du kannst dich bei der ersten Migration auch dafür entscheiden, die Modals beizubehalten, anstatt externe Fenster zu verwenden, was die sauberere Lösung wäre. Beachte aber, dass im Gegensatz zu macOS modale Sheets in visionOS nicht in der Größe veränderbar sind. Stelle also zumindest sicher, dass du eine Größe angibst, die gut für dein Sheet funktioniert – für alle Arten von möglicherweise dynamischen Daten, die im Modal angezeigt werden. Genau das habe ich für das „Puzzle spielen” in CrossCraft gemacht.
Farben
Beachte, dass Controls mit weißem Hintergrund nicht gut mit dem Hover-Effekt harmonieren, weil der Effekt ein weißes Overlay verwendet. Weiß auf Weiß ist eben nicht sichtbar. Dieses Problem trat bei meinem Kreuzworträtsel-Spielmodus auf, wo Nutzer auf Kacheln drücken, um Buchstaben einzugeben. Hier ist der Cursor auf einer Kachel, aber der Hover ist nicht sichtbar:

Hovers sind auf weißen Hintergrund-Buttons nicht sichtbar.
Meine schnelle Lösung war, den Modifier .opacity(0.85) hinzuzufügen, um meine weißen Hintergründe 15 % transparent zu machen, was schon geholfen hat. Mehr wäre besser, aber Weiß ist eben eine erwartete „Kreuzworträtsel”-Farbe, also habe ich versucht, es so weiß wie möglich zu halten.

Farben, die auf iOS lesbar sind, funktionieren auf visionOS möglicherweise nicht.
Mir ist auch aufgefallen, dass viele Farben mit mittlerem Kontrast, einschließlich der Standard-„Blue”-Accent Color, einen wirklich schlechten Kontrast auf dem Standard-Glas-Hintergrund des Fensters haben. Stelle sicher, dass du diese Farben für visionOS heller machst. Du kannst eine spezifische Variante für „Apple Vision” über den Attributes Inspector hinzufügen, wenn eine Farbe ausgewählt ist.

Um eine Farbe im HSB-System heller zu machen, musst du die Sättigung leicht verringern und die Helligkeit deutlich erhöhen. Zusätzlich kannst du den Farbton ein wenig in Richtung des nächsten hellen RGB-Werts verschieben. Lies diesen Artikel, um mehr darüber zu erfahren, wie man Farben mit HSB richtig heller/dunkler macht.
Fazit
Wenn du eine App hast, die bereits auf iPadOS & macOS läuft, bist du in einer sehr guten Ausgangslage, um visionOS-Unterstützung hinzuzufügen. Du kannst deinen gesamten SwiftUI-Code wiederverwenden. Bei der Fensterverwaltung solltest du die macOS-Variante wählen. Für alles andere die iPadOS-Variante. Stelle dann sicher, dass alle deine eigenen Controls einen korrekten Hover-Effekt haben. Nimm einige Layout- & UI-Anpassungen vor, wie Padding hinzufügen, Farben heller machen oder dein App-Icon in Front- und Back-Ebene aufteilen.
Der gesamte Prozess hat bei mir effektiv 2 Stunden gedauert. Ich habe den gesamten Vorgang live gestreamt – du findest meine Aufnahmen (ohne die „Warten auf Build”- und „Chat”-Phasen) in den folgenden zwei YouTube-Videos, jedes etwa eine Stunde lang. Ich habe Zeitmarken für die verschiedenen oben beschriebenen Schritte hinzugefügt, damit du gezielt in Einzelheiten eintauchen kannst:

