Introduction
Thank you for using this project builder that will enable you to build and deploy Unity-powered iOS and macOS apps in Windows. I hope you enjoy using it as much as I enjoyed creating it!
This documentation describes how to set up and use the builder environment. It is not daunting, it is detailed - when you need docs to solve a problem, you want maximum details, don't you?
⚖️ Legal foreword
In the license that comes with their SDKs, Apple Inc. states that these SDKs shall only be deployed on Apple-branded computers. So, for the rest of this document, I will assume that your Windows computer is an Apple-branded computer running Windows through Boot Camp (or any other sort of emulation or virtualization). I do not condone the use of this program outside this scope, and cannot be held responsible for any misuse you make of it.
Onboarding
In order to use this Windows builder, you need to borrow a few things from your Mac:
- Migrate the Apple SDKs
- Migrate your existing Apple code signing identities (optional)
Click-once scripts are provided that will do this gathering for you. Read on and follow these directions step by step.
Migrating the Apple SDKs and your signing identities
- Prepare a FAT-formatted USB key (not NTFS! because macOS can't write to NTFS keys), or have one with at least 200 Mbytes free.
- Open the
SDK migration assistant
shortcut from your Windows Start menu (it's in All Programs > Project Builder for Unity
)
- Drag both these files to your USB key (copy them there). We'll use them in macOS.
-
Login onto macOS.
💡 You can also borrow or rent a Mac for one hour to perform this step, if you don't want or are unable to use yours (e.g. lack of disk space).
-
Make sure Xcode is installed and up to date (very important! You can install or update it for free from the Mac App Store).
💡 If your Mac is too old for the latest Xcode, here's how to proceed: download the latest Xcode from https://xcodereleases.com, extract it and put the resulting Xcode.app in your Applications directory (backup your previous Xcode first). The new Xcode.app will have a "forbidden" 🚫 icon on it as it's incompatible with your Mac, but this won't prevent the builder's migration tools to locate it and make use of it anyway. Once the SDK migration is done, you can move the new Xcode to trash and put your previous, working one back in place.
- Open your USB key and double-click the item called
Migration assistant (step 1, Mac).command
.
-
Let it do its job, and when it tells you so, reboot into Windows.
💡 You will notice that a SDK.zip
, and possibly a Keychain.zip
file too, will have been created on your USB key. They contain the files we need.
- Login onto Windows.
- Open your USB key and double-click the item called
Migration assistant (step 2, PC)
.
- The Apple SDKs unpack. Wait for them to finish.
Now you can build iOS and macOS projects.
But in order to deploy them to your device/App Store/TestFlight, you'll also need them to be signed with a digital signing identity; so read on.
Setting up your default signing identity
A signing identity, in a nutshell, is the combination of 3 things:
- a certificate that attests of your identity (as a software producer),
- the private key (and its protecting passphrase), out of which this certificate was created by Apple and granted to you,
- and a provisioning profile that tells which iDevice(s) are allowed to install the app that is signed with such a certificate.
Apps built for the Apple ecosystem need to be signed (unless they're built for "jailbroken" devices).
IMPORTANT: As long as you haven't set up a signing identity, you will not be able to deploy your apps, unless to jailbroken devices. Unsigned or "pseudo-signed" apps can only be deployed on jailbroken devices, whereas fully signed apps can be deployed on any device, jailbroken or not.
Of course, each app you build can have a specific signing identity specified for it (like in Xcode), but for convenience, the builder allows you to specify a default one that will be proposed each time you build a new app. Here is how to set it up, so that the builder produces signed apps by default.
- On Windows, open the Keychain Tool shortcut from your Start menu (it's in
All Programs > Project Builder for Unity
).
If your Mac was previously used to build iOS apps, the Migration Assistant
will have found your existing signing identity and you should see that appear in the Keychain Tool.
💡 That is to say, you should see certificates named iPhone Developer: <Your Name> (or Apple Developer: <Your Name>) and/or iPhone Distribution: <Your Name> (or Apple Distribution: <Your Name>), along with at least one private key and probably some provisioning profiles.
On the other hand, if you see nothing in the listbox like on this image, then it means no existing signing identity was imported from your Mac, and you need to create one from scratch.
Another possibility is that you see your certificates but no private key: it usually means you haven't allowed the Migration Assistant
script access to your Mac's Keychain to locate and export your certificates' associated private keys.
If you're in either of those cases, please see How to set up a signing identity to create a new signing identity from scratch.
Assuming you now have all 3 required elements for a signing identity (certficate, private key, provisioning profile):
-
Select the provisioning profile to use by default.
💡 This will auto-select the certificate it contains (if your profile contains multiple certificates, pick the most suitable), and the private key that was used to create it.
- Enter that private key's passphrase.
When everything matches, the lock turns ✅ green. This will be your default signing identity.
Note that for a generic identity, you should select a generic, development provisioning profile, i.e. one that's not specific to a particular app. Later, when you build your apps for release, you will select for each of them a distribution certificate and a specific provisioning profile.
VoilĂ . Your build environment is ready!
Building and deploying your first app
Now that everything is set up, let's introduce your new workflow for building apps! 🎉
Basic build workflow
For those who prefer a 🎥 YouTube video, click here.
-
Create your iOS or macOS project within Unity on Windows (select File - Build Profiles or Ctrl+Shift+B, then select iOS or macOS, and hit Build.)
Unity will prepare a directory containing a Xcode project. Save it in C:\Darwin
, e.g. C:\Darwin\Your_Unity_Project
.
💡 It is recommended that you start with one of the Unity sample projects to see how things work, instead of building your own project headfront - complex projects might require extra flags or build dependencies, that will be covered later in this guide.
-
Start the "Project Builder for Unity" from your Start menu (it's located in the "Project Builder for Unity" programs group in the Start menu, under "All programs").
💡 You can leave it open in the background, if you build multiple times a day.
- Pick up the location of the Xcode project created by Unity using the Browse button (top right of the UI).
- Check that your signing identity is correct (green lock) and that the provisioning profile is suitable (e.g. "Development" profile to deploy your app to your device immediately), review the build options (you can leave everything default for simple projects), then hit Build.
-
Grab a cookie while your project is being built. 🍵 🍪
💡 On a modern machine this can range from a few seconds to a few minutes. The number of CPU cores is significant, and also the number of dependencies that your app has to rebuild from source. The longest part, if your project uses the Unity IL2CPP backend, will probably be the compilation of the il2cpp library.
If all went well, you should be invited to scan a QR code to deploy your app to your iDevice (unless you chose to do a "distribution" build for the App Store).
Problem encountered during the build phase? 👉 Head up there: Troubleshooting.
How to deploy your app to your device
Signed apps: typical way (OTA)
If you opted so, after a successful compilation, your app becomes be available for OTA (over the air) installation to your device. Just flash the QR code with your mobile device and follow the on-screen instructions to install your freshly built app to your device. Note that:
- The app must be signed with a DEVELOPMENT certificate (or an enterprise one). OTA deployment with other types of certificates is blocked by Apple (that would be a convenient method of app piracy).
- Your iDevice must have a working Internet connection to download the install manifest. This is a tiny resource that is generated online during the process and downloaded to your device to allow installation.
In all cases, after a successful compilation, your apps can be found in the Packages
subdirectory of your project.
These packages are ready to be deployed on your iDevice (or your Mac), sent to your testers, or uploaded to the App Store.
- To deploy a signed
.ipa
package to your iDevice, double-click it: a QR code appears.
- To upload it to the App Store / TestFlight, use the supplied App Store Connect upload tool.
Problem encountered during the deployment phase? 👉 Head up there: Troubleshooting.
If you haven't opted for the recommended way to deploy your app over the air, you can still deploy it manually. There are two cases.
Signed apps: iTunes sideloading
To install your app on your device the "manual" way (not OTA), you must ensure:
- that it has been signed with your digital identity at build time,
- that you have a valid provisioning profile for this device that allows it to install this app, either embedded during the building of the app, or already installed on your device.
If the above conditions are met, we can proceed:
- open iTunes
- Locate your device in the left-hand panel (connect it via USB if necessary) and click the little arrow next to it so as to develop its contents.
- Now drag-and-drop the created app package (
.ipa
file) onto any of these categories in iTunes.
- Sync, and your app icon appears on your decice's screen.
This is called sideloading. Please note that these instructions are known to work for iTunes 12.7 (see this thread on StackOverflow). It has been reported to me that in some cases you need to select and copy (CTRL+C) the .ipa file then paste it (CTRL+V) on the device's Music tab in iTunes. You might need to experiment a bit if you use another version (please don't ask me: I don't know how other iTunes versions are meant to work.)
Unsigned apps
💡 Apple would prefer that you do not do that.
If you don't have any digital signing identity from the Apple developer program, you can only deploy your app if your device is jailbroken.
Jailbreaking your device is the process of lifting some security barriers that Apple put on it, that prevent it to run unsigned code (which is literally code for which we can't know the author. You understand why it's an important security restriction). Although Apple would prefer you to abstain, lifting this restriction by "jailbreaking" is authorized, safe (as long as you understand what you are doing), and reversible.
To jailbreak your device, first check its model and OS version in Preferences > General > Informations. Once you know these, head up to http://jailbreak-me.info and let the website select the right jailbreak program for you. Make sure you have a good ad blocker enabled so as not to download anything else than the jailbreak program (I am not affiliated with this website nor with the ad campaigns it runs - you have been warned). Get the jailbreak program that's right for your device (they're free), run it and follow the instructions.
Now on to installing your unsigned app by hand on a jailbroken device:
- Automatically with iTunes: Install AppSync first (Cydia package hosted in the http://www.pmbaty.com/cydia source. This package is also provided in the
AppSync
directory of the builder, so that you can copy it to your device and install it over SSH using the dpkg -i AppSync.deb command). Once this is done, simply double-click on the app package (.ipa file) to open it in iTunes, and then drag the app from the app library to your device in the left hand panel in iTunes. Sync. Your app icon appears.
- Automatically with iFile: Install iFile (Cydia package). Copy your app's .deb file to your device. Open it with iFile and choose Install. Your app icon appears.
- Automatically over SSH: Copy your app's .deb file to your device. Install it over SSH using the dpkg -i YourApp.deb command (as
root
). Your app icon appears.
- Manually: Copy the YourApp.app folder created during the build into the /Applications directory on your device, set the "execute" flag onto the main binary, and run
su -c /usr/bin/uicache mobile
to refresh the SpringBoard icons. Your app icon appears.
Problem encountered during the deployment phase? 👉 Head up there: Troubleshooting.
Testing and debugging your app
With the Remote Debug Console
While you test your app on your device, it's often useful to see what happens in the background. Having the ability to watch a live console output
, like a command-line app, is very handy indeed.
The good news is that this builder allows you to do so! 🎉
Open the remote debug console by clicking on the link in the builder window, or from your Windows start menu: All Programs > Project Builder for Unity > Remote debug console
.
This cute piece of witchcraft allows any iOS or macOS app to send all that it would normally write to the standard output and the standard error streams stdout
and stderr
, to a window on your PC. That is to say, everything that is logged in your app using NSLog()
macros, with printf()
, fprintf(stderr, ...)
or with cout << ...
and cerr << ...
will be caught, even stack dumps when it crashes, sent over the network to display before your eyes, on your computer, while your app is running on your device. And this works even for release builds.
It also catches the Apple system logs. If your device is plugged to your computer with a USB cable and if you have iTunes installed, you can catch the syslogs in glorious ANSI colors. This is most valuable to find out what happens when your app refuses to launch (for example, because of an invalid entitlement). To opt for it, tick the "System logs" checkbox in the debug console's bottom status bar. Beware: iOS is a very chatty system.
Data filtering. Say you've enabled all checkboxes and are now receiving encyclopediae of data flooding the console window so fast that you can't read anything. Halp. You may filter all this garbage to either exclude, or allow, or highlight a character string. No less valuable.
When you need a break, Stop and resume logging by clicking the "Stop / Record" button (top right of the console window).
To enable debug cuteness, tick the "Enable remote debug console" checkbox in the builder window, and make sure the IP address that displays next to it is the IP of your computer. The app will send its data over TCP ports 5001 and 5002, so you should also make sure that the remote debug console window is open on your PC and that no firewall or other "Internet Security" stupidware gets in the way (at least for these protocol/port pairs).
NOTE: each time you toggle this checkbox, the project must be rebuilt from source to embed or disembed some additional code. If you get linker errors such as: duplicate symbol _main
or ld: entry point (_main) undefined
, then it means you toggled the remote debug console on/off without doing a full rebuild. Simply tick the "Rebuild all files (once)" checkbox to fix it.
Problem encountered debugging your app? 👉 Head up there: Troubleshooting.
With .dSYM files
The builder has the ability to produce .dSYM
bundles, like Xcode. .dSYM
bundles are the format used by Apple to store a program's debug information (used to find out what line number in which source file corresponds to a particular crash address).
To create a companion .dSYM
file when you build your app, check the relevant checkbox in the builder UI.
Uploading your app
To the App Store or TestFlight
In order to submit an app to the App Store, you need to send it using your App Store Connect account to the App Store Connect website. This can be done using the App Store Connect upload tool.
You need a few things to upload your app to App Store Connect. Checklist:
- A build of your app, signed using a distribution certificate, with an App Store type provisioning profile, that has an explicit app ID in it (phew, 3 criteria in one!),
- A valid app record for your app on the App Store Connect website,
- Your App Store Connect account login and password (possibly an app-specific password if 2-factor authentication is enabled on your account).
First, you need a build of your app signed (or re-signed) with a signing identity that's fit for App Store submissions. App Store Connect will only accept apps that are signed using a distribution certificate, so make sure your app is signed with such a certificate. In case it's not, see the signing identities section of this document to learn how to get one.
💡 The App Store Connect upload tool allows you to re-sign already packaged apps before submission.
You also need your app to embed a provisioning profile that has an explicit app ID in it. See the provisioning profiles section of this document to learn about the difference between explicit and wildcard app IDs.
💡 Basically, a provisioning profile with an explicit app ID is made for one single app, whereas a provisioning profile with a wildcard app ID can be reused across multiple apps. Apple doesn't allow the latter for App Store submissions. So, if your current provisioning profile is a wildcard one, you will need to create an explicit app ID for your app first, then an explicit provisioning profile out of it.
You also need your provisioning profile to be fit for App Store submissions. Choose App Store (under the Distribution category) when creating such a provisioning profile.
Now, if you haven't already done so, you need to prepare a record for your app on the App Store connect website. Log in to the App Store Connect website, go to My Apps and click the plus sign to create a new record. Fill the form that describes your app: the platform on which it'll run, the app's name, its main language, its bundle identifier (which must match the one you specified in your app's make.cmd
file or in the Unity editor if your app is a Unity app), and a unique string for you to identify it easily.
If your account uses two-factor authentication, Apple will require you to set up an app-specific password to use in the upload tool in place of your App Store Connect account's main password. Click here to find out how to create an app-specific password.
When you're set, fire up the App Store Connect upload tool. Browse to the .ipa file you want to upload, specify a signing identity suitable for the App Store, fill in the fields as appropriate (your login/password or app-specific password), and hit the Upload button. A window will pop up with a progress bar during which your app will be delivered to App Store Connect.
After the upload is done, Apple will send you an e-mail at your App Store Connect account's address to tell you whether it accepted your app for delivery (w00t!) or whether there are still some problems to fix in it. Pay attention to these messages if you want to correct them quickly and efficiently. A Google search on a particular phrase usually helps in understanding what's wrong.
Once your build has been accepted by Apple, it's up to you to choose what to do with it: for example, distribute it to your beta testers through the TestFlight program, or send it to the App Store. All the rest of this process now takes place on the App Store Connect website, which you will soon be accustomed to use. 😉
Problem encountered during the publication phase? 👉 Head up there: Troubleshooting.
To a Cydia Store
Cydia is an alternate store for jailbroken devices.
To send your app to Cydia, it depends on which source (repository) you want to host it. For example, submissions on the BigBoss repository happen here: http://thebigboss.org/hosting-repository/submit-your-app. For the other repositories, please refer to their owner's website. I don't provide a list, as every now and then the URLs change.
How to set up your signing identity
Uh - What's that?
It's a stamp with your face on it, barred with Apple's pictorial cancellation along with the IDs of your test devices, right on your app. So that anybody looking at it knows who made it, under the authority of whom, and whose devices it may be installed to.
A signing identity is the combination of 3 things:
- a 📜certificate that attests of your identity (as a software producer) and granted to you by a top authority,
- the 🔑private key (and its protecting passphrase), out of which this certificate was created by Apple (the top authority),
- and a 📝provisioning profile that tells which iDevice(s) are allowed to install the app that is signed with such a certificate.
Apple's policy regarding code signature is different depending on whether iOS or macOS is the target:
- iOS apps require to be signed with a signing identity in order to be deployed on non-jailbroken (stock) devices, and/or submitted to TestFlight and the App Store.
-
macOS apps require to be signed only when submitted to the App Store.
💡 Note however that macOS insists more and more in presenting unsigned apps as "potentially dangerous" or "unsafe"; a wise developer will want to avoid that.
Bottom line: better sign every piece of code you produce to be on the safe side.
💡 How it works technically: the 🔑private key is used to seal the 📜certificate into the app, along with a hash of the app's contents, using asymetric cryptography. As a result, any modification of a single byte in there would invalidate the certificate that's sealed in it (the signed hash would differ). On top of that, the 📝provisioning profile (which is embedded into the signed app) seals the identities of which devices (typically yours and your testers') are allowed to install that app. The result is a quite locked down, but very secure, app ecosystem for all the Apple products.
As usuals with Apple, nothing's free. To obtain a code signing certificate from them, you must enroll in their Apple Developer Program ($99 / year).
If you're already member, skip the following section. Else, read on:
Can I use the "free" signing identity that Apple offers to Xcode users?
Alright. It is not strictly necessary to enroll in the Apple Developer Program, and you can reuse an Xcode-supplied code signing identity, but in almost everybody's opinion the benefits of enrolling in the Apple Developer Program (and paying $99 to Apple) exceed the expense. Look:
| Free | Paid |
Supports OTA (Wi-Fi) deployment | ❌ | ✅ |
Supports all application services | ❌ | ✅ |
Validity | 10 days | 1 year |
Simply put, if you don't, you will use code signing certificates generated by Xcode that have a very limited lifetime (10 days), so be prepared to migrate them each and every week from your Mac and rebuild the apps that use them to make them installable again.
Not only that, but by Apple's design, over-the-air deployment can't be used with free certificates, which means you'll need to deploy your apps by hand on your devices (e.g. using iTunes sideloading), and when you do this 20 times a day or more, that's a consequent waste of time.
A lot of advanced features related to signing identities, application entitlements, device provisioning, are not available to free users (or seriously hindered), which means you won't be able to test many things that you code. That's why my advice is to bite the bullet and go the official way, unless you're really budget-constrained - and be prepared for a lot of hassle if you choose to go the narrow way.
How to set it up
Now that it's made clear why a robust signing identity is important, here's how to set up one with this builder. 3 cases:
Case where you already have these files on Windows
In order to sign your app with a signing identity that you already have somewhere on disk (for example, you kept these files in a backup), simply put these certificate(s) (.cer
files), private key (.key
file) and provisioning profile(s) (.mobileprovision
or .provisionprofile
files) in the Keychain
directory where the Project Builder for Unity is installed.
The build tools will then be able to pick them up and let you select the identity to use.
Case where you already have a signing identity on Mac
All code signing identities found on your Mac are automatically migrated along with the Apple SDKs by the Migration Assistant
when you run it... provided you allowed the Migration Assistant
to access the Mac's Keychain.
If you've been wary and refused that permission, or if you want to grab a signing identity that was created after the Apple SDKs were migrated, no worries: simply run the Migration Assistant
again.
💡 In case your Mac doesn't ask for your permission anymore, it means your choice for this authorization was "remembered" by macOS. Logging out and back from macOS should fix it and allow it to ask you again.
Alternatively, you can open the Mac's Keychain Access app, and export your private key and code signing certificate into a .p12
bundle, sealed by a passphrase, that you will import on the Windows side in the Keychain Tool
by clicking the Import .p12 from Mac button.
Case where you don't have any signing identity yet
First, you need to enroll in the Apple Developer Program: https://developer.apple.com/programs/
💡 If you wonder whether you can use a free Xcode certificate, read about it a few paragraphs above.
When you're enrolled, you need to generate a signing identity. Here's how:
- Open the supplied
Keychain Tool
.
-
Click on the New private key button and fill the form to create a new private key 🔑.
💡 Choose an alphanumeric-only passphrase (a mix of a-z
, A-Z
, 0-9
) to protect your private key. Special characters are known to cause problems in the build scripts.
-
Highlight (select) your new private key in the Keychain, then click on the New certificate signing request button. This info will be used by Apple to name your certificate.
💡 You get a .certSigningRequest
file. Save it somewhere on your disk. You will need to upload this file to Apple.
- Now visit the Apple Developer Portal at https://developer.apple.com/account/ and login with your Apple Developer ID.
- Click on Certificates, Identifiers & Profiles in the menu.
- Create the 📜 certificates you need by uploading your
.certSigningRequest
file (click the +
button and follow Apple's instructions).
- When the Apple website created your certificate, download it and save it in the
Keychain
directory, alongside with your private key.
Finally, you need a 📝provisioning profile, to tell which iDevices are allowed to install your apps. Read on the next section to find out how to create that.
All cases
Once you have your private key and certificate installed in the Keychain
directory, they become usable by the builder. Please note that only valid, non-expired certificates will appear in the certificates list.
You may then deploy your signed apps to all the devices that are listed in the app's provisioning profile with the generated .ipa, automatically after each build (OTA), or manually (see How to deploy.)
Creating a provisioning profile
Uh - What's that?
Provisioning profiles are authorization files that, when installed on an iDevice, tells the OS on which criteria it is allowed to let you install an app. When you install a custom app on your iDevice, the OS complies to the provisioning profiles it has to decide whether it lets the app in or throws it away. App Store apps are not concerned, because they are all re-signed by Apple as part of the app submission process (this is why App Store apps are always accepted by iOS.)
If you want to deploy your own signed apps on an iDevice, you need a provisioning profile for it.
Can I reuse the one I already have from Xcode?
Sure. Just make sure that the App ID you specify in Unity matches the App ID in the provisioning profile.
If it's a "wildcard" provisioning profile, you're good. Else, if it's an "explicit" one, the IDs must match exactly. To find out the difference between a "wildcard" app ID and an "explicit" app ID, see this page.
In all cases, if you have the slightest doubt, the safest way is certainly to obtain a new provisioning profile from the Apple Developer Portal.
How to create a provisioning profile
Provisioning profiles instruct an Apple device to let you install apps signed by yourself. Here is how to create one.
First, create an app ID:
- Open the Apple Developer Portal here: https://developer.apple.com/account/
- Click
Certificates, Identifiers & Profiles
.
- In the "Identifiers" section, click
App IDs
.
- Click the
+
button (top right) to create a new app ID.
-
Here, two alternatives:
-
Either you want a wildcard app ID (will match all your apps, very handy during development, but can't be used on the App Store):
- Under "App ID description", enter something like "
All apps created by me
" (word it as you wish).
- Under "App ID suffix", select the
Wildcard App ID
checkbox and enter com.yourdomain.*
in the bundle ID field (replace com.yourdomain
by your company's or your own top level Internet domain in reverse order, such as net.whizzbanggames for example) — and don't miss out the trailing .*
-
Then enable any services you think you may need in your apps below (see Adding Application Services)
💡 Note that not all services may be enabled with a wildcard app ID: some require an explicit app ID.
-
Or you want an explicit app ID (will match only one app, less handy for development, but mandatory for the App Store):
- Under "App ID description", enter your app's name.
- Under "App ID suffix", enter a unique ID for your app (no spaces, no punctuation, e.g:
MyCoolApp
) in the bundle ID field.
- Then enable any services your app uses below (see Adding Application Services).
- Click
Continue
, review your choices, and click Submit
.
Now, let's instruct which device(s) this provisioning profile will concern:
- In the "Devices" section from the left-hand menu, click
All
.
- Click the
+
button (top right) to register a new iDevice.
- Enter a friendly name for your iDevice (such as "
My iPad
") and its Unique Device Identifier (UDID). To display the UDID of a device:- Plug your device into iTunes.
- Click on its serial number in the iTunes window: the UDID displays.
- When it's displayed, right-click on it and choose "Copy" to have it in the clipboard.
- Click
Continue
, review your choices, and click Register
. Redo these steps for each iDevice you have.
Finally, create a provisioning profile for this app ID and the device(s) you just registered:
- In the "Provisioning profiles" section from the left-hand menu, click
All
.
- Click the
+
button (top right) to create a new provisioning profile.
- Select "Apple Development" if you want to deploy your app on your device immediately after compilation, or "Apple Distribution" if you want to use TestFlight and/or send it to the App Store.
- Click
Continue
, select the app ID you just created in the list, and click Continue
.
- Select the same certificate that you will use to sign your apps. If you use several certificates, select all of those.
- Select the device(s) on which you plan to install your apps, and click
Continue
.
- Name your provisioning profile (such as "
Generic provisioning profile
" if you used a wildcard app ID, or "Provisioning profile for MyCoolApp
" if you used an explicit one), and click Generate
.
Your provisioning profile is ready. You may download and save it in the Keychain
directory, alongside with your private key.
And now that you have a private key, a signing certificate and a provisioning profile with your app and device IDs in it, your signing identity is usable and you're good to go.
Adding Application Services
Application Services are a set of special capabilities (such as access to Apple Pay, iCloud, etc) that you can enable in Xcode for a particular app.
Enabling a service
This is done when you register your app ID on the Apple Developer portal here: https://developer.apple.com/account/
When you create an app ID, either generic (wildcard ID) or specific (explicit ID), you are allowed to choose which extra Application Services will be accessible to an app wearing this ID.
💡 Technically speaking, these services are called an app's entitlements.
Simply tick the relevant checkboxes to enable the services your app needs in the app ID section of the Apple Developer Portal.
💡 NOTE: not all services can be enabled if your app ID is a generic app ID. Some of them require that you use an explicit app ID.
Configuring a service
🔧 Configuring these services is done through an external .plist
file, a text-editable ASCII property list that will contain the app-specific entitlements data, that will be hard-stamped in the app binary at signing time. In a nutshell, this is a text file you create with the .plist
extension, and in which you put a dictionary of key/values like this:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.developer.associated-domains</key>
<array>
<string>applinks:some-machine.somedomain.com</string>
<string>applinks:other-machine.somedomain.com</string>
</array>
</dict>
</plist>
In this example, the Associated Domains app service is set to trust the some-machine.somedomain.com
and other-machine.somedomain.com
hosts. The key to use for this service is com.apple.developer.associated-domains
. The relevant part of the .plist
that configures this service is shown in red.
Then, simply tick the checkbox called Entitlements from .plist file in the builder window and browse to your .plist
file to tell the builder to stamp these entitlements in your app at code signing time.
💡 If your Xcode projet already has an entitlements.plist
file, simply merge your entitlements (by copy/pasting the red part in the example above, so that the plist
XML structure is preserved) inside the existing .plist
file.
💡 When you need to configure a particular service and you don't know which entitlement key(s) it uses, a good starting point is to read this page in the Apple online documentation to learn which key names are associated with a particular service. Then use Google to find out about its possible values.
Adding extra frameworks
In Apple's terminology, frameworks are a set of APIs (i.e. entrypoints to bits of native code) regrouped together in a bundle, that developers can use to extend their apps' functionality.
More specifically, frameworks are directories ending with .framework
that contain either the framework library (the framework's compiled dynamic library, or a file describing it) plus its associated header files, or the full source code for that library (such a file layout is OS-agnostic, yet when they are viewed on a Mac, those directories look like a regular file and you need to "show package contents" to browse inside them).
The Add extra framework button lets you add a framework that isn't already included in your Xcode project, just as you would add it to your project in Xcode.
Adding an Apple framework
The Add extra framework button opens the frameworks directory in the relevant Apple SDK. A typical framework list from the Apple SDK looks like this:
- 📁 Accelerate.framework
- 📁 Accessibility.framework
- 📁 AccessorySetupKit.framework
- ...
- (around 240 frameworks in the iOS SDK as of 2025-09)
- ...
- 📁 WebKit.framework
- 📁 WidgetKit.framework
- 📁 WorkoutKit.framework
Simply pick the framework you need among those. The builder will compose the right compiler and linker flags to use to include this framework during the build.
Adding a third-party framework
If it's not one of the stock Apple frameworks, you need to have this framework somewhere on your PC. Each third-party framework supplier provides instructions on how to use them and a download link (sometimes labelled "SDK").
The recommended way to add extra frameworks to your Xcode project is simply to regroup them in a subdirectory of the project root (for example, a subdirectory called 3rdParty
). The builder will notice them and automatically include them to your app, as needed.
In case you want to add a framework that's located elsewhere (for example, on a code repository), then click on the button, browse to the .framework
directory that you want to include, and validate with OK. The builder will set up for you the extra compiler and linker flags to use at build time to include the framework you selected.
.xcframework
bundles are supported too!
Multiple variants of the same framework for different operating systems (i.e. one variant for macOS, one for iOS, one for watchOS, one for tvOS...) are often distributed together in a .xcframework
bundle. When you feed it such a bundle instead of a regular .framework
directory, the builder is smart enough to pick the right variant according to your app's target system and architecture.
💡 Hint for advanced users: if you know the clang
compiler and ld64
linker syntax, you can also specify any extra flags of your choice here.
Example: how to add the Google Mobile Ads framework
This is an excellent example because the GoogleMobileAds
framework is known to require a bunch of dependencies that are not explicitly specified. Quite expectedly, Google doesn't simplify the task of people wanting to use their technologies on concurrent platforms (they'd rather have them all throw their iPhones and buy Androids). So, you click on the "Add framework" button and you locate the GoogleMobileAds.framework
directory that's in the Google Mobile Ads SDK that you downloaded from Google. But when you attempt to build your project after that, you get unresolved symbols
errors, telling you that the Google framework wants to call functions that are present in other frameworks that you also need to include in your project. The procedure to solve this problem is simply to resolve them one after the other. Taking the first one of the list, a Google search tells us that the missing symbol called "SecTrustCopyPublicKey" about which the linker complains is found in the Apple system framework called Security: see.
So, you should also add the Security
framework from Apple, rebuild, and see what comes next. Repeat the process until no more errors.
Handling CocoaPods dependencies
CocoaPods is a plugin manager for Xcode projects, that wasn't ever meant to run on Windows. It became the de facto standard on macOS to handle Xcode project dependencies, and many Xcode projects now rely on it.
Due to popular demand, an emulation layer for CocoaPods
is implemented in this builder as an opt-in daemon service.
💡 Emulation was necessary because the NTFS filesystem used by Windows has limitations that are definitely incompatible with what CocoaPods expects (some Pods create files with filenames that are forbidden on a NTFS filesystem). Thus, a Darwin-compatible lightweight UNIX subsystem had to be created to host the CocoaPods client.
This subsystem comes pre-installed with the toolchain, but not activated. If you want to use it, you'll need to acquire an activation key: simply follow the instructions when you start it the first time.
You may choose to use it, in which case read the following paragraph, and simply follow the instructions when asked (the build script will ask you what to do the first time it encounters a CocoaPods-based project).
Using the CocoaPods daemon
If CocoaPods is activated and the build script detects your project needs it, it will automatically issue a pod install
command to the CocoaPods subsystem to deploy and install your project's Pods dependencies, update the Xcode project, and then proceed to building your project once this is done. You typically don't need to do anything yourself. And, in an ideal world, everything will work just fine. 🤞
Since this subsystem is implemented as a daemon, it must be started first, which can take some time at the first call (around one minute, the time it takes for booting an operating system), after what it stays resident in memory until you decide to stop it.
Note that during that initial setup phase, you need a good and steady Internet downlink, else CocoaPods will likely issue this error: [!] Unable to add a source with url `https://github.com/CocoaPods/Specs.git` named `cocoapods`. You can try adding it manually in `/Users/cocoapods/.cocoapods/repos` or via `pod repo add`.
It simply means your connection to GitHub was interrupted, and CocoaPods isn't smart enough to recover from where it stopped.
Why make this an optional add-on? Because even if I spent an insane amount of time (i.e. months) figuring out a way to make it work, not all of those who build iOS or macOS apps on Windows also need CocoaPods to work on Windows, and trying to recover the development cost by raising the whole toolchain's would have turned out unfair for many. The legacy workarounds still work (e.g. downloading and laying out your project's dependencies by hand, although more fastidious), so this add-on is strictly optional.
Without the CocoaPods daemon
You may choose not to use it - and if you're a knowledgeable user, I would actually recommend that. Despite having become the standard, we don't live in an ideal world and CocoaPods has many problems, even on macOS.
- It will download gigabytes of data (the whole Pods specs master repository) from GitHub to figure out your app's dependency tree, which takes time; of these gigabytes of data only a few kilobytes(!) are actually useful to your application.
- It will rebuild all your dependencies from source, which is a consequent waste of time - especially for dependencies that never change.
- It sometimes fails at figuring out a compatible dependency tree that will build without errors, due to inconsistent filling of the Pod specification by the dependency authors.
- It is always possible to not use CocoaPods if you add the dependencies your project needs by hand, in the form of precompiled libraries and frameworks. The initial setup is more complex (because it's a manual process and you need to understand what you're bringing into your project), but once this is done your build times will decrease a lot.
If you choose not to use it, the workaround is to identify the frameworks, libraries, and/or bundles that CocoaPods is supposed to inject in your Xcode project (this can be done by reading the Podfile
), download these missing frameworks/libraries/bundles from their website (almost all of them provide a non-CocoaPods means of distribution in the form of a zip archive or similar), and inject them at build time using the "Add extra framework" and "Add binary" buttons. After that, either move the Podfile
away (e.g. rename it) or tick the "Skip CocoaPods" checkbox in the builder UI to let the build script move past the CocoaPods step.
Adding embedded binaries
This is normally automatic if those embedded binaries or frameworks are defined as such in your Unity-generated Xcode project file. The builder should simply detect them, and do the job appropriately.
In the case where it wouldn't be the case (you can tell so if you examine the contents of your MyAppName.app
directory after a successful build, and check whether your embedded binaries are there or not in the Libraries
or Frameworks
subdirectories), you may tell the builder explicitly to embed particular binaries or bundles by specifying them explicitly.
To do so, click the Add binary button (center right of the builder UI) and browse to the file or bundle you want embedded in your app. You can do so multiple times.
💡 Please note: specifying the embedded binaries on the command-line will add them to the list of embedded binaries that the builder found in the Xcode project. It will not replace them! So, if there's an error in your Xcode project file and because of that something isn't embedded at the right place, it's wiser to fix the Xcode project so that your extra binaries are embedded correctly instead of relying on an explicit directive to do the job.
Customizing your app bundle
The builder tries to interpret your Xcode project file to correctly embed all the resources your app needs in the final ipa
bundle. Yet there are cases, specially when complex third-party plugins are used, where some extra tweaking will be needed.
A "pre-packaging script" is an executable file that you specify (either .bat
, .cmd
, or even .exe
, or any extension for which you have an interpreter: .js
for Javascript, .py
for Python, etc. as long as the corresponding interpreter is installed), that the builder will run just before your app is signed and packaged. You can use this extra build step to arrange custom resource files in the app's directory, move some from here to there, include some others, exclude other ones, touch/modify a file's contents, or even generate new ones on the fly.
The directory in which you're interested to make changes, is typically the build\Release-iphoneos\iPhone-target\YourAppName.app
directory that is created in your project directory by the builder (Release-iphoneos
being the Xcode project's configuration name and the SDK's target system, and iPhone-target
being the Xcode project's target name which in Apple's terminology is equivalent to a Visual Studio's project name). Anyway, this is the directory whose contents will be signed and packaged in the final .ipa
file, that will be deployed to your iDevice.
If you need to use a pre-packaging script for your app, create a script that will make the necessary changes to this directory, and instruct the builder to call it. To find out which change(s) are needed, you must refer to your plugins' documentation.
💡 Most Unity apps won't ever need this feature, so I recommend using it only if yours require it.
Here's an example of a fictious pre-packaging script (and thus not applicable to your project), called MyCoolApp-prepkg.bat
:
@echo off
rem // hint: there are a few environment variables that you can use in your script. For example:
rem // %NAME% - will expand to your app's main binary name, e.g. "MyCoolApp"
rem // %CONFIGURATION% - will expand to the Xcode project configuration, e.g. "Release"
rem // %TARGET_BUILD_DIR% - path where the app was constructed, e.g. "build\Release-iphoneos\iPhone-target"
rem // see if we're calling this for the right app
if not exist "%TARGET_BUILD_DIR%\MyCoolApp.app\." (
echo Looks like you're not calling this script for the right project... ^(exiting with an error code^)
exit /b 1
)
rem // copy the missing .png files that my app expects to find in 'ExtraResources'
mkdir "%TARGET_BUILD_DIR%\MyCoolApp.app\ExtraResources"
copy /b /y C:\Users\Me\DevelopmentPictures\*.png "%TARGET_BUILD_DIR%\MyCoolApp.app\ExtraResources"
rem // remove a big duplicate file that was incorrectly bundled in two places
del /y "%TARGET_BUILD_DIR%\MyCoolApp.app\BigFatDataBase.sqlite"
Needless to say, do not blindly copy this example expecting that it will automagically solve a build problem with your project. You weren't going to do that, were you? You need to create a tailor-made script for your particular issue, that you must have well understood before writing anything.
Building from the command line
Let's Make the Command Line Great Again.
All the magic lies in the build.cmd
file that's in the main directory where the Project Builder was installed. Basically, you call it with the path to an Xcode project generated by Unity, your signer identity string, and a few options to tell whether you want it to produce an .ipa file, a .deb file, or both, whether you want to deploy the app to your device directly, etc.
Invoke it with build.cmd /?
to see the full list of command-line options, and to learn more about what is a signer identity string in the context of this builder. An example of command-line invokation could look like this:
"%UNITYBUILDER_PATH%\build.cmd" -xcname "Unity-iPhone" -xcpname "Unity-iPhone" -identity "development.cer:private.key:MySecretPassPhrase" -provision "Generic_development.mobileprovision" -archs arm64 -ipa "C:\Users\Pierre-Marie Baty\Desktop\AngryBots"
This command line builds the AngryBots Xcode project that's sitting on my desktop for arm64
and outputs a signed .ipa
file ready for deployment. Ain't it cute?
Extra magic: in the Xcode project builder UI, if you hit the Build button while holding the Shift key pressed, the build log will output a lot of debug info. The first line will be the full invokation of build.cmd
, that you can copy/paste and use in your own continuous integration tools!
Building from the command line is specially useful if you want to integrate this builder into your own custom build chain. If you are a professional developer, it will make your testers save an insane amount of time!
Uploading (with or without resigning) an .ipa file to App Store Connect is also possible from the command line. See upload.cmd /?
for the gory details. Here's an example:
"%UNITYBUILDER_PATH%\upload.cmd" "C:\Users\Pierre-Marie Baty\Desktop\AngryBots\Packages\AngryBots.ipa" -identity "distribution.cer:private.key:MySecretPassPhrase" -provision "App_Store_distribution.mobileprovision" -asclogin "your@apple-id.email" -ascpassword "AppStoreSecretPassword"
This command line re-signs then uploads the AngryBots.ipa file to App Store Connect.
Limitations and workarounds
Limited Swift support at the moment (no mixed Swift/ObjC targets). If you need to compile the Facebook SDK, use their pre-compiled SDK rather, and inject the Facebook frameworks into your build using the "Add extra framework" button (this will have the extra advantage that your build will be faster than recompiling the Facebook SDK).
Asset catalogs support is limited. While covering the vast majority of the cases, the current asset compiler this toolchain provides (cartool
) is able to compile app icons, splash screens, launch images, AR reference groups, AR reference images, AR objects, color sets, data sets, image sets and store artwork, but not other resources (yet). To have them supported in the next release, please send me the .xcassets
directory that doesn't build, so that I can provide you a patch quickly. In the meantime, assuming that your assets don't change across builds, and if you have a working macOS at hand or can borrow one, you can build your project on macOS once, then extract the compiled Assets.car
file from the .ipa
archive (.xcassets
directories are compiled into an Assets.car
file), then use it as a resource file when building your project on Windows, by inserting this Assets.car
file into the final app using a pre-packaging script (see the section about pre-packaging scripts).
The builder doesn't know how to compile .xib
files (some assets embed Xcode plugins that do). Assuming that these .xib
files don't change across builds, the workaround, if you have a working macOS at hand or can borrow one, is to either build your project on macOS once then extract the compiled .nib
files from the .ipa
archive, or compile them by hand on Mac using the ibtool --compile <outfile.nib> <infile.xib>
, then put those precompiled .nib files aside their corresponding .xib files in your Xcode project for the builder to pick these precompiled versions.
The builder doesn't work if your project is accessed through a network path (UNC path such as \\SERVER\share\project
). To workaround this limitation, just mount your network path as a network drive, and access it using the drive letter instead of the network path.
Troubleshooting
Foreword: esoterics
Some security bloatware (Avast DeepScan is one of them) are known to crash-test executables they encounter for the first time once in a sandboxed environment, to make sure they're safe. This is likely to happen the first time you build an app because of the various executables involved in the toolchain, and produce extraordinarily creative results. If you have such security software, don't interfere ; let it toy with all that it likes, and when it'll finally admit that the toolchain is safe, you'll be able to rebuild your apps consistently (note that it can take several tries before the way is finally clean). A more radical approach, yet guaranteed, is to put the directory where the builder is installed in your virus scanner's exclusion list.
Unity won't create your app's Xcode project
If you get an error in the Unity editor saying that Firebase iOS builds are not supported on Windows, go to your Unity project build settings for iOS and set it so that it generates only the Podfile
. This will enable you to create the Xcode project anyway, without the Pods directory (and thumb down Google).
Your app won't build
- Make sure you're using the latest version of the builder, and update if necessary. While doing this, it is advisable to keep a backup copy of your current installer, for reference.
- Make sure you're not trying to compile a project that requires a version of the Apple SDK that's higher than the one you have migrated. If in doubt, update your Apple SDKs to the latest one by first updating Xcode on Mac, then using the
Migration Assistant
again to transfer the Xcode SDKs from Mac to Windows. Also, avoid using "beta" versions of the SDKs unless you really have to: they might be less well digested by this builder.
- If the builder output window disappears immediately after popping up, make sure your Xcode project is not hosted in a directory monitored by a file synchronization service (such as the Dropbox folder). The various temporary locks used by those sync services hinder and prevent the builder to run. Another likely reason is that your project path contains invalid characters. Some characters are known to cause problems, e.g. parentheses in a directory name like
"New folder (2)"
. Simply rename your project's directory to something safe (use only alphanumeric characters and spaces) and see if it makes the problem disappear.
-
If you get a warning about CocoaPods, it means your project is using a third-party plugin or asset that relies on it. CocoaPods is a plugin manager for Xcode projects, which is implemented in this builder as an opt-in daemon service. You may choose to use it, in which case simply follow the instructions. If you choose not to use it, the workaround in this case is to identify the frameworks, libraries, and/or bundles that CocoaPods is supposed to inject in your Xcode project (this can be done by reading the
Podfile
), download these missing frameworks/libraries/bundles from their website (almost all of them provide a non-CocoaPods means of distribution in the form of a zip archive or similar), and inject them at build time using the "Add extra framework" and "Add binary" buttons. After that, move the Podfile
away (e.g. rename it) so that the build is allowed to continue.
- Google Firebase: download a version of their binary SDK that's compatible with your project here, and inject the frameworks you need in the builder UI. Also add
-ObjC
to the extra linker flags.
- Google Mobile Ads (AdMob): Google has integrated AdMob into Firebase, so follow the same instructions as with Firebase, and inject the framework called
GoogleMobileAds.framework
. This framework depends on other Firebase components: UserMessagingPlatform
, GoogleAppMeasurement
, GoogleUtilities
, PromisesObjC
, nanopb
, so inject those as well and add -ObjC
to the extra linker flags.
- Facebook SDK: download their pre-compiled frameworks here, and inject them into your build.
- IronSource SDK: download their pre-compiled frameworks here, and inject them into your build.
- Appodeal: download their SDK here. Also download their StackContentManager framework (which they seemingly forgot to include). You can get its URL by looking at its
PodSpec
on their GitHub repository; until it changes, the download is here. Then inject the frameworks normally. Also add -ObjC
in the extra linker flags (NOTE: there is controversy about this ad mediation service. I found it out after discovering that my malware filters block their domains. I am not advocating them here, I'm simply answering a recurrent user request. Please make your own mind.)
- Others: refer to their respective documentation, or contact their maintainer, supplier or author for guidance.
Each time you download pre-compiled frameworks, make sure to download the right version for your project. Picking the most recent one will NOT be better and greater, on the contrary it's likely to cause errors because of API/ABI incompatibilities!
One last thing™: you will experience longer build times with CocoaPods than without. That is because using CocoaPods implies to build all of your projects' dependencies from source, instead of just linking against precompiled libraries and frameworks. The same is true on macOS. That's just the way it works. So make an educated choice on whether to use it or not.
- If you get an error that says:
duplicate symbol _main
, or an error that says: ld: entry point (_main) undefined
, then it means you toggled the remote debug console on/off without rebuilding. Simply tick the "Rebuild all files" checkbox to fix it.
- Temporarily uncheck the "Use all available CPU cores during compilation" checkbox to make sure the
-multicore
option is not in use. This will greatly help diagnosis, because the builder will now process one file at a time, and stop at the first error.
- Temporarily check the "Disable compiler cache" checkbox to rule out a possible cache collision problem where two different compiled object files would get the same hash value, and be confused altogether by the system (very unlikely, but might theoretically happen).
- Make sure the target OS version that you specified in the Unity editor is not too old for the frameworks or plugins that your app requires (for example, the Social plugin requires iOS 6+). The fact that it builds with Xcode can be misleading here, because Xcode doesn't warn you when there is an inconsistency between the targeted OS version and the frameworks on which it relies.
- If your Unity project uses third-party plugins and your error is a header (.h) file not found, you will need to supply the relative path to the directory that contains that file as an extra compiler flag. The format is :
-I<relative_path_to_the_directory>
, for example: -ISomeDir/SomeOtherDir/DirWhereMyHeaderIs
. You probably will have to do that again for other include directories (maybe a lot, if you use a lot of plugins). Append all compiler flags with a space between each of them, like this: -Ipath1 -Ipath2 -Ipath3
.
- If you get an error like this one:
ccache: error: Failed to create temporary file for [very long path]
, then you're likely to have been plagued by the Windows 260 characters path limit. Try relocating your project closer to the root of your drive (e.g. if your project is located at C:\Users\YourName\Documents\Projects\Stuff\Miscellaneous\2020\RandomIdeas\ThingsThatWork\MyCoolProject
, simply move that directory to C:\MyCoolProject
. Most of the times it's enough to stay under the path limit.
- For all the other cases, to make sure the problem is with the builder and not with the project, try building it with Xcode. If it fails there too, then you need to fix something within Unity so that it exports a buildable project.
- If it builds with Xcode but not with my builder, then it means something needs to be fixed and I'd like to hear about it for sure. Drop me a note here after verifying that the answer you need isn't already in the forum.
Your app won't install (the app icon is shadow masked)
- If your app is signed with a digital identity, make sure you picked the right certificate, the right private key, and the right provisioning profile. Most of the install failures come from an inconsistent signature. You must sign your app with a certificate approven on your device, such as one from the Apple Developer Program, and with the same private key out of which that certificate was created. Then you must also make sure you have a valid provisioning profile for this very device, this very certificate and this app ID (note: you're beginning to understand why "generic profiles" are so cool, don't you? ;-)) - in all cases, if you messed up with your private keys, certificates and profiles and don't know for sure which was used to create which, the only advice that is guaranteed to work 100% is to re-create them all from scratch, following the directions in this file (see the sections about signing identities, and provisioning profiles.)
- If your app uses extra entitlements (a.k.a. "Application Services"), make sure these entitlements are defined in your provisioning profile. To do so, go to the Apple Developer Portal, enter the App IDs section, click on the App ID that your current provisioning profile uses, and make sure the corresponding entitlement (Apple calls them Application Services) is enabled for this app ID. If not, edit this app ID to enable it, then re-generate the provisioning profiles that use it. Note that some entitlements require an explicit app ID (for example, the "associated domains" entitlement cannot be activated with a generic app ID).
- If your app is unsigned, make sure your device is jailbroken (see How to deploy.), and the
AppSync
package is installed. Don't miss out AppSync
to install unsigned IPAs! On the other hand, .deb
packages don't require AppSync
.
- If you're trying to use the OTA deploy feature, make sure your iDevice has a working Internet connection, make sure that it can ping your computer, and also that no firewall or "security" bloatware (Norton, etc) prevents your computer from exposing a webserver to the local network. If in doubt, try disabling your firewall. And finally, make sure that the certificate you used to sign your app is either a development or an enterprise certificate from the paid Apple Developer Program. No other certificate types are allowed by Apple for OTA deployment. This implies that the "free" Xcode certificates, whose validity is one week, will not work for OTA deployment: if you have one of these, you must deploy manually with iTunes (although I recommend enrolling in Apple's iOS developer program instead and getting unrestricted certificates, it's well worth the expense).
- If you're using iTunes to deploy your app, it has been reported that in some cases instead of drag-and-dropping you need to select and copy (CTRL+C) the .ipa file then paste it (CTRL+V) on the device's Music tab in iTunes, then sync.
- If you can't find out the reason, use the remote debug console over USB to log the system activity, starting from when the app is being downloaded, and stop recording after the icon stops circling. Filter the messages using your app's bundle ID so as to display only the ones relevant to your app. It might still be a lot of read, but the rejection reason will necessarily be written here.
Your app installs successfully, but won't start
- Make sure the installation finished successfully. If your app's icon looks a bit "grayed out", it did not install successfully, and most probably you have a code signature problem. See above (app fails to install).
- Make sure you built it for an OS version that is not higher than the one on your device. Check in the Unity Editor for that.
- Make sure you didn't delete or move accidentally one of the resource files in the Xcode project directory exported by Unity. All of them need to be there.
- If your Unity app uses third-party plugins that rely on extra resource files, make sure the resource files they need are all present at their right location in the final
.app
directory. A pre-packaging script may be used if you need to lay out these files manually.
Your app sends nothing to the remote debug console
- Make sure the IP of the computer hosting the console is correct. Your app might simply be trying to connect to a non-responding IP.
- Make sure that your IP is accessible on TCP ports 5001. and 5002 A firewall or another security program might be preventing connections to the remote debug console. Temporarily turn them off to see if it's the cause of the problem, then set them up appropriately.
- Some iDevices require a SIM card to be plugged in to allow apps network access (strange Apple logic). Make sure you have one if appliable.
- Finally, make sure nothing in your code would close or reroute the
stdout
(standard output) and stderr
(standard error) streams.
Your app crashes
- Rebuild your app to use the remote debug console so as to catch the application logs and stack trace, and learn the actual reason of the crash. Let this become an automatism.
- If you get a message that says
unrecognized selector sent to instance
or unrecognized selector sent to class
and your app uses static libraries or frameworks (static libraries are pre-compiled files ending in .a
and static frameworks are all those third-party frameworks that are merged in your app's main binary and don't go in a Frameworks
subdirectory of your app — if you're unsure that your app uses either of those, assume it does), and these static libraries or frameworks contain Objective-C classes (if you're unsure, assume they do), then the linker may have stripped too much of them. Because Objective-C calls work by character strings and not function addresses like everybody else (for the curious, see how objc_msgSend() works), it's hard for the linker to know whether your code will be using a particular Objective-C function or not, so when this function is inside a library, by default it assumes it won't... and is often wrong. So, to prevent the linker from stripping "seemingly unused" Objective-C code found in your static libraries and frameworks, add -ObjC
to the extra linker flags and rebuild your app. Note that this is often an explicit direction given by third-party framework providers (for example, Google does tell you to add the -ObjC
linker flag to Xcode when you use Firebase or AdMob, unless you're using CocoaPods, which does it automatically). Note that this necessarily will make your app bigger, so if you want to restrict this linker behaviour to a particular library or framework, consider using the -force_load path/to/library
flag instead.
Your app starts, but ARKit features don't work
Check if you get the following warning in the Unity Editor console when Unity creates the Xcode project: xcrun (an Xcode tool) was not found. This is necessary to bake ARKit-specific data into an XRReferenceImageLibrary. XRReferenceImageLibrary assets will not work in asset bundles.
If you do, it means the Unity Editor expected to find a macOS-specific tool called xcrun
(which obviously doesn't exist on Windows) to copy your AR reference images at their right place in the Xcode project. Relying on this tool should not be necessary as there are more portable ways to do the same thing. Until Unity fixes this issue in a more portable manner, you may use this workaround suggested by a user. The idea is to load your AR reference images by hand when they're needed, programmatically.
Your working app won't upload to App Store / TestFlight
- App Store accounts that use Two-factor authentication need to set up an app-specific password. Apple explains how to set up one here. Once you have your app-specific password set up, simply use that password in place of your main App Store account password in the upload tool.
- Make sure it's been signed with a certificate that's fit for App Store submission. Re-sign it with the right certificate if needed.
- Make sure it's been provisioned with a provisioning profile that's fit for App Store submission. Re-sign it with the right provisioning profile if needed.
- Make sure that your app's bundle ID is registered and that App Store Connect is actually waiting for an upload for this app.
- Make sure your App Store Connect login and password are correct. Avoid special characters in your password: some are known to cause problems (such as
"
, \
, !
or ^
). If needed, update your App Store Connect password on the Apple website.
- Check that the Apple servers are actually reachable. If you can browse to this page with a web browser (you should get a JSON error, this is normal) it means the Apple upload servers are online. In case you get no response (blank page), it might mean a temporary network problem. In which case check your network connection. The problem might also be on Apple's side, in which case try again sometime later.
- If these verifications fail to solve your problem, turn verbosity on in the upload tool and see if you can get a better understanding of the problem with the log messages.
If none of these directions help to solve your problem, then 👉 head up there for support (but only after these verifications are made!)
One last word
If you feel so, you are warmly invited to leave the worst possible review about this asset on the Unity Asset Store, expressing all your frustration that it can't just do what you want with a single click, without needing to read all this stupid documentation and follow worthless instructions. Really, what sort of programmers read docs today?
More seriously, if you feel frustrated, my advice is to take a breath, take your time to sort out your questions, and kindly do ask for help. If hundreds of people could use this toolchain successfully, there's really little reason you could not. Really.
Engineer for hire. The author of this toolchain is in search of interesting challenges. If you need a skilled self-taught developer to evaluate, roadmap and solve a technical challenge for you or your company in original and unprecedented ways, contact me! -- Pierre-Marie Baty <pm@pmbaty.com>
Change log