<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Dan Ardelean</title>
    <description>The latest articles on DEV Community by Dan Ardelean (@danardelean).</description>
    <link>https://dev.to/danardelean</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1055631%2F33e901c6-da26-458b-b9b9-63128d4072ff.jpg</url>
      <title>DEV Community: Dan Ardelean</title>
      <link>https://dev.to/danardelean</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/danardelean"/>
    <language>en</language>
    <item>
      <title>Run the next .NET today, from a folder in your repo, with nothing installed system-wide</title>
      <dc:creator>Dan Ardelean</dc:creator>
      <pubDate>Fri, 09 Oct 2026 16:46:10 +0000</pubDate>
      <link>https://dev.to/danardelean/run-the-next-net-today-from-a-folder-in-your-repo-with-nothing-installed-system-wide-26pb</link>
      <guid>https://dev.to/danardelean/run-the-next-net-today-from-a-folder-in-your-repo-with-nothing-installed-system-wide-26pb</guid>
      <description>&lt;p&gt;You do not have to install a .NET preview system-wide to try it. Since the .NET 10 SDK, &lt;code&gt;global.json&lt;/code&gt; can point &lt;code&gt;dotnet&lt;/code&gt; at an SDK that lives in a folder of your repository (the &lt;code&gt;paths&lt;/code&gt; key, &lt;a href="https://learn.microsoft.com/dotnet/core/tools/global-json#paths" rel="noopener noreferrer"&gt;documented in the global.json reference&lt;/a&gt;), and &lt;code&gt;dotnet workload install&lt;/code&gt; run from that folder puts the workloads there too. Nothing lands in &lt;code&gt;/usr/local/share/dotnet&lt;/code&gt;, no &lt;code&gt;sudo&lt;/code&gt;, no wondering which SDK your real projects are on. Outside the repository nothing changes. Delete the folder and the preview was never there.&lt;/p&gt;

&lt;p&gt;I found this when I needed the .NET 11 RC2 nightlies: I had macOS 27 and Xcode 27 before RC2 was out, RC1 could not build iOS any more, and I had to keep testing &lt;a href="https://github.com/danardelean/Maui.NativeStyles" rel="noopener noreferrer"&gt;Maui.NativeStyles&lt;/a&gt;, the library I am working on. The dotnet/maui and dotnet/macios repositories work this way every day, and almost nobody else seems to. So I wrote &lt;a href="https://github.com/danardelean/dotnet-nightly-local" rel="noopener noreferrer"&gt;dotnet-nightly-local&lt;/a&gt;, a script that does it for any channel and any workload (&lt;code&gt;install.sh&lt;/code&gt; for macOS and Linux, &lt;code&gt;install.ps1&lt;/code&gt; for PowerShell 7 anywhere, tests for both), and this post is one pass through it: which channels exist, what each one offers, install, build, update, clean. Every number comes from runs on October 9, 2026.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-sSL&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; ~/install.sh https://raw.githubusercontent.com/danardelean/dotnet-nightly-local/main/install.sh
&lt;span class="nb"&gt;chmod&lt;/span&gt; +x ~/install.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  1. The channels
&lt;/h2&gt;

&lt;p&gt;A &lt;em&gt;channel&lt;/em&gt; is the name of a build lane of &lt;a href="https://github.com/dotnet/dotnet" rel="noopener noreferrer"&gt;dotnet/dotnet&lt;/a&gt;: &lt;code&gt;12.0.1xx&lt;/code&gt; is &lt;code&gt;main&lt;/code&gt;, already branded as .NET 12; &lt;code&gt;11.0.1xx-rc2&lt;/code&gt; the RC2 release branch of .NET 11; &lt;code&gt;11.0.1xx&lt;/code&gt; its RTM branch. &lt;code&gt;--list-channels&lt;/code&gt; probes every channel name the .NET build system declares and prints today's SDK version, which is the column to read: the official &lt;a href="https://github.com/dotnet/dotnet/blob/main/docs/builds-table.md" rel="noopener noreferrer"&gt;builds table&lt;/a&gt; lags, and still calls &lt;code&gt;11.0.1xx&lt;/code&gt; "main". Abridged:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ ~/install.sh --list-channels
channel              current daily SDK
11.0.1xx             11.0.100-rtm.26480.113
11.0.1xx-rc2         11.0.100-rc.2.26473.112
12.0.1xx             12.0.100-alpha.1.26509.101
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So today there are two things to try: the next version, the .NET 12 alpha, and the nightly builds of the current one, .NET 11 RC2, which is the lane built against Xcode 27 and is due in a few days. When &lt;code&gt;13.0.1xx&lt;/code&gt; appears, nothing below changes but the name.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. The workloads of each channel
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;--list-workloads&lt;/code&gt; resolves the channel's SDK version with one HEAD request, looks for every workload manifest of that version's band on the feeds an install would use, and prints the workload ids each manifest defines. It downloads nothing but the manifests, a few kilobytes each, and takes about a minute. The .NET 12 lane:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ ~/install.sh --list-workloads --channel 12.0.1xx
SDK 12.0.100-alpha.1.26509.101 (band 12.0.100-alpha.1), channel 12.0.1xx
Feeds:
  (from dotnet/maui NuGet.config: main net11.0 release/11.0.1xx-rc2)
  ...
Manifests (12.0.100-alpha.1) and the workloads each defines:
  microsoft.net.sdk.android: 37.99.0-preview.1.135  (dotnet12)
      android
  microsoft.net.sdk.ios: 27.0.12277-net12-p1  (dotnet12)
      ios
  ...
  microsoft.net.workload.mono.toolchain.current: 12.0.100-alpha.1.26509.101  (dotnet12)
      mobile-librarybuilder wasi-experimental wasm-experimental wasm-tools
  microsoft.net.workload.mono.toolchain.net11: 12.0.100-alpha.1.26509.101  (dotnet12)
      mobile-librarybuilder-net11 wasi-experimental-net11 wasm-experimental-net11 wasm-tools-net11
  ...
Not on any of the feeds for this band: microsoft.net.sdk.maui
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each pair is a manifest (the package that describes one family of workloads) with the build the script would install and the feed it comes from, then the workload ids you can pass to &lt;code&gt;--workloads&lt;/code&gt;. A manifest whose workloads are all abstract (the emscripten ones, pulled in by &lt;code&gt;wasm-tools&lt;/code&gt;) says so. The last line names the usual manifests no feed has for this band: &lt;code&gt;maui&lt;/code&gt;, because dotnet/maui has not moved to .NET 12 yet. The other two lanes, in one line each:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;11.0.1xx-rc2&lt;/code&gt;: all six SDK manifests. &lt;code&gt;maui 11.0.0-rc.2.26505.5&lt;/code&gt; defines &lt;code&gt;maui maui-android maui-desktop maui-ios maui-maccatalyst maui-mobile maui-tizen maui-windows&lt;/code&gt;; iOS is &lt;code&gt;27.0.12212-net11-rc.2&lt;/code&gt;, built for Xcode 27.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;11.0.1xx&lt;/code&gt; (RTM): &lt;code&gt;maui&lt;/code&gt; and &lt;code&gt;android&lt;/code&gt; only. No RTM manifest for iOS, Mac Catalyst, macOS or tvOS is public yet, so the lane cannot build for Apple platforms today: &lt;code&gt;--workloads ios&lt;/code&gt; is skipped, and &lt;code&gt;maui&lt;/code&gt; comes with the SDK's bundled Preview 6 iOS (Xcode 26). The listing tells you before you spend the download.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  3. Install
&lt;/h2&gt;

&lt;p&gt;The next version, with the two workloads it has:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir &lt;/span&gt;net12 &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cd &lt;/span&gt;net12
~/install.sh &lt;span class="nt"&gt;--channel&lt;/span&gt; 12.0.1xx &lt;span class="nt"&gt;--workloads&lt;/span&gt; ios,android,mobile-librarybuilder-net11
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SDK 12.0.100-alpha.1.26509.101 (band 12.0.100-alpha.1) in /Users/dan/net12/.dotnet
Feeds:
  (from dotnet/maui NuGet.config: main net11.0 release/11.0.1xx-rc2)
  ...
Manifests (12.0.100-alpha.1):
  microsoft.net.sdk.android: 37.99.0-preview.1.135  (dotnet12)
  microsoft.net.sdk.ios: 27.0.12277-net12-p1  (dotnet12)
  ...
