Weisser Zwerg Logo

Weisser Zwerg

iOS App without a Mac

Published by Weisser Zwerg Blog on

How to develop and iOS app without ever touching an Apple device for development.

  

Rational

I have refined my tool-set for developing code over the past 20+ years and chosen Linux as my go-to operating system and platform. As a phone I have chosen to use an iOS device, mostly because my family and my colleagues at work use iOS, too. Recently, I wanted to create my first iOS app myself and recognized only then how many obstacles Apple is putting in your way if you want to do that without touching an Apple device for development. This is close to impossible! especially if you have no prior knowledge about the Apple / Xcode development eco-system. I managed to create my app on Linux, nevertheless, without ever touching an Apple device for development. In this blog post I’d like to share my learning.

Caveat

Just to make this clear up-front: I needed to sign-up for an Apple developer account for 99$/year in order to generate the required code signing certificates to get the app on my iPhone.

It looks like there are some ways to get an iPhone app onto your device even without an Apple developer account, but either I did not fully trust these approaches or they seemed overly complex:

If you happen to have mastered a way to get an iPhone app onto your device even without an Apple developer account, I’d like to hear about that. Just leave a comment below.

React-Native vs. Flutter

You can create cross platform apps with several different frameworks. I only looked in more detail at React-Native and Flutter. With React-Native / Expo you can get really quickly an app to the point where you can interact with it on your iPhone via the Expo App as a sort of sandbox. But I was not able to create a standalone app for the iPhone without access to a Mac after that initial quick success. Therefore, I’ll describe below, how I managed to create a standalone app for my iPhone without ever touching an Apple device by using Flutter and the Codemagic build service.

Setting-up the Flutter Hello-World Project

As my goal was to proof that I can develop an iOS app on Linux without the need to touch an Apple device I simply used the Flutter hello-world example without further modifications.

Using Codemagic as a Build Service

If you want to learn more about the background of Codemagic go ahead and read the following article: Nevercode partners with Google and launches a dedicated CI/CD tool for Flutter apps.

The basic approach on how to get a Flutter app onto your iOS device is described in the following two articles:

The most difficult part here is to get the project.pbxproj file right! Normally this file and the required certificates are generated by Xcode and once you have them in place you don’t need Xcode again. If you can save yourself the trouble to create these artifacts by hand by just using an Apple device once (and you know how to do that) I’d strongly suggest you do that. I’ll describe next how you can create the required artifacts even without access to Xcode.

In Codemagic in the build settings you will have the option to let Codemagic take care of the certificates for you, but this would require to give your Apple developer account credentials to Codemagic. If you don’t have a problem with that then just go ahead. This is the quickest way to get the Codemagic build running and you won’t need to read most of the below. But if you are like me and don’t like to hand out your credentials of your Apple developer account to third party services like Codemagic then just follow the below steps to make it work.

Create Xcode Artifacts by Hand

You will need:

  • iOS App Development Certificate
  • Registered Device
  • App Profile with App ID

iOS App Development Certificate

For more background about how to use OpenSSL to create Certificate Signing Requests (CSRs) have a look at:

But otherwise just create an ios-dev.cnf file similar to:

ORGNAME = First Last

# --- no modifications required below ---
[ req ]
default_bits = 2048
default_md = sha256
prompt = no
encrypt_key = no
distinguished_name = dn

[ dn ]
C = DE
O = $ORGNAME
OU = 8467KY9FLZ # look at right top at https://developer.apple.com/account/resources/certificates/list
CN = iPhone Developer: $ORGNAME

Remember to replace First and Last to your first and last name respectively.

The value to put for OU is what you see when you log-in to the Apple developer account and go to the Certificates, Identifiers & Profiles section. Look at the right top below your name.

Then execute:

> openssl genrsa -out ios-dev.key 2048
> openssl req -new -config ios-dev.cnf -key ios-dev.key -out ios-dev.csr

You can look at the ios-dev.csr file via the gcr-viewer application or similar:

> gcr-viewer ios-dev.csr

Next go to the Certificates section in the Apple developer account web-site and click the + right besides Certificates. Select iOS App Development, click Continue and upload your ios-dev.csr file. Once that is done your iOS App Development certificate is created and you can download it.

App ID

As next step we’ll create an App ID in the Identifiers section of the Apple developer account web-site. For the description you can put whatever you like. I used “flutterHelloWorld”. For the Bundle ID I used “dev.weisser-zwerg.flutterHelloWorld”. Then click Continue and click Register again. Once you’re done you will have created your App ID.

Register Device

In order to be able to use the app on your device you will have to register your iOS device in the Devices section of the Apple developer account web-site. The most difficult part here will be to get hold of the Device ID (UDID). I only was able to do that via a Windows host and iTunes:

It seems that an alternative to using iTunes is via a Mac OS X device via its System Report as described here:

“iOS App Development” Provisioning Profile

As next step we’ll create an iOS App Development provisioning profile in the Profiles section of the Apple developer account web-site. Chose iOS App Development and click Continue. Select the App ID you created before and click Continue. On the next screen select your iOS Development certificate we created before and click Continue. On the next screen select your device that we registered before and click Continue. Finally provide a Provisioning Profile Name (I used dev weisser zwerg flutterHelloWorld devel) and click Generate. You’ll need to download the provisioning profile so that you can use it later in the Codemagic configuration. My downloaded file was called dev_weisser_zwerg_flutterHelloWorld_devel.mobileprovision.

