Guide

Build your .NET MAUI app for iOS without owning a Mac

You can write a .NET MAUI app in Visual Studio on Windows. The iOS build still needs a Mac, because it needs Xcode. Microsoft's answer is Pair to Mac, which drives a Mac over SSH. This page shows a simpler route we tested: install .NET on a Mac and build with dotnet in its terminal.

Why MAUI needs a Mac for iOS

Microsoft's Pair to Mac page is plain about it. Building iOS apps requires Apple's build tools, and they only run on a Mac. Visual Studio on Windows sends the build to a Mac over SSH. The Mac compiles and signs the app.

The old way around this is gone. Microsoft retired Visual Studio for Mac on August 31, 2024. On a Mac you now use the dotnet command line, or an editor such as VS Code with the C# Dev Kit.

The route we tested: dotnet on the Mac

Connect to the Mac over SSH and install the .NET SDK. Then add the MAUI workload and build. Our test used Microsoft's install script and a new app from the MAUI template. This is the build job from the workflow, exactly as it passed on GitHub's macos-26 runner on October 19, 2026.

name: maui-ios
on: workflow_dispatch
jobs:
  build-ios-xcode-26-6:
    runs-on: macos-26
    steps:
      - name: Install .NET SDK 10.0.401 into the home folder
        run: |
          curl -sSL https://dot.net/v1/dotnet-install.sh -o dotnet-install.sh
          bash dotnet-install.sh --version 10.0.401 --install-dir "$HOME/.dotnet"
          echo "DOTNET_ROOT=$HOME/.dotnet" >> "$GITHUB_ENV"
          echo "$HOME/.dotnet" >> "$GITHUB_PATH"
      - run: dotnet --version
      - name: Install the MAUI workload (workload set 10.0.401)
        run: time dotnet workload install maui --version 10.0.401
      - run: dotnet workload --info
      - run: sudo xcode-select -s /Applications/Xcode_26.6.app
      - run: xcodebuild -version && sw_vers
      - run: dotnet new maui -n MauiDemo
      - run: grep -n "TargetFrameworks" MauiDemo/MauiDemo.csproj
      - name: Build for the iOS Simulator (Debug)
        run: time dotnet build -f net10.0-ios -c Debug
        working-directory: MauiDemo
      - name: Build for a device, unsigned (Release, ios-arm64)
        run: time dotnet build -f net10.0-ios -c Release -r ios-arm64 -p:EnableCodeSigning=false
        working-directory: MauiDemo
      - name: Publish for a device, unsigned
        continue-on-error: true
        run: time dotnet publish -f net10.0-ios -c Release -r ios-arm64 -p:EnableCodeSigning=false
        working-directory: MauiDemo
      - run: find bin -maxdepth 5 \( -name "*.app" -o -name "*.ipa" \) -print -exec du -sh {} \;
        working-directory: MauiDemo

On your own Mac, run the same commands over SSH. The install script puts .NET in your home folder, so it needs no sudo. Set these two variables in each new terminal, or add them to ~/.zshrc:

export DOTNET_ROOT=$HOME/.dotnet
export PATH=$PATH:$DOTNET_ROOT:$DOTNET_ROOT/tools

Install the full maui workload, not maui-ios. Our first run tried maui-ios alone. The restore failed with NETSDK1147, because the template also targets Android and the android workload was missing.

EnableCodeSigning=false lets you test a device build with no certificate. The result cannot go on a phone or to Apple. It only proves that your code compiles for ios-arm64.

What the run printed

These lines are copied from the job log. The whole job took 3 minutes 21 seconds.

Xcode 26.6
Build version 17F113
 Workload version: 10.0.401
Successfully installed workload(s) maui.
  MauiDemo -> .../bin/Debug/net10.0-ios/iossimulator-arm64/MauiDemo.dll
Build succeeded.
Time Elapsed 00:00:23.70
  MauiDemo -> .../bin/Release/net10.0-ios/ios-arm64/MauiDemo.dll
Build succeeded.
Time Elapsed 00:02:06.70
  Created the package: bin/Release/net10.0-ios/ios-arm64/publish//MauiDemo.ipa
 97M	bin/Debug/net10.0-ios/iossimulator-arm64/MauiDemo.app
 16M	bin/Release/net10.0-ios/ios-arm64/publish/MauiDemo.ipa

The simulator build took 24 seconds. The device build took just over 2 minutes, since a Release build compiles ahead of time for arm64.

Match the workload to the Xcode

Each .NET for iOS release needs one exact Xcode version. The release notes on GitHub, in dotnet/macios, name it. As of October 19, 2026, these are the .NET 10 workload sets:

  • 10.0.402.1 (iOS 27.1.10018) needs Xcode 27.1. It came out on October 19, 2026, with .NET SDK 10.0.402.
  • 10.0.402 (iOS 27.0.10722) needs Xcode 27.0.
  • 10.0.401 (iOS 26.5.10318) needs Xcode 26.6. This is the pair we ran.

We also ran the wrong pair on purpose. With workload set 10.0.402.1 and Xcode 26.6 selected, the build stopped with this error:

