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.
Rationale
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:
- Developing and debugging Flutter apps for iOS without a Mac
- This article describes how to use Cydia Impactor. The article says: “You will be prompted to enter your Apple ID email and password.” I did not trust the tool enough to give it my Apple ID email and password, but decide for yourself.
- How to develop and distribute iOS apps without Mac with Flutter & Codemagic
- You will also need to read through How to sign Flutter apps for iOS automatically without a Mac
- This is their Codemagic-Demo app and the most important piece of it is its project.pbxproj file.
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.