loading...

Ad Hoc distribution for iOS

gualtierofr profile image Gualtiero Frigerio ・6 min read

I want to share my experience building and distributing apps with xcodebuild hoping to help some iOS developer struggling with automating build and distribution via command line.

There are two ways to distribute a test version of your app for iOS: TestFlight and Ad Hoc.
The former allows you to submit a build to App Store connect and immediately push it to your internal team, up to 25 members, or ask for a review and have up to 10,000 tester receive the build via the Test Flight app on their devices.
But what if you don't want to bother with creating a new app in App Store Connect? Maybe you're in the early stage of development, or just prototyping an idea of an app, or don't yet have access to Connect with your account (that shouldn't be true starting February 2019 as Apple is allowing the same account on the developer portal and Connect).
Ad Hoc distribution is an easy and quick way to send an app to test devices you have register on the developer portal.
I have to say it isn't straightforward to get the UUID from your test devices if you need to ask them to a client, and that's the main advantage of using TestFlight.
I usually send this link: http://whatsmyudid.com/ and I eventually get the UUID I'm asking for, but I think you should rely on TestFlight if you are dealing with non tech-savvy people.
Anyway, let's suppose you have some devices registered on your developer portal. You need to create a provisioning profile for testing so you can connect them to Xcode and install the app to debug, or you can distribute the app Ad Hoc. That means you create an IPA (basically a zip containing your signed app) you can distribute over-the-air to register devices.
You can either generate the IPA on Xcode or via command line, and that's what my article is all about.

Generate the IPA in Xcode

Let's start with the easy way: generating an IPA in Xcode.
The first step is creating an archive, it is exactly the same one you'd use to submit an app to the store.
Alt Text
make sure you have a device selected, or Generic iOS device, as your target otherwise Archive will be disabled.
Then open the Organizer (Xcode will likely open it automatically after the archive is ready) and click on Distribute
Alt Text
Select Ad Hoc and hit next a couple of times. You can leave Include manifest for over-the-air installation unchecked as I'll give you the file to use.
You are now prompted for your certificate and iOS Ad Hoc profiles. What you need is a distribution certificate, not a development one, and a provisioning profile linked to that certificate with your registered devices.
Xcode will finally export the IPA into a directory, alongside 3 other files: a log, a plist called DistributionSummary and another useful file named ExportOptions.plist that you'll need to implement the command line generation of the IPA later on.

Install the IPA over-the-air

Installing the IPA is as simple as tapping on a link on your iOS device that you can send via email or put on a web page.

itms-services://?action=download-manifest&url=https://www.gfrigerio.com/apps/test.plist

so the link isn't the usual http one. iOS recognises itms-services and prompts with an alert asking you to install the app. You do have to provide a valid https url for your manifest, a plist file telling iOS where it the IPA, what is your app's name, its bundle identifier and the icon file to show during download. Old versions of iOS used to accept http URL, but now you need to have everything in https: the manifest and the URLs contained in it.

This is the plist file you need to provide

<?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>items</key>
<array>
<dict>
<key>assets</key>
<array>
<dict>
<key>kind</key>
<string>software-package</string>
<key>url</key>
<string>https://www.gfrigerio.com/app/test.ipa</string>
</dict>
<dict>
<key>kind</key>
<string>display-image</string>
<key>needs-shine</key>
<true/>
<key>url</key>
<string>https://www.gfrigerio.com/app/test.png</string>
</dict>
<dict>
<key>kind</key>
<string>full-size-image</string>
<key>needs-shine</key>
<true/>
<key>url</key>
<string>https://www.gfrigerio.com/app/test.png</string>
</dict>
</array>
<key>metadata</key>
<dict>
<key>bundle-identifier</key>
<string>com.gfrigerio.test</string>
<key>bundle-version</key>
<string>1.0</string>
<key>kind</key>
<string>software</string>
<key>title</key>
<string>Test</string>
<key>subtitle</key>
<string>Test</string>
</dict>
</dict>
</array>
</dict>
</plist>

You need to change those fields:

  • software-package: url of your IPA
  • display-image: url of your app's icon
  • full-size-image: can be the same url of the display image
  • bundle-identifier: must be the same bundle identifier of the app
  • title: title of the app, should be the bundle display name
  • subtitle: I found out it doesn't really matter what you write here

Remember to have https for all the URLs included in the plist.

Generate IPA via command line

The same IPA you can build in Xcode via the Archive voice of the menu can be built via a script. You could have a script building the latest version of your app at the end of each day, but even if you launch it manually it saves time to script the building and deploying for over-the-air instead of waiting for Xcode to archive your project, than export it and copy the IPA wherever the URL on the plist points to.
Two steps are necessary to build and distribuite via command line: first you have to archive the app with xcodebuild, then create the package signed with the distribution provisioning profile.
I always start the process by cleaning the old build

xcodebuild clean

Time to archive the project

