Bijgewerkt: 20 september 2026
Om een .NET MAUI iOS app te publiceren naar de App Store heb je in 2026 een Mac met Xcode 16.4 of hoger, een actief Apple Developer Program-lidmaatschap ($99/jaar), een Apple Distribution-certificaat, een App Store-provisioning profile dat gekoppeld is aan je Bundle Identifier, en een dotnet publish-configuratie die deze drie zaken samenbrengt in een ondertekende .ipa. Daarna upload je met Transporter of xcrun altool naar App Store Connect, waarna TestFlight of de reguliere App Store-review het overneemt.
Vereisten voor iOS-publicatie in 2026
Ik ga hier geen doekjes om winden: je hebt een Mac nodig. Punt. Cross-platform betekent dat je code op Windows en Linux kan draaien, maar Apple's toolchain (codesign, xcrun, actool, ibtool) draait alleen op macOS. Je kunt via Visual Studio 2022 op Windows koppelen met een Mac Build Host, maar de Mac zelf blijft essentieel. Ik heb ooit een paar weken geprobeerd om dat te omzeilen met een cloud-CI zonder Mac-runner. Zonde van de tijd.
In 2026 zijn de minimale vereisten:
- macOS Sonoma 14.5 of hoger (Xcode 16 draait niet op oudere versies).
- Xcode 16.4+. Apple's SDK-vereisten voor App Store-inzendingen schrijven sinds april 2025 iOS 18 SDK voor.
- .NET SDK 9.0.100+ of 10.0.100+, met de workload
maui-iosgeïnstalleerd viadotnet workload install maui. - Apple Developer Program-lidmaatschap ($99/jaar voor individuen, $299/jaar voor Enterprise, wat je waarschijnlijk niet nodig hebt).
- Een geregistreerde Bundle Identifier in App Store Connect, bijvoorbeeld
com.jouwbedrijf.jouwapp.
Ben je een Xamarin.iOS-veteraan? Goed nieuws: de MSBuild-eigenschappen die je kent, werken grotendeels ongewijzigd. De handlers-architectuur van MAUI heeft het model rond native views veranderd, maar de deployment-pipeline is bewust conservatief gehouden.
Certificaten en provisioning profiles opzetten
Apple's signing-systeem is (eerlijk gezegd) een bron van gebruikersleed, en de MAUI-documentatie glijdt er soms overheen. Wat er onder de motorkap echt gebeurt: iedere ondertekende .ipa heeft twee onlosmakelijke onderdelen. Een certificaat (bewijst wie jij bent) en een provisioning profile (bewijst wat je app mag doen op welke apparaten). Zo simpel, en toch zo vaak fout.
Certificaat aanmaken
- Open Xcode, ga naar Settings → Accounts, meld je Apple ID aan en selecteer je team.
- Klik op Manage Certificates en voeg een Apple Distribution-certificaat toe. Voor App Store-uploads heb je géén losse "Mac Installer" nodig; dat is voor Mac Catalyst.
- Xcode maakt de private key aan in je login-keychain. Exporteer die naar een
.p12-bestand als je op meerdere machines of via CI wilt bouwen. Anders kun je nooit meer bij hetzelfde certificaat, en dat is een pijnlijke les om zelf op te doen.
App ID en provisioning profile
- Log in op Apple Developer's Identifiers-portal en registreer een expliciete App ID (bijv.
com.jouwbedrijf.jouwapp). Vermijd wildcard-IDs (com.jouwbedrijf.*); die ondersteunen geen push notifications, geen App Groups, en geen In-App Purchase. - Schakel de capabilities in die je app nodig heeft: Push Notifications, Sign in with Apple, In-App Purchase, HealthKit. Alles wat niet standaard aan staat, moet je expliciet aanzetten.
- Onder Profiles, maak een nieuw profile van het type App Store (dus niet Ad Hoc en niet Development). Koppel het aan je App ID en je Distribution-certificaat.
- Download het
.mobileprovision-bestand en dubbelklik erop. Het wordt automatisch geïnstalleerd in~/Library/MobileDevice/Provisioning Profiles/.
MSBuild-configuratie voor code signing
MAUI iOS-projecten leunen zwaar op MSBuild-eigenschappen. Je kunt ze in je .csproj zetten, of via -p:-argumenten op de commandline meegeven. Voor productie raad ik een conditional PropertyGroup aan, zodat je Debug-builds niet per ongeluk ondertekent met je distribution-certificaat:
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)'=='Release|net9.0-ios'">
<RuntimeIdentifier>ios-arm64</RuntimeIdentifier>
<CodesignKey>Apple Distribution: Jouw Bedrijf B.V. (ABCDE12345)</CodesignKey>
<CodesignProvision>JouwApp App Store Profile</CodesignProvision>
<ArchiveOnBuild>true</ArchiveOnBuild>
<TcpPort>58181</TcpPort>
<ServerAddress>127.0.0.1</ServerAddress>
<MtouchLink>SdkOnly</MtouchLink>
<MtouchUseLlvm>true</MtouchUseLlvm>
</PropertyGroup>Een paar dingen die vaak over het hoofd worden gezien:
CodesignKeymoet exact matchen met de naam van het certificaat zoals die in Keychain Access staat, inclusief het team-ID tussen haakjes. Kopieer de naam letterlijk. Spaties tellen.CodesignProvisionis de naam van het profile (zoals ingevoerd in de developer portal), niet de bestandsnaam en niet de UUID.ArchiveOnBuild=truezorgt dat na de build automatisch een.xcarchivewordt gemaakt in~/Library/Developer/Xcode/Archives/. Handig voor debugging in Xcode's Organizer.RuntimeIdentifier=ios-arm64is verplicht sinds .NET 8; Apple accepteert geen x86- of universal binaries meer voor iOS-inzendingen.
Voor .NET MAUI 10 kun je bovendien <PublishAot>true</PublishAot> overwegen. AOT-compilatie levert kleinere binaries en snellere opstart op, maar breekt reflection-heavy code. Profileer je app dus vooraf.
Bouwen met dotnet publish
Nu de configuratie staat, is het bouwen zelf verrassend recht-toe-recht-aan. Vanuit de root van je MAUI-project:
dotnet publish -f net9.0-ios \
-c Release \
-p:RuntimeIdentifier=ios-arm64 \
-p:ArchiveOnBuild=true \
-p:CodesignKey="Apple Distribution: Jouw Bedrijf B.V. (ABCDE12345)" \
-p:CodesignProvision="JouwApp App Store Profile"Als alles goed gaat, verschijnt er een .ipa in bin/Release/net9.0-ios/ios-arm64/publish/. Verifieer voordat je uploadt:
# Controleer signing
codesign --verify --deep --strict --verbose=2 JouwApp.app
# Bekijk het ingebedde provisioning profile
security cms -D -i JouwApp.app/embedded.mobileprovision
# Bevestig entitlements
codesign -d --entitlements :- JouwApp.appAls codesign --verify een foutmelding als "code object is not signed at all" geeft voor een framework, betekent dat meestal dat een NuGet-package een bundle resource bevat die MSBuild niet automatisch meeneemt in de signing-pass. Voeg <CodesignExtraArgs>--deep</CodesignExtraArgs> toe of, beter, rapporteer het als bug bij de packagemaker. Ik heb dit exacte probleem gehad met een oudere SkiaSharp-versie in productie, en de fix zat uiteindelijk in een package-update.
Uploaden naar App Store Connect
Er zijn drie manieren om een .ipa naar App Store Connect te sturen. Kies er één op basis van je context:
1. Transporter (GUI, aanbevolen voor beginners)
Download Transporter uit de Mac App Store, meld je aan met je Apple ID en sleep de .ipa. Transporter valideert het pakket, toont ITMS-fouten in klare taal en handelt de upload af. Voor eenmalige releases is dit de minst pijnlijke route.
2. xcrun altool (commandline, voor CI)
xcrun altool --upload-app \
-f bin/Release/net9.0-ios/ios-arm64/publish/JouwApp.ipa \
-t ios \
--apiKey ABCDE12345 \
--apiIssuer 12345678-abcd-...Merk op dat altool sinds Xcode 15 is vervangen door xcrun notarytool voor macOS-apps, maar voor iOS blijft altool voorlopig ondersteund. Genereer een App Store Connect API-key via Users and Access → Keys. De key wordt éénmalig getoond, dus sla die veilig op (BitWarden, 1Password, whatever je gebruikt).
3. Fastlane (voor teams met veel releases)
Fastlane's pilot en deliver actions blijven de gouden standaard voor grote teams. Het is een extra afhankelijkheid, maar het idempotent maken van releases (inclusief screenshots, metadata en TestFlight-notes) is de investering meestal waard. In mijn laatste project bespaarde het letterlijk uren per release.
TestFlight instellen voor beta-distributie
Zodra je build verwerkt is in App Store Connect (5 tot 15 minuten voor kleinere apps), verschijnt er een tabblad TestFlight naast App Store. Hier scheid je twee doelgroepen:
- Interne testers: leden van je App Store Connect team, tot 100 personen op maximaal 30 apparaten elk. Geen Beta App Review nodig; builds zijn direct beschikbaar.
- Externe testers: tot 10.000 e-mailadressen of een publieke uitnodigingslink. Vereist een Beta App Review (meestal 24 tot 48 uur voor de eerste build; snellere reviews voor daaropvolgende builds die geen features toevoegen).
Praktische zaken die in de docs onderbelicht blijven:
- TestFlight-builds vervallen na 90 dagen. Plan je release-cadans daaromheen. Niets is zo vervelend als een test die stukloopt op een verlopen build.
- Elke build vereist What to test-notities (max. 4.000 tekens). Testers zien deze in de TestFlight-app; leeg laten is een gemiste kans om bugreports gerichter te maken.
- Voor Encryption Export Compliance moet je aangeven of je app cryptografie gebruikt boven wat iOS zelf al levert. HTTPS via
NSURLSessionvalt onder de standaardexemptie; je eigen crypto niet.
App Store-review en release
De reguliere App Store-review is een ander beest dan TestFlight's beta-review. Verwacht 24 tot 72 uur voor de eerste inzending, korter voor updates. Wat de reviewers in 2026 het scherpst controleren:
- Purpose strings in
Info.plistvoor elke privacygevoelige API die je aanroept. Camera, foto's, locatie, microfoon, contacten, Bluetooth: allemaal. Ontbrekende strings zijn de nummer-één afwijzingsgrond. - App Tracking Transparency (ATT). Als je iets doet dat lijkt op tracking over apps of websites heen, moet je
NSUserTrackingUsageDescriptionzetten énATTrackingManager.requestTrackingAuthorizationaanroepen vóór je enige tracking-ID gebruikt. - Sign in with Apple. Als je een third-party social login aanbiedt (Google, Facebook), verplicht Apple je om ook Sign in with Apple aan te bieden. Uitgezonderd zijn apps die alleen een eigen accountsysteem gebruiken.
- In-App Purchase-integratie. Digitale content mag alleen via StoreKit worden verkocht. Externe betaalflows zijn na de EU DMA-uitspraak in bepaalde regio's toegestaan, maar buiten de EU nog steeds reden voor afwijzing.
Zodra je build is goedgekeurd, kies je tussen Manual release, Automatic release en Phased release. Voor grote user bases raad ik de zeven-daagse phased release aan; je kunt afbreken als je crash rates ziet pieken. Gebruik parallel je CollectionView-performance-instrumentatie om na de release regressies vroeg te vangen.
Veelvoorkomende fouten en oplossingen
Hier is de lijst met foutmeldingen die iedereen minstens één keer tegenkomt. Bewaar hem. Je zult hem opnieuw nodig hebben, geloof me.
| Foutmelding | Oorzaak | Oplossing |
|---|---|---|
| ITMS-90683: Missing Purpose String | Je code refereert naar een privacygevoelige API zonder NSXxxUsageDescription. | Voeg de gevraagde key toe aan Platforms/iOS/Info.plist. Zoek in je binary met strings JouwApp.app/JouwApp | grep CFBundle naar frameworkverwijzingen. |
| ITMS-90062: This bundle is invalid | Versienummer (CFBundleShortVersionString) is lager dan of gelijk aan een vorige upload. | Verhoog ApplicationDisplayVersion in je .csproj. Voor patches: ApplicationVersion-int met 1 verhogen. |
| Code Signing "No matching provisioning profile found" | De CodesignProvision-naam matcht niet met een geïnstalleerd profile. | Draai security find-identity -v -p codesigning en ls ~/Library/MobileDevice/Provisioning\ Profiles/. Controleer dat namen exact overeenkomen. |
| Cannot verify the signature | De private key voor je Distribution-certificaat ontbreekt op de buildmachine. | Exporteer de .p12 vanuit je oorspronkelijke Mac en importeer op de nieuwe. Zonder key kun je niet ondertekenen, hoe zwak dat ook voelt. |
| ITMS-90209: Invalid Segment Alignment | Native library in NuGet is niet gebouwd met correcte 16 KB page alignment. | Update de NuGet-package. Sinds november 2025 vereist Apple 16 KB memory pages voor iOS 18-inzendingen. |
Ontwikkelaars die van Xamarin.iOS migreren merken vaak dat hun oude signing-scripts nog werken, maar de MSBuild-property namen zijn subtiel gewijzigd. CodesignKey heette in het Xamarin-tijdperk óók CodesignKey, dus dat is safe; MtouchArch is echter vervangen door RuntimeIdentifiers. Als je nog steeds MTouch-eigenschappen in je csproj hebt staan, verwijder ze. MAUI negeert ze grotendeels, wat verwarrende resultaten oplevert.
Veelgestelde vragen
Kan ik een .NET MAUI iOS-app publiceren zonder een Mac?
Nee, niet volledig. Je kunt vanuit Visual Studio op Windows koppelen met een remote Mac Build Host (bijvoorbeeld een MacStadium- of Scaleway-instance), maar de daadwerkelijke codesign- en Xcode-toolchain draait alleen op macOS. In de praktijk betekent dat: minimaal één Mac in de pipeline.
Hoe lang duurt de App Store-review in 2026?
Volgens Apple's eigen statistieken wordt 90% van de inzendingen binnen 24 uur beoordeeld. Reken op 24 tot 48 uur voor eerste inzendingen en enkele uren tot een dag voor updates. Feestdagen (Kerstmis, WWDC-week) verlengen de wachttijden merkbaar.
Wat is het verschil tussen TestFlight en Ad Hoc-distributie?
TestFlight distribueert via App Store Connect naar tot 10.000 externe testers zonder dat je hun UDIDs hoeft te registreren. Ad Hoc-distributie vereist een provisioning profile met specifieke UDIDs (max. 100 per apparaattype per jaar) en werkt zonder review, maar schaalt slecht. Voor beta's is TestFlight vrijwel altijd de betere keuze.
Waarom faalt mijn upload met ITMS-90714?
ITMS-90714 betekent dat je Info.plist een UIRequiredDeviceCapabilities-vermelding heeft die niet matcht met je architectuur. Sinds .NET 8 bouwt MAUI standaard ios-arm64; als je armv7 of oudere waarden in je plist hebt staan, verwijder ze. Apple accepteert alleen nog 64-bit sinds 2017.
Moet ik AOT-compilatie inschakelen voor productie-iOS-builds?
iOS vereist AOT: Apple staat geen JIT-compilatie toe buiten Safari's WebKit. MAUI schakelt AOT automatisch in voor Release-builds naar net9.0-ios/net10.0-ios. De optionele <PublishAot>true</PublishAot>-vlag activeert de nieuwere Native AOT-pijplijn, wat betere startup geeft maar reflection en dynamische code kan breken.




