Continuous Integration and Delivery For Native iOS App using Codemagic

Kevin Jonathan
7 min readJan 10, 2023

Wanting to implement Continuous Integration (or even CI/CD) to your own project or company project using Codemagic? Continue reading!

In this article, I will explain about using Codemagic as our CI/CD for our iOS project.

What CI/CD stands for?

TL;DR CI/CD is a process that makes developers easier to test and deploy their apps quickly.

It is actually an acronym of “Continuous Integration” and “Continuous Delivery/Deployment”. Believe it or not, the usage of both is actually easier to understand than the terms itself.

But first of all, why CI/CD?

Do you know what is important for you to pay attention to after writing code? Ensure that the code you have written is correct and passes tests before release. Do not let the application gets downloaded by the user but there are so many unnecessary bugs or crashes. It could mean there will be many 1 star App Store app reviews incoming. Of course you don’t want that do you? Well, that’s why you must know about CI/CD.

Then what’s CI/CD?

Continuous Integration is a process whereby we as developers can integrate code into a repository like GitHub and run tests quickly and automatically. So that if there is an error it can be looked up and handled quickly, and if the test is successful, you can build the project into an IPA (iOS application extension), for example.

Whereas Continuous Delivery is a process after the code has been successfully integrated, it can then be deployed to a live production environment.

Now, back to the main topic, how to implement CI/CD in our iOS project using Codemagic?

Implementation

First of all, push your project to a cloud repository like GitHub (you can use anything you’d like). After done, let’s get started!

Open Codemagic, and sign in using GitHub/Bitbucket/GitLab or create new account using email. I’d recommend sign in using the first third options though to make things easier.

After logging in, you will be redirected to the dashboard, and you can find menus like Apps, Builds, etc. We can just add our application by clicking “Add application” in the top right. After that, you will be asked to select a Git provider or add a repository from a URL. I will use GitHub in my case.

After that, we can authorize the integration and follow the steps provided on your screen. Once done, we can choose our repository and project type. Don’t forget to select “iOS App” if you are developing a native iOS app.

You will be redirected to the settings page of your newly created app in Codemagic. Follow the steps to add “codemagic.yaml” file to your repository since it’s needed for the integration.

For starters, you can add this configuration to your codemagic.yaml

workflows:  
ios-project-debug: # Workflow ID
name: iOS Debug # Workflow name
environment:
xcode: latest
cocoapods: default
vars:
BUNDLE_ID: "com.example.id" # replace with your app bundle
XCODE_PROJECT: "App.xcodeproj" # Replace with your xcodeproj name
XCODE_SCHEME: "App" # Replace with your project name
ios_signing:
distribution_type: app_store # or: ad_hoc | development | enterprise
bundle_identifier: com.example.id # replace with your app bundle
scripts:
- name: Run tests
script: |
xcodebuild \
-project "$XCODE_PROJECT" \
-scheme "$XCODE_SCHEME" \
-sdk iphonesimulator \
-destination 'platform=iOS Simulator,name=iPhone 14,OS=16.2' \
clean build test CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO
- name: Build debug app
script: |
xcodebuild build -project "$XCODE_PROJECT" \
-scheme "$XCODE_SCHEME" \
CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO CODE_SIGNING_ALLOWED=NO - name: Build ipa for distribution
- name: Increment build number
script: |
#!/bin/sh
cd $CM_BUILD_DIR
LATEST_BUILD_NUMBER=$(app-store-connect get-latest-app-store-build-number "$APP_STORE_APPLE_ID")
agvtool new-version -all $(($LATEST_BUILD_NUMBER + 1))
- name: Build ipa for distribution
script: |
xcode-project build-ipa \
--workspace "$CM_BUILD_DIR/$XCODE_WORKSPACE" \
--scheme "$XCODE_SCHEME"
artifacts:
- build/ios/ipa/*.ipa
- /tmp/xcodebuild_logs/*.log
- $HOME/Library/Developer/Xcode/DerivedData/**/Build/**/*.app
- $HOME/Library/Developer/Xcode/DerivedData/**/Build/**/*.dSYM
publishing:
email:
recipients:
- username@mail.com # Replace with your email
notify:
success: true
failure: true

Once done, click the “Check for configuration file” on the top left, and then we can proceed to start the first build.

If the test succeeded, you will find green tick next to the app name on the build overview. This is the example of the successful build of my “FeelJournal” app, a journal app that uses NLP. You will also get an email too to tell you if the build succeeded or not.

Successful build on Codemagic