error : This version of .NET for iOS (27.1.10018) requires Xcode 27.1.
The current version of Xcode is 26.6. Either install Xcode 27.1, or use
a different version of .NET for iOS.

For Xcode 27.1, the default on our Macs, the release notes give this pair. We could not run it, since GitHub's macos-26 image has no Xcode 27.

bash dotnet-install.sh --version 10.0.402 --install-dir "$HOME/.dotnet"
dotnet workload install maui --version 10.0.402.1

Microsoft's other fix is to keep several Xcodes and switch between them. Check which one is active, list the installed ones, and pick one:

xcode-select -p
ls -d /Applications/Xcode*.app
sudo xcode-select -s /Applications/Xcode.app
xcodebuild -version

On a MacRun Mac, /Applications/Xcode.app is Xcode 26.6 and /Applications/Xcode-27.1.app is Xcode 27.1, the default. On another Mac, use the exact app name that ls prints. You can also set DEVELOPER_DIR for one terminal session instead. Microsoft also notes a breaking change for Xcode 27. iOS apps must adopt the UIScene lifecycle, or they crash at launch.

Run, sign and publish on the Mac

To launch the app in the Simulator, Microsoft's CLI guide uses the Run target. You watch the Simulator through the Mac's screen, from Windows over VNC.

dotnet build -t:Run -f net10.0-ios

For the App Store, add your signing identity and profile. This is Microsoft's example with net8.0 changed to net10.0. We could not run it, since our test had no certificate.

dotnet publish -f net10.0-ios -c Release -p:ArchiveOnBuild=true \
  -p:RuntimeIdentifier=ios-arm64 \
  -p:CodesignKey="Apple Distribution: John Smith (AY2GDE9QM7)" \
  -p:CodesignProvision="MyMauiApp"

The .ipa lands in bin/Release/net10.0-ios/ios-arm64/publish. Microsoft points to Transporter for the upload. Our guide to publishing without a Mac covers API keys and TestFlight.

Pair to Mac from Visual Studio: what the docs require

You may prefer to stay in Visual Studio on Windows. Pair to Mac then needs the following, per Microsoft's docs:

  • Remote Login on in the Mac's Sharing settings, with full disk access for remote users.
  • Xcode installed and opened once. Pair to Mac cannot install Xcode.
  • Mono installed, and Rosetta on an Apple silicon Mac.
  • A Mac that Windows can reach. If it is not listed, use Add Mac and type its address.

Visual Studio then installs .NET on the Mac over SSH. On a MacRun Mac, the address would be its Tailscale address. We have not tested Pair to Mac over Tailscale, so treat that as untested. The dotnet route above is the one we ran.

Who should not rent a Mac for this

  • Build only. The workflow above runs on GitHub as is. Public repositories pay nothing for standard runners. Private ones pay $0.062 a minute for macOS, per GitHub's billing page. Our 3 minute 21 second job bills as 4 minutes, about 25 cents.
  • Debug on your own iPhone. Hot Restart in Visual Studio 2022 deploys Debug builds to a USB iPhone without a Mac. It is not supported in Visual Studio 2026.
  • Daily iOS work. If you need the Mac every day, buying one may cost less over a year.

When a rented Mac fits

A CI job cannot show you the app. A rented Mac gives you the Simulator, the debugger and a signing setup you can fix by hand. A MacRun day pass is a dedicated M6 Mac mini for $15, and a week is $59. Xcode 27.1 is the default and Xcode 26.6 is installed too. .NET is not preinstalled. You install it with the commands above. The workload install took 34 seconds in our test.

The Mac is wiped when the pass ends. Push your code and copy your .ipa off before then. See Xcode from Windows for the remote screen setup.

Frequently asked questions

Can I build a .NET MAUI iOS app on Windows without a Mac?

+

Not a full build. Microsoft's docs say the iOS build tools only run on a Mac. Visual Studio sends the build to a Mac through Pair to Mac, or you build on a Mac with dotnet directly.

Which Xcode does .NET 10 MAUI need?

+

It depends on the workload set. On October 19, 2026, workload set 10.0.402.1 needs Xcode 27.1, and 10.0.401 needs Xcode 26.6. The dotnet/macios release notes name the Xcode for each release.

Why does my MAUI build say it requires a different Xcode?

+

Your workload and your selected Xcode do not match. Switch Xcode with sudo xcode-select -s, or install the workload set that matches your Xcode with dotnet workload install maui --version.

Is Visual Studio for Mac still available?

+

No. Microsoft retired Visual Studio for Mac on August 31, 2024. On a Mac, use the dotnet command line or VS Code with the C# Dev Kit.

Does Pair to Mac work with a cloud Mac?

+

Microsoft's requirement is a Mac that Windows can reach, with Remote Login on. We have not tested Pair to Mac over Tailscale. Building with dotnet on the Mac's terminal is the route we tested.

Can I build an unsigned MAUI iOS app?

+

Yes. In our test, dotnet build with -r ios-arm64 and -p:EnableCodeSigning=false built a device app with no certificate. It cannot be installed or uploaded until you sign it.

Related guides