I'm not a MacOS user yet. However, I have a couple of my apps released on App Store, that were initially built for Android. To test my apps before they reach the app store review phase, I purchased the most affordable iPhone that I could get for testing my app - iPhone SE 2022. I wanted a device that would remain supported by Apple for years to come. Apple typically provides updates and support for iPhones (and all their devices) for seven years from the last time the model was sold.

My Stack

  • Ubuntu / Windows machine (depends if I'm working from desktop computer or a laptop, different OS-es are installed)
  • Cheap iPhone for testing
  • Apple developer account
  • Expo account
  • Github account for versioning and building the app
  • Patience

Steps

The process of publishing an app to the App Store involves creating an app identifier, configuring its capabilities, uploading the app (ipa file) to App Store Connect, testing it via TestFlight, and submitting it for Apple’s review and approval.

Create an app identifier

An App Identifier uniquely identifies your app within the Apple ecosystem. It's essential for app provisioning, and it must match the Bundle Identifier in your project.

  • Log In to Your Apple Developer Account
  • Navigate to Certificates, Identifiers & Profiles
  • Click on + to add a new identifier
  • Select App IDs, and then App
  • Add an explicit and unique bundle id. It's a unique package name e.g. 'net.myawesomeapp'
  • Select capabilities that you need to run your app. It's the same as permissions on Android.

Capabilities Checklist

When setting up your app, carefully choose the necessary capabilities to avoid rejection during the review process. Here’s what I know:

  1. Sign-In Options: If your app offers signup functionality, include "Sign in with Apple". This is mandatory to comply with Apple's guidelines. Just having Google, email/pass or any other signing will make your app rejected in the review.
  2. In-App Purchases: If your app includes subscriptions or any kind of purchases, you must select "In-App Purchase" to ensure compliance with Apple’s policies.
  3. Deep Linking: To enable seamless navigation between links that open directly in your app, select "Associated Domains". This is essential for deep linking.
  4. Sharing Functionality: If you want to enable sharing to or from your app, activate "App Groups". This is useful for apps that involve collaborative features or content sharing.

Create an App

Go to https://appstoreconnect.apple.com/apps and create a new app. Assign the created bundle id, fill the other fields and create.

After you've created your app, you'll be asked for other input.

If you collect anything from the user (analytics, crashlytics), you'll need to setup a link to privacy policy.

You'll also need to answer questions about data collection and usage.

Then you'll need to answer what type of encryption algorithms does your app implement? You probably fall into standard encryption category. If you use firebase, or communicate with any kind of server, it's most likely over https.

Configure TestFlight

TestFlight is a beta testing service provided by Apple to enable developers test pre release versions of their app. To Configure TestFlight, go to App Store Connect, navigate to TestFlight, and then Internal Testing. Create a new internal group, add emails of testers and save your changes.

Install TestFlight on your device. Once your app is submitted to TestFlight, you can install it and test on your iPhone.

Build and publish to TestFlight

My solution uses GitHub actions to build and publish to TestFlight. However, for the first build I had to use eas cloud build. However, for the initial build, I needed to use EAS Cloud Build. This is because Expo requires you to configure your iOS credentials during the first build (error "Distribution Certificate is not validated for non-interactive builds. Failed to set up credentials."). It needs to configure Distribution Certificate and Provisioning Profile. Once these credentials are set up, Expo won't ask for them again in subsequent builds.

You'll get 15 iOS lower priority builds on free plan, or 2$ per build on per demand plan. After that first eas cloud build, you can continue using GitHub actions.

Easiest way to configure ios platform in expo is by invoking a cloud build in the command line, and eas will do the rest - configure build credentials, provisioning profile and the distribution certificate:

eas build -p ios --profile production

Follow the wizard, authenticate with Apple when asked, build your ios app. After that first build, you can continue with GitHub actions if you'd like.

With GitHub actions I get 3000 free CI build minutes per month. However, using GitHub to run on MacOS runner costs 10 x the free build CI minute. That means I actually have 300 free CI minutes. After the free minutes get exhausted, there's no multiplier and builds usually cost me around 2.5$ per build (expo charges 2$).

Basic example looks like this:

name: Release to Apple

on:
  workflow_dispatch:
  push:
    branches:
      - main
release-app-ipa:
  runs-on: macos-latest
  steps:
    - uses: actions/checkout@v4
    - uses: actions/setup-node@v4
      with:
        node-version: 20
    - run: npm ci
      shell: bash
    - name: 🏗 Setup EAS
      uses: expo/expo-github-action@v8
      with:
        expo-version: latest
        eas-version: latest
        expo-cache: true
        # Create personal access token https://expo.dev/settings/access-tokens
        token: ${{ secrets.EXPO_TOKEN }}
    - name: Build iOS app
      run: eas build --local -p ios --profile production --non-interactive --output app.ipa
    - name: 'Upload app to TestFlight'
      uses: apple-actions/upload-testflight-build@v1
      with:
        app-path: './app/app.ipa'
        issuer-id: ${{ secrets.APPSTORE_ISSUER_ID }}
        api-key-id: ${{ secrets.APPSTORE_API_KEY_ID }}
        api-private-key: ${{ secrets.APPSTORE_API_PRIVATE_KEY }}

You need to setup several env variables and set them in project settings secrets:

If you've set everything, you can run your github action. Once your build completes without error, your app build ends up in test flight and you can install it and test it.

Prepare for App Store Submission

In App Store Connect update the app metadata, fill out all required fields under app info. Upload Screenshots and Previews. Select the countries and regions for distribution. Provide details about data collection and usage.
Add Notes for the Reviewer. It's marked as optional, but I recommend that you fill it. Include any specific information that might help the review process. Select your app build and submit for review.

In Review

This is the challenging part for several reasons. Review can be a few days or a day, but they will most certainly reject your app if your app doesn't follow the Apple standards. This is where you need a lot of patience. Also, carefully reading the Apple guidelines. I'm not a careful reader, so that's why some of my first submissions take a while to resolve. Bug fixes and features that don't introduce significant changes like in-app purchases shouldn't take much time to publish to App store.

GitHub: https://github.com/amarjanica/react-native-sqlite-expo-demo

Youtube Video: https://youtu.be/wUvtS8CWhMs