In juni 2023, toen Apple de Vision Pro aankondigde, had ik een idee dat goed zou kunnen werken op de headset: een verzameling geloopte video's die worden afgespeeld naast je dagelijkse gebruik. Ik had al een app die dit kon doen, genaamd Christmas Chill , iets dat ik bouwde toen de eerste Apple TV met een App Store beschikbaar kwam. Het bevat een verzameling geloopte video's die je kunt gebruiken als feestelijke achtergrond.
Elk jaar in de winter besteed ik een paar dagen aan het verbeteren ervan, het toevoegen van nieuwe content en het verbeteren van de codebase. Een van de grotere veranderingen die aan het project zijn doorgevoerd, vond plaats in december 2023, toen de UI werd gemigreerd van UIKit en Storyboards naar SwiftUI.
Nou, grotendeels gemigreerd. Ik had een AVPlayer-backed view nodig, verpakt in een UIViewRepresentable . Een geweldige API die interoperabiliteit biedt tussen UIKit en SwiftUI, mocht je dat ooit nodig hebben.
Ik aarzelde enigszins om eerder te migreren, omdat ik Declarative UI en de concepten ervan goed begreep door ander werk met React en Jetpack Compose . Dat veranderde voor mij met de introductie van Apple Vision Pro en de ondersteuning voor SwiftUI. Christmas Chill was een leuk project om mijn Apple Dev-kennis up-to-date te houden en ik wilde graag ervaring opdoen met het uitbreiden van de app naar verschillende apparaten.
Nadat de migratie naar SwiftUI voor Christmas Chill in 2023 was voltooid, besloot ik in 2024 ondersteuning voor de Vision Pro toe te voegen. Hieronder staat hoe ik dat heb gedaan en wat ik aanbeveel als je hetzelfde wilt doen voor je eigen apps.
Om te beginnen moet het project kunnen bouwen voor Vision Pro als bestemming; dit is verrassend eenvoudig! Selecteer in Xcode het bestand .xcodeproj
en klik onder de vervolgkeuzelijst Ondersteunde bestemmingen op de plusknop.
Er verschijnt een dropdown met alle beschikbare Apple-platforms. Beweeg de muis over het gewenste platform dat u als bestemming wilt toevoegen, in dit geval Apple Vision, en klik vervolgens op Apple Vision in de nieuw verschijnende sectie.
Er verschijnt een kleine pop-up om u te informeren over de wijzigingen die Xcode moet aanbrengen in het doel. Klik op Inschakelen .
Bouw vervolgens de app met de visionOS Simulator. Als u een Vision Pro bij de hand hebt, kunt u hier instructies vinden over hoe u deze op uw apparaat installeert.
Tijdens het compileren is het waarschijnlijk dat Xcode compilerfouten vindt en/of de app crasht. Dit is te verwachten en een oefening in geduld. Vanaf dit punt moet u de fouten in uw project oplossen totdat de app compileert en niet langer crasht.
In mijn geval duurde dit ongeveer 30 minuten, deels omdat ik eerder al de moeite had genomen om de app van UIKit naar SwiftUI te migreren!
SwiftUI is in de kern een multiplatform-framework, wat betekent dat alleen al door SwiftUI-code te compileren voor een ander platform, het uiterlijk verandert. Rekening houdend met platformstijl en verschillende interactiemethoden.
Hoewel dit helpt om snel vooruitgang te boeken tijdens de ontwikkeling, wilt u misschien meer controle over hoe de app eruitziet en profiteren van de sterke punten van elk afzonderlijk platform. Een goed voorbeeld zijn de Immersion-mogelijkheden van Vision Pro; SwiftUI biedt hiervoor een API via ImmersiveSpace , een API die alleen beschikbaar is voor visionOS.
Als u deze API probeert te gebruiken tijdens het compileren van het project voor Apple TV, geeft Xcode een foutmelding met de melding dat deze API niet beschikbaar is.
Dus, wat is de oplossing om deze situatie te vermijden? Het antwoord komt van het gebruik van Conditional Compilation Blocks . Compilation Blocks zijn delen van code die instructies geven voor wanneer de compiler code binnen het blok moet compileren.
Hoewel ze een verscheidenheid aan condities ondersteunen, is het meest nuttige voor onze behoeften het detecteren voor welk platform de code wordt gecompileerd. U kunt dit doen met slechts een paar regels code:
var body: some Scene { #if os(tvOS) WindowGroup { HStack { Text("I am running on tvOS!") } } #elseif os(visionOS) ImmersiveSpace(id: "MyImmersiveSpace") { } #endif }
Een leuke feature die Xcode heeft om conditionele compilatieblokken te ondersteunen is om duidelijk te maken welke code gecompileerd zal worden, afhankelijk van het platform dat geselecteerd is voor compilatie. Het zal ook code die niet gecompileerd zal worden enigszins vervagen.
Een van de handigste trucs die ik heb gevonden, is het gebruiken van de buildfases Compile Sources en Copy Bundle Resources als een vorm van dependency injection. Deze processen worden uitgevoerd wanneer de app wordt gebouwd en zijn te vinden onder het tabblad Build Phases in het Xcode Project.
Compile Sources doet het zware werk van het compileren van uw broncode naar machinecode. Of het nu Swift, Objective-C of zelfs C/C++ is.
Copy Bundle Resources kopieert alle gerelateerde resources voor het app-doel naar de App Bundle . Een soort container voor alle code en resources van de app, inclusief afbeeldingen, video's, lokaliseerbare strings en meer.
Deze twee bouwfases geven apps veel flexibiliteit, omdat elk nieuw doel zijn eigen bouwfases biedt, inclusief de twee bovenstaande stappen. Whitelabel-apps die bedrijven een manier bieden om hun content aan te passen, gebruiken deze techniek, naast andere.
Misschien wilt u verschillende content voor uw eigen apps leveren, afhankelijk van het platform waarop ze draaien. Laten we deze bouwfases in ons voordeel gebruiken en twee verschillende bronnen van content leveren om dat te doen.
Laten we eerst eenSwift-protocol gebruiken om een contract te bieden dat moet worden uitgevoerd door een struct of klasse.
protocol ContentManager { var content: [Content] { get } }
Laten we nu eens kijken naar twee implementers van het protocol. Hier is de eerste:
class TargetAppAContentManager : ContentManager { var content: [Content] { return [ Content(name: TargetAppAContentIdentifier.videoOneName.rawValue, image: TargetAppAImagePreviewIdentifier.videoOnePreview.rawValue, video: TargetAppAImageVideoIdentifier.videoOneVideo.rawValue), Content(name: TargetAppAContentIdentifier.videoTwoName.rawValue, image: TargetAppAImagePreviewIdentifier.videoTwoPreview.rawValue, video: TargetAppAImageVideoIdentifier.videoTwoVideo.rawValue), Content(name: TargetAppAContentIdentifier.videoThreeName.rawValue, image: TargetAppAImagePreviewIdentifier.videoThreePreview.rawValue, video: TargetAppAImageVideoIdentifier.videoThreeVideo.rawValue), ] return contentToShow } }
TargetAppAContentManager
is de concrete implementatie die wordt gebruikt voor het eerste app-doel. Het biedt een array van Content
, die verwijst naar resourcenamen die in de app-bundel voor het doel zijn gevonden.
class TargetAppBContentManager : ContentManager { var content: [Content] { return [ Content(name: TargetAppBContentIdentifier.videoOneName.rawValue, image: TargetAppBImagePreviewIdentifier.videoOnePreview.rawValue, video: TargetAppBImageVideoIdentifier.videoOneVideo.rawValue), Content(name: TargetAppBContentIdentifier.videoTwoName.rawValue, image: TargetAppBImagePreviewIdentifier.videoTwoPreview.rawValue, video: TargetAppBImageVideoIdentifier.videoTwoVideo.rawValue), Content(name: TargetAppBContentIdentifier.videoThreeName.rawValue, image: TargetAppBImagePreviewIdentifier.videoThreePreview.rawValue, video: TargetAppBImageVideoIdentifier.videoThreeVideo.rawValue), ] } }
De volgende is TargetAppBContentManager
, de concrete implementatie die is gebruikt voor het tweede app-doel. Het lijkt erg op de eerste implementatie, behalve dat de identifiers voor App B anders zijn.
Nu beide implementaties zijn gemaakt, kunt u er indirect naar verwijzen in uw code door het type van het object in te stellen op ContentManager
. Bekijk het voorbeeld ViewModel hieronder:
@Observable class VideoListViewModel { var contentManager: ContentManager init(contentManager: ContentManager) { self.contentManager = contentManager } }
De ViewModel verwacht dat een type ContentManager
via zijn initializer wordt doorgegeven. De ViewModel kan door beide typen ContentManager
worden doorgegeven en blijft functioneren zoals verwacht. Dit betekent ook dat de ViewModel kan worden hergebruikt in beide app-doelen.
Het laatste wat u moet doen, is ervoor zorgen dat de juiste ContentManager wordt toegevoegd aan de fase Bronnen compileren. In dit geval wordt aan App A TargetAppAContentMananger
doorgegeven als onderdeel van de bronnen en aan App B TargetAppBContentManager
.
Het laatste wat u nog moet doen, is ervoor zorgen dat elke app Bundle resources bevat met namen die overeenkomen met de identifiers die door de app worden gebruikt. De makkelijkste manier is om de Copy Bundle Resources
build-fase van elk app-doel te controleren en ervoor te zorgen dat de content manager naar de resources verwijst. Als dat niet het geval is, sleept u ze van uw Xcode-project naar de copy resources-fase.
Dit kost wat tijd en zorg om te testen, omdat je geen compile-time error krijgt als een resource waarnaar wordt verwezen niet beschikbaar is in de bundel. Tijdens runtime krijg je een crash!
Een goede manier om de controle te automatiseren is om een unit test te schrijven om te bevestigen dat alle resources waarnaar door ContentManager
wordt verwezen, in de bundel zijn opgeslagen. Als de test mislukt wanneer deze wordt uitgevoerd, weet u dat er een resource in de bundel ontbreekt.
Als je tot hier gekomen bent, heb je waarschijnlijk een goed idee hoe je jouw app naar andere Apple-platforms kunt brengen.
Ter afsluiting van dit bericht geef ik u nog een paar tips en bronnen die ik u kan aanbevelen:
Als u Apple Vision-ondersteuning toevoegt aan een bestaande app, migreer dan eerst zoveel mogelijk van uw code van UIKit naar SwiftUI. Nu u de snelheid van een bestaande app op Vision Pro hebt gezien die is gemigreerd naar SwiftUI, is het handig om erop te kunnen vertrouwen.
Lees Apple's richtlijnen over het overbrengen van bestaande apps naar visionOS . Het biedt nuttige tips en suggesties over hoe u dit kunt doen en hoe u kunt profiteren van visionOS-functies.
Als je erover denkt om zelf een nieuwe multiplatform-app te starten, is er een Multiplatform-tab beschikbaar in Xcode, die een aantal app-sjablonen biedt om te gebruiken. Er is ook een video van WWDC 2022 over het onderwerp.
Als je voorbeelden wilt zien van apps die op meerdere platforms werken, raad ik je aan om mijn persoonlijke apps Christmas Chill en Ocean Chill te bekijken. Dit zijn twee apps die op tvOS en Vision Pro werken, gebouwd vanuit één codebase. (tvOS-ondersteuning voor Ocean Chill komt eraan!)