xcodebuild -scheme TestScheme -sdk iphoneos -archivePath . archive

The scheme parameter is the name of one of your schemes, you can see it in Xcode between the start and stop button and the name of the device or simulator. Use iphoneos as SDK as you're not exporting for a simulator but for a real device. You can specify a path for your archive, while the last command archive defines the operation you want xcodebuild to perform: archive
Finally we can build the IPA from the archive generated in the previous step by calling xcodebuild with parameter -exportArchive

xcodebuild -exportArchive -archivePath TestScheme.xarchive -exportOptionsPlist exportOptions.plist -exportPath .

The archive command generated a file called TestScheme.archive, the archivePath parameter specifies a path, in the example you see the file name as I told xcodebuild to create the archive in the current directory.
In order to create an IPA xcodebuild needs a plist containing some information. If you exported the IPA in Xcode you found that very plist in the directory together with the IPA. Here is an example:

<?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>method</key>
<string>ad-hoc</string>
<key>teamID</key>
<string>ABCDE123</string>
<key>compileBitcode</key>
<false/>
<key>provisioningProfiles</key>
<dict>
    <key>com.gfrigerio.test</key>
    <string>Ad Hoc Distribution</string>
</dict>
</dict>
</plist>

In teamID you have to put your teamID, you can find it in the developer portal if you look at the Prefix value in your application App ID.
Under provisioning profile you need to specify your app's bundle identifier as the key, and the value is the name of your distribution provisioning profile.

Tips

I want to end this article by giving a couple of tips, as I wasted a lot of time over some of those problems and it isn't easy to find answers on Stackoverflow or discussions.apple.com
If you get the alert saying there was an error while installing your app it is likely you made a mistake in the plist or the device isn't in the provisioning profile you're using.

  • check if every URL you're using is https
  • did you put the correct bundle identifier into the plist?
  • is your device registered and included into the provisioning profile you used to sign the IPA?

If everything seems ok, the app is installed on the device but crashes right after you launch it (while it works if you run it from Xcode), chances are there is something wrong with the signing process.
I run into that very problem a while ago and found out it was related to Keychain, where I stored my signing certificate. The Trust option was set to Always trust, xcodebuild signed the IPA without warnings but the app crashed on every device except for the old iPad with iOS 9. I solved by setting Trust to the default, so make sure you have the same value.
Update: Xcode11 gives an error while building the IPA now and tells you to use System defaults.
Alt Text
This is wrong, set Use System defaults instead

Original article http://www.gfrigerio.com/ad-hoc-distribution/

Posted on Feb 18 by:

gualtierofr profile

Gualtiero Frigerio

@gualtierofr

Senior iOS developer at Epress spa

Discussion

markdown guide
 

Hello Gualtiero,
Thank you for this useful article!

I have the possibility to have all iPads of my customer, so i can just use XCode and start a Debug to have the app installed, that is usually simpler in my case.
However i have two doubts:
1) how can I run the app in Release mode, so that the compiled binary is release and not debug?
2) how can I be sure of the expiration date of the deployed app?

Thank you for your help!
Regards

 

So you're installing the app via Xcode without paying for a developer account? I'm not sure it is possible to send push notifications this way.

 

I do have a paid account, however since all iPads are in-house at the customer headquarter, we usually compile directly on the devices (4 in total) through XCode.
We do not need push notification, however the build this way is a "debug" build, and also we are not completely sure of the expiration date.

The app needs a provisioning profile to run on a device. Xcode can create one for you even if you're not a paying member but I guess it lasts only for a few days.
Since you have an account you can register the iPads and create a provisioning profile either for development or ad hoc distribution. The profile expires after 12 months, after that you'll need to sign the app again and install it on the devices. That's why I prefer ad hoc, I create the archive, sign it and send a link via email to the customer so I can handle it remotely.

 

Nice post!

I have a silly question: I am new to this game, and I am trying to distribute an app. I can install it on my device through Xcode but is it possible to share it without a dev account?

I know websites like diawi.com/ can publish it, just with the IPA file, but I am having some problems generating it.

Do you have a clue?
Thank you.

 

I didn't know diawi, but if you want to distribute an app to a client or a friend you need their UUID, so a paid developer account. Otherwise they need Xcode to install the app on their device like you did, but you need to send them the project.

 
 

Thank you for this useful Post

when you said: This is the plist file you need to provide, where is the place to provide it?

and I don't know what is the correct way to use these fields and how create or to obtain the distribution manifest, what I need is to distribute my app to de QA teams for the test.

Thank you for your help!
Regards

 

You can upload the plist in your intranet so your QA can access it.
It is important to have HTTPS, otherwise iOS won't download the IPA. If you don't have an HTTPS server with a certificate in your intranet you can use a third party service, like Dropbox.

You need the distribution manifest only if you want to automate the build via script, if you export from Xcode you only need to upload the IPA and the Plist