Successfully installed workload(s) ios android mobile-librarybuilder-net11.
global.json: SDK 12.0.100-alpha.1.26509.101 from .dotnet
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three and a half minutes on my connection, 7 GB in &lt;code&gt;./net12/.dotnet&lt;/code&gt; (the SDK and its runtime alone are about 430 MB; the rest is packs), nothing anywhere else. The third workload is the alpha being an alpha: its templates still create &lt;code&gt;net11.0-ios&lt;/code&gt; and &lt;code&gt;net11.0-android&lt;/code&gt; projects, and such a project needs the .NET 11 build of the &lt;code&gt;mono.toolchain&lt;/code&gt; packs that &lt;code&gt;ios&lt;/code&gt; and &lt;code&gt;android&lt;/code&gt; do not pull in, the MonoTargets build tasks and the Mono AOT cross-compilers for iOS. The build names the workload that has them, &lt;code&gt;mobile-librarybuilder-net11&lt;/code&gt;. (The manifest's name is historical: Android apps run on CoreCLR since .NET for Android 37 and its workload ships no Mono runtime any more; iOS apps still run on Mono, with NativeAOT as the option.) Ask for &lt;code&gt;maui&lt;/code&gt; here and the script skips it with a message, since no manifest of the band defines it.&lt;/p&gt;

&lt;p&gt;The nightly builds of the current version, same script, different channel, and &lt;code&gt;maui&lt;/code&gt; exists:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir &lt;/span&gt;net11 &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cd &lt;/span&gt;net11
~/install.sh &lt;span class="nt"&gt;--channel&lt;/span&gt; 11.0.1xx-rc2 &lt;span class="nt"&gt;--workloads&lt;/span&gt; maui
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Inside either folder the plain &lt;code&gt;dotnet&lt;/code&gt; is the one in &lt;code&gt;.dotnet&lt;/code&gt;; outside, it is whatever was on the machine:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;dotnet &lt;span class="nt"&gt;--version&lt;/span&gt;          &lt;span class="c"&gt;# in net12&lt;/span&gt;
12.0.100-alpha.1.26509.101
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; ~ &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; dotnet &lt;span class="nt"&gt;--version&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
10.0.401
&lt;span class="nv"&gt;$ &lt;/span&gt;./.dotnet/dotnet workload list
android                          37.99.0-preview.1.135/12.0.100-alpha.1
ios                              27.0.12277-net12-p1/12.0.100-alpha.1
mobile-librarybuilder-net11      12.0.100-alpha.1.26509.101/12.0.100-alpha.1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The workload commands have to go through the folder's own &lt;code&gt;dotnet&lt;/code&gt;: run through the system one they look at the system install, where the nightly band does not exist, and print an empty table. Builds do not care, the MSBuild resolver finds the folder from the SDK directory.&lt;/p&gt;

&lt;p&gt;One more file. Projects built with a nightly restore runtime packages of the same nightly version, which are on the &lt;code&gt;dotnet12&lt;/code&gt; (or &lt;code&gt;dotnet11&lt;/code&gt;) feed and not on nuget.org, so give them a &lt;code&gt;NuGet.config&lt;/code&gt; next to the project; the script prints the URL as its last line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;configuration&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;packageSources&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;add&lt;/span&gt; &lt;span class="na"&gt;key=&lt;/span&gt;&lt;span class="s"&gt;"dotnet12"&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet12/nuget/v3/index.json"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/packageSources&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/configuration&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  4. Test
&lt;/h2&gt;

&lt;p&gt;The SDK alone, no workload needed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dotnet new console &lt;span class="nt"&gt;-o&lt;/span&gt; Hello &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; dotnet run &lt;span class="nt"&gt;--project&lt;/span&gt; Hello
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Hello, World!
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The workloads, on the .NET 12 alpha with Xcode 27 (iOS needs a Mac):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dotnet new ios &lt;span class="nt"&gt;-o&lt;/span&gt; HelloIos &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; dotnet build HelloIos
dotnet new android &lt;span class="nt"&gt;-o&lt;/span&gt; HelloDroid &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; dotnet build HelloDroid
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  HelloIos -&amp;gt; /Users/dan/net12/HelloIos/bin/Debug/net11.0-ios/iossimulator-arm64/HelloIos.dll
Build succeeded.     (23 s; HelloIos.app for the simulator)
  HelloDroid -&amp;gt; /Users/dan/net12/HelloDroid/bin/Debug/net11.0-android/HelloDroid.dll
Build succeeded.     (21 s; com.companyname.HelloDroid-Signed.apk)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And the real reason I started: in the &lt;code&gt;net11&lt;/code&gt; folder, Maui.NativeStyles has been building for Xcode 27 since October 6, 2026 with &lt;code&gt;dotnet build -f net11.0-ios&lt;/code&gt;, while the machine-wide .NET 10 keeps building everything else. Three things changed in the app for .NET 11 and Xcode 27, none of them in the script: .NET for Android 37 rejects &lt;code&gt;SupportedOSPlatformVersion&lt;/code&gt; below 24 (XA4216); the iOS 27 bindings return &lt;code&gt;UIFont?&lt;/code&gt; from &lt;code&gt;SystemFontOfSize&lt;/code&gt; and &lt;code&gt;FromDescriptor&lt;/code&gt;, three new warnings; and an app built with the iOS 27 SDK and no scene delegate is terminated at launch, which I covered in &lt;a href="https://dev.to/danardelean/your-net-maui-ios-app-will-crash-at-launch-with-xcode-27-here-is-the-two-file-fix-mlm"&gt;an earlier post&lt;/a&gt;. (If something you depend on pulls in &lt;code&gt;Microsoft.Maui.Controls.Compatibility&lt;/code&gt;, pin it to &lt;code&gt;11.0.0-preview.6.26324.7&lt;/code&gt;: there is no later build on the feed.)&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Update
&lt;/h2&gt;

&lt;p&gt;Nightlies move daily: the alpha went from &lt;code&gt;26508.114&lt;/code&gt; to &lt;code&gt;26509.101&lt;/code&gt; while I was writing this. To update, run the same install command again, with &lt;code&gt;--prune&lt;/code&gt; so that it drops what it replaces. On a folder I had installed in the morning from &lt;code&gt;26508.114&lt;/code&gt;, with &lt;code&gt;android&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;~/install.sh &lt;span class="nt"&gt;--channel&lt;/span&gt; 12.0.1xx &lt;span class="nt"&gt;--workloads&lt;/span&gt; android &lt;span class="nt"&gt;--prune&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SDK 12.0.100-alpha.1.26509.101 (band 12.0.100-alpha.1) in /Users/dan/net12/.dotnet
Manifests (12.0.100-alpha.1):
  microsoft.net.sdk.android: 37.99.0-preview.1.135 (present)
  microsoft.net.sdk.ios: 27.0.12277-net12-p1 (present)
  ...
Workload(s) 'android' are already installed.
  pruning sdk/12.0.100-alpha.1.26508.114
  pruning shared/Microsoft.NETCore.App/12.0.0-alpha.1.26508.114
  pruning shared/Microsoft.AspNetCore.App/12.0.0-alpha.1.26508.114
  pruning host/fxr/12.0.0-alpha.1.26508.114
global.json: SDK 12.0.100-alpha.1.26509.101 from .dotnet
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Sixty-five seconds, most of it the SDK download. The newer daily went in next to the old one, every manifest was still the newest build on the feeds so nothing was refetched, the workload was recognised as installed, the old SDK, runtime and host were pruned, and &lt;code&gt;global.json&lt;/code&gt; now pins the new build, so &lt;code&gt;dotnet --version&lt;/code&gt; in the folder says &lt;code&gt;26509.101&lt;/code&gt;. When a manifest has moved (the Android one went from &lt;code&gt;.129&lt;/code&gt; to &lt;code&gt;.135&lt;/code&gt; during the day), its newer version replaces the older folder and the workload's packs are installed for it; the script then shuts down the build servers of that SDK, which would otherwise keep the old manifests in memory. The install and the update are the same command, so a &lt;code&gt;scripts/install-nightly.sh&lt;/code&gt; wrapper in the repository with the channel and workloads filled in is all a teammate needs, before and after.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Clean
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;~/install.sh &lt;span class="nt"&gt;--clean&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Removed /Users/dan/net12/.dotnet
Removed global.json (it pointed at .dotnet)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is the whole undo: the folder and the &lt;code&gt;global.json&lt;/code&gt; that pointed at it, nothing else was written. It refuses a folder that holds neither a &lt;code&gt;dotnet&lt;/code&gt; executable nor an &lt;code&gt;sdk/&lt;/code&gt; directory, so a typo cannot delete your project. When RC2 ships, &lt;code&gt;--channel 11.0 --quality preview&lt;/code&gt; gives you the release itself, with the workloads from nuget.org and none of the nightly feeds.&lt;/p&gt;

&lt;h2&gt;
  
  
  How it works
&lt;/h2&gt;

&lt;p&gt;Three standard pieces, nothing clever:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;The SDK.&lt;/strong&gt; Microsoft's &lt;a href="https://learn.microsoft.com/dotnet/core/tools/dotnet-install-script" rel="noopener noreferrer"&gt;&lt;code&gt;dotnet-install.sh&lt;/code&gt;&lt;/a&gt; with &lt;code&gt;--install-dir ./.dotnet&lt;/code&gt; extracts the SDK and its runtime there and touches nothing else; it is the documented way to install .NET without an installer.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The workloads.&lt;/strong&gt; &lt;code&gt;dotnet workload install&lt;/code&gt; installs into the root of the &lt;code&gt;dotnet&lt;/code&gt; executable that runs it, so &lt;code&gt;./.dotnet/dotnet workload install ios&lt;/code&gt; lands in &lt;code&gt;./.dotnet/&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;global.json.&lt;/strong&gt; &lt;code&gt;{ "sdk": { "version": "12.0.100-alpha.1.26509.101", "paths": [ ".dotnet", "$host$" ] } }&lt;/code&gt;. Inside the repository &lt;code&gt;dotnet&lt;/code&gt; resolves to the folder; anywhere else &lt;code&gt;$host$&lt;/code&gt; means the machine-wide install. The &lt;a href="https://learn.microsoft.com/dotnet/core/tools/global-json#paths" rel="noopener noreferrer"&gt;global.json reference&lt;/a&gt; describes &lt;code&gt;paths&lt;/code&gt; as made for local SDK installations relative to a repository root, and its own example uses &lt;code&gt;".dotnet"&lt;/code&gt; with an &lt;code&gt;errorMessage&lt;/code&gt; that tells you to run &lt;code&gt;./install.sh&lt;/code&gt;. dotnet/maui and dotnet/macios have exactly this in their own &lt;code&gt;global.json&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Why a script and not just dotnet-install.sh
&lt;/h2&gt;

&lt;p&gt;Because the SDK alone is not enough, and each of these cost me a wrong turn. The script does what the dotnet/maui build does for each:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The bundled manifests are stale.&lt;/strong&gt; A daily SDK ships baseline workload manifests months old (the newest RC2 daily still carries July's Preview 6 iOS and MAUI). The script downloads the newest &lt;code&gt;&amp;lt;id&amp;gt;.Manifest-&amp;lt;band&amp;gt;&lt;/code&gt; of every manifest the SDK knows and places it in &lt;code&gt;sdk-manifests/&amp;lt;band&amp;gt;/&lt;/code&gt;, then installs with &lt;code&gt;--skip-manifest-update&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The feeds are several, and some are hidden.&lt;/strong&gt; &lt;code&gt;dotnet&amp;lt;major&amp;gt;&lt;/code&gt; on dnceng and nuget.org, plus whatever dotnet/maui's branch for that band lists in its &lt;code&gt;NuGet.config&lt;/code&gt;: release builds of dotnet/macios and dotnet/android land on isolated &lt;code&gt;darc-pub-*&lt;/code&gt; feeds that only that file names. The .NET 12 iOS and Android workloads still carry .NET 10 servicing packs from such builds, and dotnet/maui's &lt;code&gt;main&lt;/code&gt; (still a .NET 10 branch) does not name those feeds, so for an alpha the script also reads the previous major's release branches.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A compat manifest can point at a runtime that is not public yet.&lt;/strong&gt; The &lt;code&gt;*-net10&lt;/code&gt; workloads reference the .NET 10 runtime the SDK was built with, a servicing build that reaches nuget.org on Patch Tuesday. The script points them at the newest public one; those packs are not used to build for the new .NET anyway. It is the one real hack, on a throwaway SDK.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Version sorting.&lt;/strong&gt; &lt;code&gt;main&lt;/code&gt; publishes into the RC2 band too, and &lt;code&gt;preview.7&lt;/code&gt; sorts above &lt;code&gt;rc.2&lt;/code&gt; if you compare numbers. SemVer precedence, and prefer the band's own label.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Build servers remember.&lt;/strong&gt; MSBuild nodes and the compiler server outlive a build and keep the manifests they loaded; after a refresh my next build imported a targets file of a version that was gone. The script runs &lt;code&gt;dotnet build-server shutdown&lt;/code&gt; after it changes anything.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Nothing in it knows about .NET 11 or 12.&lt;/strong&gt; The band, the feeds (&lt;code&gt;dotnet13&lt;/code&gt; when the time comes, plus the dotnet/maui branches of that band and of the previous major) and the compat runtime are all derived from the version it finds. Two conventions it cannot look up are what to check if a future lane looks wrong, the naming of dotnet/maui's branches and the list of manifest ids it probes on nuget.org; both are one line in the script and both can be overridden from the command line (&lt;code&gt;--maui-branch&lt;/code&gt;, &lt;code&gt;--feeds&lt;/code&gt;).&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Caveats
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;install.sh&lt;/code&gt; has done every run quoted here on macOS. &lt;code&gt;install.ps1&lt;/code&gt; has run on macOS and Linux; its Windows path is written and tested against a fake SDK, not yet on a Windows machine.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--skip-sign-check&lt;/code&gt; and hand-placed manifests bypass the installer's own checks, as the dotnet/maui build does. Read the script before running it; it writes &lt;code&gt;global.json&lt;/code&gt; in the current directory and nothing outside the folder you name.&lt;/li&gt;
&lt;li&gt;Everything about the alpha is a snapshot of October 9, 2026. Workloads appear and move; &lt;code&gt;--list-channels&lt;/code&gt; and &lt;code&gt;--list-workloads&lt;/code&gt; are the truth on your day.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Credits
&lt;/h2&gt;

&lt;p&gt;Both the scripts and this article were written together with Claude, Anthropic's AI assistant, in Claude Code. The runs, the Mac, the wrong turns and the decision to look at .NET 12 are mine; the first draft of most lines, the tests and a good part of the digging through the dotnet/maui and dotnet/macios build files are Claude's. Every number here comes from a run on my machine or from the feeds, checked on October 9, 2026.&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>ios</category>
      <category>android</category>
      <category>maui</category>
    </item>
    <item>
      <title>Why is my .NET MAUI iOS application crashing at launch after upgrading to Xcode 27?</title>
      <dc:creator>Dan Ardelean</dc:creator>
      <pubDate>Tue, 06 Oct 2026 08:04:18 +0000</pubDate>
      <link>https://dev.to/danardelean/your-net-maui-ios-app-will-crash-at-launch-with-xcode-27-here-is-the-two-file-fix-mlm</link>
      <guid>https://dev.to/danardelean/your-net-maui-ios-app-will-crash-at-launch-with-xcode-27-here-is-the-two-file-fix-mlm</guid>
      <description>&lt;p&gt;&lt;em&gt;Apple now requires the UIScene lifecycle. Most .NET MAUI apps created before September 2026 don't have it. Here is what breaks, who is affected, and what I had to change in two production apps beyond the "official" fix.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;Last week I rebuilt two production .NET MAUI apps on a Mac that had just been upgraded to Xcode 27. Both compiled without a single warning. Both died a fraction of a second after the splash screen on every iOS 27 device and simulator, in Debug and in Release.&lt;/p&gt;

&lt;p&gt;No managed exception. No &lt;code&gt;UnhandledException&lt;/code&gt; event. Visual Studio didn't even notice. The only trace was a native crash report on the Mac:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Exception Type:  EXC_BREAKPOINT (SIGTRAP)

Thread 0 Crashed:: Dispatch queue: com.apple.main-thread
0  UIKitCore  ___UIApplicationEvaluateRuntimeIssueForNoSceneLifecycleAdoption_block_invoke + 712
1  libdispatch.dylib  _dispatch_client_callout + 12
2  libdispatch.dylib  _dispatch_once_callout + 28
3  UIKitCore  -[UIApplication workspace:didCreateScene:withTransitionContext:completion:] + 320
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The function name says it all: &lt;code&gt;NoSceneLifecycleAdoption&lt;/code&gt;. If you ship .NET MAUI apps for iOS, this article is for you.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Apple changed, and when
&lt;/h2&gt;

&lt;p&gt;Since iOS 13, UIKit has had two application lifecycles:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the &lt;strong&gt;legacy lifecycle&lt;/strong&gt;, where &lt;code&gt;UIApplicationDelegate&lt;/code&gt; owns the single &lt;code&gt;UIWindow&lt;/code&gt; and receives every callback (&lt;code&gt;didFinishLaunching&lt;/code&gt;, &lt;code&gt;openURL&lt;/code&gt;, &lt;code&gt;applicationDidBecomeActive&lt;/code&gt;, and so on);&lt;/li&gt;
&lt;li&gt;the &lt;strong&gt;scene lifecycle&lt;/strong&gt;, where the app delegate only bootstraps the process and a &lt;code&gt;UISceneDelegate&lt;/code&gt; owns each window (a "scene") and receives the UI-related callbacks.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For seven years the legacy path kept working. Apple escalated in three steps, all documented in Technote TN3187, "Migrating to the UIKit scene-based life cycle":&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;iOS 18.4&lt;/strong&gt;: UIKit logs "This process does not adopt UIScene lifecycle. This will become an assert in a future version."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;iOS 26&lt;/strong&gt;: the message becomes "UIScene lifecycle will soon be required. Failure to adopt will result in an assert in the future." At WWDC25 (session 282, "Make your UIKit app more flexible") Apple said it out loud: "In the next major release following iOS 26, UIScene life cycle will be required when building with the latest SDK."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;iOS 27 SDK, Xcode 27&lt;/strong&gt;: the assert is live. The iOS and iPadOS 27 release notes put it in one sentence: "Apps built with the latest SDK must adopt the scene-based life cycle or they fail to launch."&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Two details matter in practice:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The trigger is the &lt;strong&gt;SDK you link against&lt;/strong&gt;, not the OS the user runs. An Apple engineer confirmed it on the developer forums: "The assert is only evaluated when building with the next major release's SDK." Your existing binary built with Xcode 26 keeps working on iOS 27 phones. Rebuild it with Xcode 27 and it dies.&lt;/li&gt;
&lt;li&gt;The assert fires on &lt;strong&gt;iOS 27 devices and simulators only&lt;/strong&gt;, and on macOS 27 for Mac Catalyst apps. The same Xcode 27 build still launches on iOS 26. That is why a quick test on an older phone, or an App Store review run on an older device, can pass. The dotnet/macios issue linked below reports exactly that: an app without scenes approved by App Review, then crashing for users on iOS 27.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Apple also says that only &lt;em&gt;adoption&lt;/em&gt; of the scene lifecycle is required. Supporting multiple windows is encouraged but optional, and that distinction becomes important in the Info.plist below.&lt;/p&gt;

&lt;h2&gt;
  
  
  Is the stock .NET MAUI template affected?
&lt;/h2&gt;

&lt;p&gt;Yes, unless your workload is recent enough. I checked the actual template packages on NuGet rather than trusting what my machine generates:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Microsoft.Maui.Templates.net10 10.0.100 and earlier&lt;/strong&gt;: no &lt;code&gt;SceneDelegate.cs&lt;/code&gt;, no &lt;code&gt;UIApplicationSceneManifest&lt;/code&gt; in &lt;code&gt;Info.plist&lt;/code&gt;. Affected.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Microsoft.Maui.Templates.net10 10.0.110&lt;/strong&gt;, the .NET 10 SR11 release of September 22, 2026: includes &lt;code&gt;Platforms/iOS/SceneDelegate.cs&lt;/code&gt;, &lt;code&gt;Platforms/MacCatalyst/SceneDelegate.cs&lt;/code&gt; and the scene manifest in every Apple head of every template (&lt;code&gt;maui&lt;/code&gt;, &lt;code&gt;maui-blazor&lt;/code&gt;, &lt;code&gt;maui-blazor-web&lt;/code&gt;, &lt;code&gt;maui-multiproject&lt;/code&gt;). Fixed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Microsoft.Maui.Templates.net11 11.0.0-rc.1&lt;/strong&gt;, the latest .NET 11 prerelease at the time of writing: still &lt;strong&gt;without&lt;/strong&gt; scenes. In fairness, .NET 11 RC 1 ships iOS 26.5 bindings and requires Xcode 26.6, so you cannot build it with Xcode 27 anyway. The fix is already in the &lt;code&gt;release/11.0.1xx-rc2&lt;/code&gt; branch of dotnet/maui. I diffed that branch's &lt;code&gt;maui&lt;/code&gt;, &lt;code&gt;maui-blazor&lt;/code&gt;, &lt;code&gt;maui-blazor-web&lt;/code&gt; and &lt;code&gt;maui-multiproject&lt;/code&gt; templates against 10.0.110: the iOS and Mac Catalyst &lt;code&gt;Info.plist&lt;/code&gt;, &lt;code&gt;SceneDelegate.cs&lt;/code&gt;, &lt;code&gt;AppDelegate.cs&lt;/code&gt; and &lt;code&gt;Program.cs&lt;/code&gt; are identical, so everything in this article applies unchanged to .NET 11 projects. RC 2 will generate the right files; the final .NET 11 is documented as requiring Xcode 27.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The template change is dotnet/maui pull request &lt;a href="https://github.com/dotnet/maui/pull/38601" rel="noopener noreferrer"&gt;#38601&lt;/a&gt;, "Adopt scene lifecycle and AppActions dispatch", merged on September 18, 2026 and backported to SR11 the same day. Its description is explicit about the root cause: the generated app compiled fine but terminated after the splash screen with "Application failed to launch: UIScene life cycle is required for apps built with this SDK."&lt;/p&gt;

&lt;p&gt;The timeline explains why so many people hit this in the same week. Xcode 27 shipped mid-September. MAUI 10.0.110 with the new template came out on September 22. Official Xcode 27 support for .NET for iOS (workload set 10.0.401.1) arrived on September 29. From that day on, everyone who updated their Mac rebuilt existing apps with the iOS 27 SDK, and existing apps don't get new template files.&lt;/p&gt;

&lt;p&gt;That is the rule to remember: &lt;strong&gt;the version of the template that created your project is what matters, not the MAUI package you reference today&lt;/strong&gt;. Upgrading &lt;code&gt;Microsoft.Maui.Controls&lt;/code&gt; to 10.0.110 does not touch your &lt;code&gt;Info.plist&lt;/code&gt;. A reporter in dotnet/maui issue &lt;a href="https://github.com/dotnet/maui/issues/38992" rel="noopener noreferrer"&gt;#38992&lt;/a&gt; had exactly this setup, 10.0.110 package and a black screen at launch, and fixed it by adding the two files by hand. And no, the build does not inject the manifest for you: neither the .NET for iOS SDK nor MAUI synthesizes it. The only switch in the whole stack is &lt;code&gt;HasSceneManifest()&lt;/code&gt;, which just checks whether the key exists in the compiled &lt;code&gt;Info.plist&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;A quick way to check which template you have:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dotnet workload list
dotnet new maui &lt;span class="nt"&gt;-n&lt;/span&gt; Probe &lt;span class="nt"&gt;-o&lt;/span&gt; /tmp/Probe &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;ls&lt;/span&gt; /tmp/Probe/Platforms/iOS
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If &lt;code&gt;SceneDelegate.cs&lt;/code&gt; is in the listing, your template is current. If not, run &lt;code&gt;dotnet workload update&lt;/code&gt; before creating new projects. Visual Studio 2026 may bundle an older workload than the .NET SDK on your Mac; the dotnet/macios issue below was opened because the VS "New Project" dialog still produced the old layout.&lt;/p&gt;

&lt;p&gt;For comparison, the native .NET for iOS template (&lt;code&gt;dotnet new ios&lt;/code&gt;) adopted scenes in July 2025, fourteen months earlier, right after WWDC25. The MAUI template waited for the crash.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reproducing it in five minutes
&lt;/h2&gt;

&lt;p&gt;I took the 10.0.110 template, which works, and removed the two scene pieces to get a "legacy" app. Then I built both for the iOS 27 simulator with Xcode 27.0 (27A266a) and .NET SDK 10.0.401:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the untouched template launches and shows the usual "Hello, World!" page;&lt;/li&gt;
&lt;li&gt;the legacy copy is killed at launch, with the stack trace above, in &lt;strong&gt;both Debug and Release&lt;/strong&gt;;&lt;/li&gt;
&lt;li&gt;the same pair built for &lt;strong&gt;Mac Catalyst&lt;/strong&gt; with the macOS 27.0 SDK behaves identically on macOS 27.0.1: the template runs, the legacy copy crashes with the same trap.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I specifically tested the hypothesis that only Release builds crash. It is false: the Debug build produced a byte-for-byte identical crash report. I also attached lldb to the Debug build before launch, in case the check behaved differently under a debugger. It doesn't. The process stops on a literal &lt;code&gt;brk #0&lt;/code&gt; instruction inside &lt;code&gt;UIApplicationEvaluateRuntimeIssueForNoSceneLifecycleAdoption&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;* thread #1, queue = 'com.apple.main-thread', stop reason = EXC_BREAKPOINT (code=1, subcode=0x1c48c336c)
    frame #0: UIKitCore`___UIApplicationEvaluateRuntimeIssueForNoSceneLifecycleAdoption_block_invoke + 712
-&amp;gt;  0x1c48c336c &amp;lt;+712&amp;gt;: brk    #0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What changes between Debug and Release is only how visible the failure is. The IDE often reports nothing useful because the trap happens on the native side before any managed frame is involved. On the simulator, &lt;code&gt;simctl launch&lt;/code&gt; even returns a PID, and the process is gone a moment later.&lt;/p&gt;

&lt;h2&gt;
  
  
  The fix
&lt;/h2&gt;

&lt;p&gt;Two files. No NuGet update required, because &lt;code&gt;MauiUISceneDelegate&lt;/code&gt; has been part of Microsoft.Maui since 2021, when multi-window support for iPad and Mac Catalyst was added. Apple's requirement just turned an opt-in feature into the only path.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Add &lt;code&gt;Platforms/iOS/SceneDelegate.cs&lt;/code&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Foundation&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;YourApp&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;Register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"SceneDelegate"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SceneDelegate&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;MauiUISceneDelegate&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The class can stay empty. &lt;code&gt;MauiUISceneDelegate&lt;/code&gt; already creates the MAUI window in &lt;code&gt;WillConnect&lt;/code&gt; and forwards the scene callbacks (&lt;code&gt;sceneDidBecomeActive&lt;/code&gt;, &lt;code&gt;sceneWillEnterForeground&lt;/code&gt;, and the rest) to the cross-platform &lt;code&gt;Window&lt;/code&gt; lifecycle events. Do not add your own forwarding; you would get every event twice.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Declare the scene in &lt;code&gt;Platforms/iOS/Info.plist&lt;/code&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;key&amp;gt;&lt;/span&gt;UIApplicationSceneManifest&lt;span class="nt"&gt;&amp;lt;/key&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;dict&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;key&amp;gt;&lt;/span&gt;UIApplicationSupportsMultipleScenes&lt;span class="nt"&gt;&amp;lt;/key&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;false/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;key&amp;gt;&lt;/span&gt;UISceneConfigurations&lt;span class="nt"&gt;&amp;lt;/key&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;dict&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;key&amp;gt;&lt;/span&gt;UIWindowSceneSessionRoleApplication&lt;span class="nt"&gt;&amp;lt;/key&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;array&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;dict&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;key&amp;gt;&lt;/span&gt;UISceneConfigurationName&lt;span class="nt"&gt;&amp;lt;/key&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;string&amp;gt;&lt;/span&gt;__MAUI_DEFAULT_SCENE_CONFIGURATION__&lt;span class="nt"&gt;&amp;lt;/string&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;key&amp;gt;&lt;/span&gt;UISceneDelegateClassName&lt;span class="nt"&gt;&amp;lt;/key&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;string&amp;gt;&lt;/span&gt;SceneDelegate&lt;span class="nt"&gt;&amp;lt;/string&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;/dict&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/array&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/dict&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/dict&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three things here are not arbitrary:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;__MAUI_DEFAULT_SCENE_CONFIGURATION__&lt;/code&gt; is a magic string. &lt;code&gt;MauiUIApplicationDelegate.GetConfiguration&lt;/code&gt; returns a &lt;code&gt;UISceneConfiguration&lt;/code&gt; with exactly this name, and &lt;code&gt;MauiUISceneDelegate.WillConnect&lt;/code&gt; only creates the window when the session configuration has this name. Rename it and you get a black screen instead of a crash.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;UISceneDelegateClassName&lt;/code&gt; must match the &lt;code&gt;[Register("SceneDelegate")]&lt;/code&gt; attribute, not the C# class name or namespace.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;UIApplicationSupportsMultipleScenes&lt;/code&gt; is &lt;code&gt;false&lt;/code&gt; in the official template. The Microsoft Learn page on multi-window, which until now was the only place showing this snippet, sets it to &lt;code&gt;true&lt;/code&gt;, and so do most snippets you will find online. &lt;code&gt;true&lt;/code&gt; works, but it also tells iPadOS and Mac Catalyst that your app supports multiple windows, which changes the app switcher, split view and window tabbing on the Mac. Keep it &lt;code&gt;false&lt;/code&gt; unless you actually want multi-window.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then delete &lt;code&gt;bin&lt;/code&gt; and &lt;code&gt;obj&lt;/code&gt; and rebuild. This is not superstition: the merged Info.plist is cached, and several reporters saw the fix "not work" until they cleaned. &lt;code&gt;Program.cs&lt;/code&gt; does not change: &lt;code&gt;UIApplication.Main(args, null, typeof(AppDelegate))&lt;/code&gt; is still the entry point, and &lt;code&gt;AppDelegate&lt;/code&gt; is still needed because it hosts the &lt;code&gt;MauiApp&lt;/code&gt; and answers UIKit's scene configuration request. It just no longer owns the window.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Mac Catalyst, if you target it
&lt;/h3&gt;

&lt;p&gt;Mac Catalyst is affected in exactly the same way, not only by the deployment-target change below. Catalyst apps are UIKit apps, and the requirement is UIKit's. I built the no-scene app for Mac Catalyst with the macOS 27.0 SDK and launched it on macOS 27.0.1: it dies immediately with the same &lt;code&gt;EXC_BREAKPOINT&lt;/code&gt; in &lt;code&gt;UIApplicationEvaluateRuntimeIssueForNoSceneLifecycleAdoption&lt;/code&gt;, while the untouched 10.0.110 template runs.&lt;/p&gt;

&lt;p&gt;So the same two pieces go into &lt;code&gt;Platforms/MacCatalyst&lt;/code&gt;: a &lt;code&gt;SceneDelegate&lt;/code&gt; class and the manifest in that head's &lt;code&gt;Info.plist&lt;/code&gt;. The Mac Catalyst 27 SDK also refuses &lt;code&gt;SupportedOSPlatformVersion&lt;/code&gt; 15.0 for app projects; the template now uses 17.0.&lt;/p&gt;

&lt;p&gt;While you are in the plist: the 27 SDK also makes a launch screen mandatory (&lt;code&gt;UILaunchStoryboardName&lt;/code&gt; or one of its siblings). MAUI's &lt;code&gt;MauiSplashScreen&lt;/code&gt; item emits it for you, but if you ever removed the splash screen configuration, that is a second way to fail at launch.&lt;/p&gt;

&lt;h2&gt;
  
  
  What MAUI does under the hood
&lt;/h2&gt;

&lt;p&gt;It helps to know why the fix is this small. &lt;code&gt;MauiUIApplicationDelegate.FinishedLaunching&lt;/code&gt; looks at the bundle:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// if there is no scene delegate or support for scene delegates, then we set up the window here&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(!&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;HasSceneManifest&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreatePlatformWindow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_application&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;application&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;launchOptions&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With no manifest, the app delegate creates the window itself (legacy lifecycle). With the manifest present, it answers &lt;code&gt;application:configurationForConnectingSceneSession:options:&lt;/code&gt; with the MAUI configuration name, UIKit instantiates your &lt;code&gt;SceneDelegate&lt;/code&gt;, and the window is created in &lt;code&gt;WillConnect&lt;/code&gt;. Your cross-platform &lt;code&gt;App&lt;/code&gt;, &lt;code&gt;Window&lt;/code&gt; and page code is identical in both paths.&lt;/p&gt;

&lt;p&gt;The consequence, and the source of every gotcha below, is that &lt;strong&gt;the window now exists later and is owned by a different object, and UIKit delivers several events to that object instead of the app delegate&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Gotchas from two production apps
&lt;/h2&gt;

&lt;p&gt;The empty &lt;code&gt;SceneDelegate&lt;/code&gt; is enough for the template. It was not enough for either of my apps. These are the things that broke after the two-file fix compiled.&lt;/p&gt;

&lt;h3&gt;
  
  
  Gotcha 1: &lt;code&gt;AppDelegate.OpenUrl&lt;/code&gt; is never called again
&lt;/h3&gt;

&lt;p&gt;The first app is a family of public-transport apps that sign in through Microsoft Entra with MSAL. The login happens in a system browser and returns to the app through a custom URL scheme (&lt;code&gt;msauth.&amp;lt;bundle id&amp;gt;://&lt;/code&gt;). Before the change, the return URL was handled here:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// AppDelegate.cs, before&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="nf"&gt;OpenUrl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;UIApplication&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;NSUrl&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;NSDictionary&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;AuthenticationContinuationHelper&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SetAuthenticationContinuationEventArgs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;base&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;OpenUrl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With a scene manifest, UIKit routes incoming URLs to &lt;code&gt;scene:openURLContexts:&lt;/code&gt; on the scene delegate. The app delegate override is simply never invoked, so the login flow hangs forever in the browser. The fix is to move the handler, with the scene-flavored signature that receives a set of &lt;code&gt;UIOpenUrlContext&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// SceneDelegate.cs, after&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;Register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"SceneDelegate"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SceneDelegate&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;MauiUISceneDelegate&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="nf"&gt;OpenUrl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;UIScene&lt;/span&gt; &lt;span class="n"&gt;scene&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;NSSet&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;UIOpenUrlContext&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;urlContexts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;urlContexts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;AuthenticationContinuationHelper&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SetAuthenticationContinuationEventArgs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;base&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;OpenUrl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;scene&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;urlContexts&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The same applies to anything else that used to arrive through the app delegate's URL or user-activity methods: deep links, universal links (&lt;code&gt;ContinueUserActivity&lt;/code&gt;), OAuth callbacks for Google or Apple Sign In, payment return URLs. Grep for every override and move it. If you use the lifecycle events API instead of overrides, the mapping is &lt;code&gt;OpenUrl&lt;/code&gt; to &lt;code&gt;SceneOpenUrl&lt;/code&gt; and &lt;code&gt;ContinueUserActivity&lt;/code&gt; to &lt;code&gt;SceneContinueUserActivity&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;There is a second half to this that bit me only later: a &lt;strong&gt;cold start&lt;/strong&gt; from a URL or a universal link no longer puts the URL in &lt;code&gt;FinishedLaunching&lt;/code&gt;'s launch options. It arrives in &lt;code&gt;connectionOptions.UrlContexts&lt;/code&gt; and &lt;code&gt;connectionOptions.UserActivities&lt;/code&gt; inside &lt;code&gt;WillConnect&lt;/code&gt;. The same is true for a cold launch from a push notification tap, which now lives in &lt;code&gt;connectionOptions.NotificationResponse&lt;/code&gt;. Warm delivery and cold delivery are two different code paths, and you need to test both.&lt;/p&gt;

&lt;p&gt;One more trap in that project: each app flavor (Dev, Stage, Prod) has its own &lt;code&gt;Info.plist&lt;/code&gt; selected by build configuration. The manifest had to go into &lt;strong&gt;all of them&lt;/strong&gt;. A missing one gives you an app that crashes only in one configuration, which is a miserable thing to debug on a Friday.&lt;/p&gt;

&lt;h3&gt;
  
  
  Gotcha 2: startup code that assumed a window in &lt;code&gt;FinishedLaunching&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;The second app is a field-service app with a custom bootstrap: after &lt;code&gt;FinishedLaunching&lt;/code&gt;, the app ran an async &lt;code&gt;RunAsync()&lt;/code&gt; that initialized databases and services and then navigated to the first page. Simplified:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// AppDelegate.cs, before&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="nf"&gt;FinishedLaunching&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;UIApplication&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;NSDictionary&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;base&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FinishedLaunching&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;InitApplicationAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;   &lt;span class="c1"&gt;// eventually sets the root page&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the legacy lifecycle this was fine because &lt;code&gt;base.FinishedLaunching&lt;/code&gt; had already created the window. With scenes, &lt;code&gt;FinishedLaunching&lt;/code&gt; returns &lt;strong&gt;before&lt;/strong&gt; any window exists; the window is created in &lt;code&gt;SceneDelegate.WillConnect&lt;/code&gt;, which runs afterwards. The bootstrap now raced against window creation and the navigation landed on a window that wasn't there yet.&lt;/p&gt;

&lt;p&gt;The fix is to start the application from the scene, once:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// SceneDelegate.cs, after&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;Register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"SceneDelegate"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SceneDelegate&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;MauiUISceneDelegate&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;_started&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;WillConnect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;UIScene&lt;/span&gt; &lt;span class="n"&gt;scene&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;UISceneSession&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                                     &lt;span class="n"&gt;UISceneConnectionOptions&lt;/span&gt; &lt;span class="n"&gt;connectionOptions&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;base&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WillConnect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;scene&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;connectionOptions&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;   &lt;span class="c1"&gt;// the window is created here&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_started&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="n"&gt;_started&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nf"&gt;RunApplicationAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;RunApplicationAsync&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;AppBootstrapper&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Instance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;RunAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;AppDelegate.FinishedLaunching&lt;/code&gt; keeps only the process-level work: SQLite initialization and the global exception handlers. The rule of thumb: anything that touches &lt;code&gt;Window&lt;/code&gt;, &lt;code&gt;MainPage&lt;/code&gt;, navigation or &lt;code&gt;UIApplication.SharedApplication.KeyWindow&lt;/code&gt; belongs after &lt;code&gt;WillConnect&lt;/code&gt;, not in &lt;code&gt;FinishedLaunching&lt;/code&gt;. With the lifecycle events API, &lt;code&gt;OnPlatformWindowCreated&lt;/code&gt; and &lt;code&gt;SceneWillConnect&lt;/code&gt; are the hooks you want. And while you are there, replace &lt;code&gt;KeyWindow&lt;/code&gt;, deprecated since iOS 13, with &lt;code&gt;WindowStateManager.Default.GetCurrentUIWindow()&lt;/code&gt; or the &lt;code&gt;UIWindowScene.KeyWindow&lt;/code&gt; of a connected scene.&lt;/p&gt;

&lt;h3&gt;
  
  
  Gotcha 3: everything else that was wired to the app delegate
&lt;/h3&gt;

&lt;p&gt;After the two apps launched again, I went through the remaining &lt;code&gt;AppDelegate&lt;/code&gt; overrides. Scene-based apps also stop receiving these on the app delegate:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Home screen quick actions&lt;/strong&gt; (&lt;code&gt;PerformActionForShortcutItem&lt;/code&gt;). UIKit does not call the app delegate selector for scene apps. MAUI 10.0.110 added the scene-side handler and forwards it to the existing &lt;code&gt;iOSLifecycle.PerformActionForShortcutItem&lt;/code&gt; registrations, so if you use &lt;code&gt;ConfigureLifecycleEvents&lt;/code&gt; and are on 10.0.110 you are fine. If you overrode the app delegate method, or you are on an older MAUI and added the manifest by hand, quick actions silently stop working. There is a new contract to respect, too: every registered handler must now invoke its completion callback exactly once, &lt;code&gt;true&lt;/code&gt; if handled and &lt;code&gt;false&lt;/code&gt; otherwise, including logging-only observers. A handler that never acknowledges blocks the native completion, and there is no timeout.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Foreground and background transitions&lt;/strong&gt;. &lt;code&gt;OnActivated&lt;/code&gt;, &lt;code&gt;OnResignActivation&lt;/code&gt;, &lt;code&gt;DidEnterBackground&lt;/code&gt; and &lt;code&gt;WillEnterForeground&lt;/code&gt; still fire on the app delegate at process level, and MAUI's &lt;code&gt;Window.Activated&lt;/code&gt;, &lt;code&gt;Deactivated&lt;/code&gt;, &lt;code&gt;Stopped&lt;/code&gt; and &lt;code&gt;Resumed&lt;/code&gt; keep working because MAUI switches its own forwarding to the scene events when a manifest is present. But UI work such as adding a privacy blur over the window belongs in the scene equivalents (&lt;code&gt;sceneDidBecomeActive&lt;/code&gt;, &lt;code&gt;sceneWillResignActive&lt;/code&gt;), where &lt;code&gt;Window&lt;/code&gt; is guaranteed to exist.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Push notification registration and &lt;code&gt;DidReceiveRemoteNotification&lt;/code&gt; stay on the app delegate. Nothing to move there, except the cold-launch case mentioned above.&lt;/p&gt;

&lt;h2&gt;
  
  
  The upgrade path, end to end
&lt;/h2&gt;

&lt;p&gt;The scene fix is the visible part. Getting a team and a CI pipeline onto Xcode 27 without a broken week has a few more steps, and the order matters.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 0: adopt scenes before you touch the toolchain
&lt;/h3&gt;

&lt;p&gt;The scene lifecycle has worked since iOS 13, and MAUI has supported it since 2021. Nothing in the fix depends on Xcode 27. So the safest sequence is to add &lt;code&gt;SceneDelegate&lt;/code&gt; and the manifest while you are still building with Xcode 26, ship that to production, watch the login and deep-link flows for a release cycle, and only then move the toolchain. When Xcode 27 arrives, your app is already compliant and the upgrade becomes a non-event. Both of my apps would have been in this situation if I had read TN3187 in June instead of a crash report in October.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: know your target versions
&lt;/h3&gt;

&lt;p&gt;As of October 2026, the supported stack for building MAUI apps with the iOS 27 SDK is one specific combination, published in the dotnet/macios release ".NET 10 - Xcode 27.0 support" of September 29:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;macOS 26.6 or later (Xcode 27 does not install on older systems).&lt;/li&gt;
&lt;li&gt;Xcode 27.0.&lt;/li&gt;
&lt;li&gt;.NET SDK 10.0.401.&lt;/li&gt;
&lt;li&gt;Workload set 10.0.401.1, which brings the iOS, tvOS, macOS and Mac Catalyst SDKs 27.0.10722 and MAUI workload 10.0.110.&lt;/li&gt;
&lt;li&gt;Microsoft.Maui.Controls 10.0.110 or later, recommended but not strictly required. Both of my apps still reference 10.0.90 and run fine with the manual fix; what 10.0.110 adds on top is the scene-side handling of quick actions and the &lt;code&gt;WebAuthenticator&lt;/code&gt; callbacks described above.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Everything else is out:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;.NET 9 and .NET 8 apps cannot build with Xcode 27.&lt;/strong&gt; The last .NET 9 release from dotnet/macios targets Xcode 26.5 (May 2026), and .NET 9 left support the same month. There is no .NET 8 or .NET 9 release for Xcode 27 and none is coming. If your app is still on .NET 8 or 9, the Xcode 27 migration is really a .NET 10 migration with the scene fix on top.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;.NET 11 RC 1 requires Xcode 26.6&lt;/strong&gt;, not 27. Do not move to .NET 11 to solve this problem. RC 2 ships the scene template and the final release in November is documented as requiring Xcode 27, but today the only supported way to build against the iOS 27 SDK is .NET 10. When you do move to .NET 11 later, note that its template also raises the Android minimum to API 24; unrelated to scenes, but part of the same upgrade.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Step 2: upgrade in the right order on the Mac
&lt;/h3&gt;

&lt;p&gt;The trap here is macOS. Xcode 26.x does not run on macOS 27, so if you take the macOS 27 update before the .NET side is ready, you lose the ability to build with the old SDK. The dotnet/macios maintainers said it in the Xcode 27 tracking issue, two weeks before the release: don't update to macOS 27 until support ships. That window is now closed, but the same logic applies to every future major release: &lt;strong&gt;.NET support first, then Xcode, then macOS&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;On a Mac that is still on macOS 26:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# 1. .NET SDK 10.0.401 from dotnet.microsoft.com, then verify&lt;/span&gt;
dotnet &lt;span class="nt"&gt;--version&lt;/span&gt;            &lt;span class="c"&gt;# must print 10.0.401&lt;/span&gt;

&lt;span class="c"&gt;# 2. Install Xcode 27 from the App Store or developer.apple.com, then select it&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;xcode-select &lt;span class="nt"&gt;-s&lt;/span&gt; /Applications/Xcode.app
xcodebuild &lt;span class="nt"&gt;-version&lt;/span&gt;         &lt;span class="c"&gt;# Xcode 27.0&lt;/span&gt;

&lt;span class="c"&gt;# 3. Pin the workload set that matches Xcode 27&lt;/span&gt;
dotnet workload &lt;span class="nb"&gt;install &lt;/span&gt;maui ios maccatalyst &lt;span class="nt"&gt;--version&lt;/span&gt; 10.0.401.1
dotnet workload &lt;span class="nt"&gt;--info&lt;/span&gt;      &lt;span class="c"&gt;# shows workload version 10.0.401.1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pinning the workload set with &lt;code&gt;--version&lt;/code&gt; is worth the extra flag. A plain &lt;code&gt;dotnet workload update&lt;/code&gt; takes whatever is latest, which is fine today and a surprise the day a new set lands mid-sprint. Pin the same version in &lt;code&gt;global.json&lt;/code&gt; with &lt;code&gt;"sdk": { "version": "10.0.401", "rollForward": "latestPatch" }&lt;/code&gt; so every machine and every CI agent resolves the same toolchain.&lt;/p&gt;

&lt;p&gt;Then verify the templates, not just the workload. The &lt;code&gt;dotnet new&lt;/code&gt; template cache under &lt;code&gt;~/.templateengine&lt;/code&gt; can keep serving an older MAUI template after the workload is updated. This is not a hypothetical: in the pull request that validated the .NET 11 RC 2 templates, the MAUI team's own first local run generated apps "from stale non-scene user templates" while the package versions looked correct. Generate a throwaway project and check for &lt;code&gt;SceneDelegate.cs&lt;/code&gt;; if it is missing, clear the template cache and try again:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dotnet new maui &lt;span class="nt"&gt;-n&lt;/span&gt; Probe &lt;span class="nt"&gt;-o&lt;/span&gt; /tmp/Probe &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;ls&lt;/span&gt; /tmp/Probe/Platforms/iOS
&lt;span class="c"&gt;# no SceneDelegate.cs?  -&amp;gt;  rm -rf ~/.templateengine &amp;amp;&amp;amp; dotnet new maui -n Probe -o /tmp/Probe --force&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you already updated to macOS 27 and need an emergency build with the old SDK, there are community workarounds (running the Xcode 26.6 bundle anyway and selecting it with &lt;code&gt;xcode-select&lt;/code&gt;, or setting &lt;code&gt;ValidateXcodeVersion=false&lt;/code&gt; in the project). I have not tested them, and they are exactly the kind of thing you don't want on the release path. Shipping the scene fix with Xcode 27 is less work.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3: per project
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Add the scene files and the manifest to every iOS and Mac Catalyst head, including every flavor-specific &lt;code&gt;Info.plist&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Raise &lt;code&gt;SupportedOSPlatformVersion&lt;/code&gt; for Mac Catalyst app projects to 17.0. iOS can stay at 15.0, the template default. The Catalyst change is a real deployment-target drop: 17.0 means macOS 14 (Sonoma) or later. If you still need to support macOS 12 or 13 users, the Templates README is blunt about it: stay on the 26.x SDK and keep the lower target, which also means staying off Xcode 27 for that app.&lt;/li&gt;
&lt;li&gt;Update &lt;code&gt;Microsoft.Maui.Controls&lt;/code&gt; and &lt;code&gt;Microsoft.Maui.Controls.Compatibility&lt;/code&gt; to 10.0.110 if you can. If you use &lt;code&gt;Microsoft.Maui.Essentials&lt;/code&gt; features like &lt;code&gt;AppActions&lt;/code&gt; or &lt;code&gt;WebAuthenticator&lt;/code&gt;, you should.&lt;/li&gt;
&lt;li&gt;Delete &lt;code&gt;bin&lt;/code&gt; and &lt;code&gt;obj&lt;/code&gt;. Yes, again.&lt;/li&gt;
&lt;li&gt;Build Debug and Release for the iOS 27 simulator and launch both. Then run the authentication, deep-link and notification flows, warm and cold.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Step 4: CI and the rest of the team
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Pin the Xcode version on your build agents. On hosted macOS images it means selecting the Xcode 27 image or running &lt;code&gt;sudo xcode-select -s /Applications/Xcode_27.0.app&lt;/code&gt; as a first step, and installing the workload set by version, not by "latest".&lt;/li&gt;
&lt;li&gt;Add an &lt;strong&gt;iOS 27 simulator&lt;/strong&gt; to whatever UI or smoke test you run. An iOS 26 simulator will keep passing on a build that crashes on every customer's phone.&lt;/li&gt;
&lt;li&gt;Windows developers using Visual Studio 2026 with a paired Mac: the first Xcode 27 release had a known issue where the remote iOS simulator loops on "Connecting to Mac" until a Visual Studio update ships. Check the dotnet/macios tracking issue &lt;a href="https://github.com/dotnet/macios/issues/25660" rel="noopener noreferrer"&gt;#25660&lt;/a&gt; before you tell the whole team to update.&lt;/li&gt;
&lt;li&gt;Communicate the trigger clearly: it is the SDK on the build machine, not the OS on the phone. A developer who updates their Mac and rebuilds is now producing crashing binaries for everyone on iOS 27, even if nothing in the repository changed.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  A checklist you can paste into a ticket
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Toolchain: macOS 26.6 or later, Xcode 27.0, .NET SDK 10.0.401, workload set 10.0.401.1, pinned in &lt;code&gt;global.json&lt;/code&gt; and in CI. Apps still on .NET 8 or 9 move to .NET 10 first.&lt;/li&gt;
&lt;li&gt;Add &lt;code&gt;Platforms/iOS/SceneDelegate.cs&lt;/code&gt; deriving from &lt;code&gt;MauiUISceneDelegate&lt;/code&gt;, registered as &lt;code&gt;SceneDelegate&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Add &lt;code&gt;UIApplicationSceneManifest&lt;/code&gt; to &lt;strong&gt;every&lt;/strong&gt; iOS &lt;code&gt;Info.plist&lt;/code&gt; in the project, with &lt;code&gt;__MAUI_DEFAULT_SCENE_CONFIGURATION__&lt;/code&gt; and &lt;code&gt;UIApplicationSupportsMultipleScenes = false&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Same for Mac Catalyst, plus &lt;code&gt;SupportedOSPlatformVersion&lt;/code&gt; 17.0.&lt;/li&gt;
&lt;li&gt;Move &lt;code&gt;OpenUrl&lt;/code&gt; and &lt;code&gt;ContinueUserActivity&lt;/code&gt; overrides from &lt;code&gt;AppDelegate&lt;/code&gt; to &lt;code&gt;SceneDelegate&lt;/code&gt;, and handle cold-start URLs, activities and notification responses from &lt;code&gt;connectionOptions&lt;/code&gt; in &lt;code&gt;WillConnect&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Move any startup code that needs the window from &lt;code&gt;FinishedLaunching&lt;/code&gt; to after &lt;code&gt;base.WillConnect&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Review quick-action and foreground/background handlers.&lt;/li&gt;
&lt;li&gt;Delete &lt;code&gt;bin&lt;/code&gt; and &lt;code&gt;obj&lt;/code&gt;, rebuild, and test on an &lt;strong&gt;iOS 27&lt;/strong&gt; simulator or device. An iOS 26 device will not show the problem.&lt;/li&gt;
&lt;li&gt;Test the login, deep-link and notification-tap flows explicitly, warm and cold. They compile fine when broken.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Apple, Technote TN3187, &lt;a href="https://developer.apple.com/documentation/technotes/tn3187-migrating-to-the-uikit-scene-based-life-cycle" rel="noopener noreferrer"&gt;Migrating to the UIKit scene-based life cycle&lt;/a&gt;. The timeline, the migration table from &lt;code&gt;UIApplicationDelegate&lt;/code&gt; to &lt;code&gt;UISceneDelegate&lt;/code&gt;, and the "should I migrate" criteria.&lt;/li&gt;
&lt;li&gt;Apple, &lt;a href="https://developer.apple.com/documentation/ios-ipados-release-notes/ios-ipados-27-release-notes" rel="noopener noreferrer"&gt;iOS &amp;amp; iPadOS 27 Release Notes&lt;/a&gt;, UIKit deprecations: "Apps built with the latest SDK must adopt the scene-based life cycle or they fail to launch."&lt;/li&gt;
&lt;li&gt;Apple, WWDC25 session 282, &lt;a href="https://developer.apple.com/videos/play/wwdc2025/282/" rel="noopener noreferrer"&gt;Make your UIKit app more flexible&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Apple Developer Forums, threads &lt;a href="https://developer.apple.com/forums/thread/788293" rel="noopener noreferrer"&gt;788293&lt;/a&gt; and &lt;a href="https://developer.apple.com/forums/thread/789004" rel="noopener noreferrer"&gt;789004&lt;/a&gt;, where an Apple engineer confirms the assert applies only when building with the new SDK.&lt;/li&gt;
&lt;li&gt;dotnet/macios issue &lt;a href="https://github.com/dotnet/macios/issues/26837" rel="noopener noreferrer"&gt;#26837&lt;/a&gt;, "[PSA] Starting with Xcode 27 (and iOS 27) you MUST use the Scene life cycle". Community write-up with the crash report and the manual fix.&lt;/li&gt;
&lt;li&gt;dotnet/macios release &lt;a href="https://github.com/dotnet/macios/releases/tag/dotnet-10.0.1xx-xcode27.0-10722" rel="noopener noreferrer"&gt;.NET 10 - Xcode 27.0 support&lt;/a&gt;, which recommends MAUI 10.0.110 or later.&lt;/li&gt;
&lt;li&gt;dotnet/maui pull request &lt;a href="https://github.com/dotnet/maui/pull/38601" rel="noopener noreferrer"&gt;#38601&lt;/a&gt;, "[iOS/MacCatalyst] Adopt scene lifecycle and AppActions dispatch", and its SR11 backport &lt;a href="https://github.com/dotnet/maui/pull/38664" rel="noopener noreferrer"&gt;#38664&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;dotnet/maui issue &lt;a href="https://github.com/dotnet/maui/issues/38992" rel="noopener noreferrer"&gt;#38992&lt;/a&gt;, an existing app on MAUI 10.0.110 still failing until the files were added by hand.&lt;/li&gt;
&lt;li&gt;dotnet/maui &lt;a href="https://github.com/dotnet/maui/blob/main/src/Templates/README.md" rel="noopener noreferrer"&gt;Templates README&lt;/a&gt;, section "iOS and Mac Catalyst scene lifecycle". Currently the most complete migration note from the MAUI team.&lt;/li&gt;
&lt;li&gt;Microsoft Learn, &lt;a href="https://learn.microsoft.com/en-us/dotnet/maui/user-interface/controls/window?view=net-maui-10.0#ipados-and-macos-configuration" rel="noopener noreferrer"&gt;.NET MAUI Window, iPadOS and macOS configuration&lt;/a&gt; and &lt;a href="https://learn.microsoft.com/en-us/dotnet/maui/fundamentals/app-lifecycle?view=net-maui-10.0#ios-and-mac-catalyst" rel="noopener noreferrer"&gt;App lifecycle, iOS and Mac Catalyst&lt;/a&gt;. Both still describe scenes as a multi-window option and do not yet mention that adoption is mandatory.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Tested with Xcode 27.0 (27A266a), .NET SDK 10.0.401, MAUI workload 10.0.110, iOS 27.0 simulator, and Mac Catalyst on macOS 27.0.1. The two production fixes described here were committed on October 5, 2026.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This article was co-authored with Claude (Anthropic), which helped reproduce the crash on the iOS 27 simulator, trace the fix through the .NET MAUI source and templates, and collect the references.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>ios</category>
      <category>mobile</category>
      <category>dotnetmaui</category>
    </item>
  </channel>
</rss>