We can also check the build log by clicking one of the build steps to check the log or to find the error if our build failed. You can also add more configurations if you like, by reading the documentation on Codemagic website.

Reference: https://docs.codemagic.io/

Explanation for codemagic.yaml configurations

Let’s break down the code into several chunks!

workflows:  
ios-project-debug: # Workflow ID
name: iOS Debug # Workflow name

This is your workflow ID and name that you can specify. It’s custom and you can put anything there.

    environment:
xcode: latest
cocoapods: default
vars:
BUNDLE_ID: "com.example.id" # replace with your app bundle
XCODE_PROJECT: "App.xcodeproj" # Replace with your xcodeproj name
XCODE_SCHEME: "App" # Replace with your project name
ios_signing:
distribution_type: app_store # or: ad_hoc | development | enterprise
bundle_identifier: com.example.id

For the environment, we can just specify that we are using the latest version of xcode and default cocoapods version. We also need to specify our XCode project and the scheme name there. For my project, it’s “FeelJournal.xcodeproj” and “FeelJournal” since my app’s project name and scheme is “FeelJournal”. For ios_signing, it’s used to sign our app before Codemagic build the app so that we can publish it to the App Store. But for app signing, you need to get the App Store Connect API key first.

    scripts:
- name: Run tests
script: |
xcodebuild \
-project "$XCODE_PROJECT" \
-scheme "$XCODE_SCHEME" \
-sdk iphonesimulator \
-destination 'platform=iOS Simulator,name=iPhone 14,OS=16.2' \
clean build test CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO
- name: Build debug app
script: |
xcodebuild build -project "$XCODE_PROJECT" \
-scheme "$XCODE_SCHEME" \
CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO CODE_SIGNING_ALLOWED=NO

This is the script to run the tests of our project, anything like UnitTests and such. It’s best to assume that this is the most important part of our codemagic.yaml, since it’s responsible to test if our code is causing errors or not.

      - name: Increment build number
script: |
#!/bin/sh
cd $CM_BUILD_DIR
LATEST_BUILD_NUMBER=$(app-store-connect get-latest-app-store-build-number "$APP_STORE_APPLE_ID")
agvtool new-version -all $(($LATEST_BUILD_NUMBER + 1))
- name: Build ipa for distribution
script: |
xcode-project build-ipa \
--workspace "$CM_BUILD_DIR/$XCODE_WORKSPACE" \
--scheme "$XCODE_SCHEME

This yaml part is optional, but it’s used to manage the increment of build number (if you are too lazy to do it yourself), and to build ipa for App Store distribution.

    artifacts:
- build/ios/ipa/*.ipa
- /tmp/xcodebuild_logs/*.log
- $HOME/Library/Developer/Xcode/DerivedData/**/Build/**/*.app
- $HOME/Library/Developer/Xcode/DerivedData/**/Build/**/*.dSYM
publishing:
email:
recipients:
- username@mail.com # Replace with your email
notify:
success: true
failure: true

And then there are the paths and names of the artifacts you would like to use for things like publishing or have available for download on the build page. For email recipients, it’s pretty self-explanatory, to send the build result to your email (you can add emails that you need, not only limited to your GitHub email). For notify, you can set to true/false if Codemagic should notify you if the build succeeded/failed.

Common issues of failing builds

Your code itself contains errors.

Fix your code first, then push the newest commit to your cloud repository before trying to build again from Codemagic. Easier said than done, perhaps?

Project Scheme is not currently configured for the test action.

You need to have a test target in your XCode project. You can add a test target (for example, Unit test) to your project by clicking “Files > New > Target…”, and then choose Unit Testing Bundle.

After that, enter the product name, and click “Finish”. You don’t need to add any unit test functions there if you don’t want. Just directly push the new changes to your cloud repository and we are good to go!

The requested device could not be found because no available devices matched the request.

This might be because your XCode version and the simulators used are different from mine, you can check the available devices in the build log. And then choosing one. For example, iPhone 14.

Build logs

Don’t forget to put the device destination on your codemagic.yaml with this format (change the name and the OS only) inside script:

-destination 'platform=iOS Simulator,name=iPhone 14,OS=16.2'

After that, don’t forget to push the changes, and try to build again with Codemagic.

The ipa build fails.

Please refer to this page for the complete guide.

That’s it for Continuous Integration and Delivery For Native iOS App using Codemagic. I hope you find this article useful for your iOS project integration with Codemagic!

--

--

Kevin Jonathan

Just a student intricately weaving personal life experience and technology related stuffs, currently navigating the intersections of life.