Adapting the project.pbxproj File

We will use the Xcodeproj ruby gem to adapt our project.pbxproj file. You find the project.pbxproj file of your project in ./ios/Runner.xcodeproj/project.pbxproj. First, we will create a copy of the folder

> cp -r ./ios/Runner.xcodeproj ./ios/Runner.xcodeproj.orig

Then we install the ruby gem:

> gem install xcodeproj

Then create a script adapt_xcode_project.rb with the following content:

#!/usr/bin/env ruby

require 'xcodeproj'
require 'yaml'

project_path = './ios/Runner.xcodeproj.orig'
project = Xcodeproj::Project.open(project_path)

target = project.targets.first

target.build_configurations.each do |config|
puts config.name
config.build_settings['CODE_SIGN_IDENTITY'] = 'iPhone Developer'
config.build_settings['CODE_SIGN_STYLE'] = 'Manual'
# for the DEVELOPMENT_TEAM value look at right top at https://developer.apple.com/account/resources/certificates/list
config.build_settings['DEVELOPMENT_TEAM'] = '8467KY9FLZ'
config.build_settings['PRODUCT_BUNDLE_IDENTIFIER'] = 'dev.weisser-zwerg.flutterHelloWorld'
config.build_settings['PROVISIONING_PROFILE_SPECIFIER'] = 'dev weisser zwerg flutterHelloWorld devel'
end

project.save('./ios/Runner.xcodeproj')

And make it executable and run it:

> chmod u+x adapt_xcode_project.rb
> ./adapt_xcode_project.rb

Once that is done commit and push the files to GitHub. Codemagic will take the files from there.

Setting Up the Codemagic Build

As already said above, we won’t use the Codemagic automatic certificate handling process, but set-up a manual signing process. In order to do that we’ll have to create an ios dev certificate in .p12 format. Via openssl you can do that via:

> openssl x509 -inform der -in ./ios-dev.cer -out ./ios-dev.pem
> openssl pkcs12 -export -in ios-dev.pem -inkey ios-dev.key -out ios-dev.p12

In the above command we used ./ios-dev.cer. This is the certificate you downloaded from the Apple developer web-site. The file is originally called ios_development.cer after the download. This means that you either adapt the above command or rename the certificate.

Now go to the Codemagic web-site and create your account. Then create your build-configuration by clicking at the right top on + Add app from custom source. You only need to adapt two parts of the build configuration.

In the Build part I deselected all other build-targets except the iOS target and left the Mode at Debug. Don’t forget to press Save to make your changes take effect!

In the Publish part, click on iOS code signing and select Manual. Then upload your .p12 file via the Code signing certificate and provide the password you used to encrypt your code signing certificate. Then upload the Provisioning profile file you downloaded above, which was called dev_weisser_zwerg_flutterHelloWorld_devel.mobileprovision in my case. Again: don’t forget to press Save to make your changes take effect!

Once that is done, we’re ready to trigger our first build. Especially after changing configurations I noticed that the build does not always work reliably. When it does not work, I did not get any error message or similar, but the log output just stopped at a certain step and the clock continued to run. In principle forever, until you stop the build manually. The whole build should finish in around 3 minutes. If it takes longer than 5 minutes you can interrupt the build process and retry. Most of the time it worked for me on second attempt, if the first one stalled.

The most critical (means: if things fail then most likely here) step is the Building iOS step. The first part of this step is:

== /usr/local/bin/flutter build ios --debug --no-codesign ==
Warning: Building for device with codesigning disabled. You will have to manually codesign before deploying to device.

And this should finish below 60s. It will say something like:

Xcode build done.                                           30.9s

Then starts the code signing part and it will say something like:

Set up code signing settings:

and later

Exporting archive with the following settings:
{
    "method": "development",
    "provisioningProfiles": {
        "dev.weisser-zwerg.flutterHelloWorld": "dev weisser zwerg flutterHelloWorld devel"
    },
    "signingCertificate": "iPhone Developer",
    "signingStyle": "manual",
    "teamID": "8467KY9FLZ"
}

Until 2019-10-09 there was a bug that prevented the manual code signing to work. But it should work now.

Codemagic will send you an e-mail about the build success, which contains a link to the installable app. If you click on that link on your iPhone with the Device ID (UDID) you provided above you’ll be able to install the app on your device.

Afterthoughts

Apple’s attitude towards developers is inacceptable

Up to recently I was agnostic to the “consumer side” of IT and was not aware of this whole situation around how difficult it is to create an iOS app without access to an Apple device. I find Apple’s attitude absolutely inacceptable! Why force a developer to re-learn her/his whole development workflow? Nobody else does that!

We as a family bought 3 MacBook Pro’s for my two children and my wife. I thought that at least then they’d use a Unix underneath and have the chance to learn interacting to some degree with a Unix operating system. This actually worked out quite ok and my children at least know how to use a bash shell and similar. After my experience described above, I am not sure if I will ever buy an Apple device again, though.

Have you written a response to this? Let me know the URL via telegraph.

No mentions yet.