Skip to main content
New Expensify for iOS provides a native experience with full offline support, camera receipt scanning, and platform-specific features.

Prerequisites

macOS Required: iOS development requires a Mac with Xcode installed.

System Requirements

  • macOS (latest version recommended)
  • Xcode 14.0 or later with iOS Platform package
  • Ruby (for CocoaPods)
  • Node.js 18+ and npm

For M1/M2 Macs

If using an M1 or M2 Mac, see this Stack Overflow post for CocoaPods installation.

Quick Start with Rock

New Expensify uses Rock to download pre-built native artifacts, avoiding lengthy local builds.

Running the App

# Install dependencies
npm install

# Start Metro bundler (required)
npm run start

# In another terminal, run iOS app
npm run ios
For standalone NewDot (without HybridApp integration), use npm run ios-standalone.

How Rock Works

  1. Fingerprint Generation: Rock creates a fingerprint from native dependencies
  2. Remote Check: Looks for matching build on S3
  3. Download or Build: Downloads if available, builds locally if not
  4. Fast Iteration: Only rebuilds when native code changes
Files that trigger rebuild when modified:
  • package.json
  • Podfile
  • ios/ directory contents
  • Native module changes

Manual Build Setup

For local development or when Rock remote builds aren’t available:

1. Install Ruby and Bundler

# Install bundler
gem install bundler

# Install project gems (including CocoaPods)
bundle install
If you get permission errors, you’re using system Ruby. Install Ruby via rbenv instead.

2. Configure MapBox

# Run MapBox configuration script
npm run configure-mapbox
Follow the prompts to enter your MapBox token. See MapBox setup guide for obtaining a token.

3. Install iOS Dependencies

# Install npm and pod dependencies
npm install
npm run pod-install

4. Run the App

# Development simulator
npm run ios

iOS-Specific Features

Face ID / Touch ID

Biometric authentication is supported for secure login:
import {canUseBiometrics} from '@libs/BiometricAuthentication';

const biometricsAvailable = await canUseBiometrics();
if (biometricsAvailable) {
  // Enable biometric login
}

Camera Receipt Scanning

iOS camera integration with permissions:
src/components/AttachmentPicker/launchCamera/launchCamera.ios.ts
import {launchCamera as launchCameraImagePicker} from 'react-native-image-picker';
import {PERMISSIONS, request, RESULTS} from 'react-native-permissions';

const launchCamera: LaunchCamera = (options, callback) => {
    // Check camera permissions
    request(PERMISSIONS.IOS.CAMERA)
        .then((permission) => {
            if (permission !== RESULTS.GRANTED) {
                throw new Error('User did not grant permissions');
            }
            launchCameraImagePicker(options, callback);
        })
        .catch((error) => {
            callback({
                errorMessage: error.message,
                errorCode: 'permission',
            });
        });
};
See Receipt Scanning for more details on mobile receipt capture.

Live Activities (iOS 16.1+)

GPS trip tracking with Live Activities:
ios/AppDelegate.swift
import ActivityKit
import AirshipFrameworkProxy

// Register GPS trip Live Activity with Airship
if #available(iOS 16.1, *) {
    try? LiveActivityManager.shared.setup { configurator in
        await configurator.register(
            forType: Activity<GpsTripAttributes>.self,
            airshipNameExtractor: nil
        )
    }
}

Share Extension

Share receipts and files from other apps:
ios/ShareViewController/ShareViewController.swift
import UIKit
import Social

class ShareViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        // Handle shared items
    }
}

Push Notifications

iOS uses APNs through Airship for push notifications:
src/libs/Notification/PushNotification/index.native.ts
import Airship, {EventType} from '@ua/react-native-airship';

// Initialize push notifications
Airship.addListener(EventType.PushReceived, (notification) => {
    // Handle notification received
});

Airship.addListener(EventType.NotificationResponse, (event) => {
    // Handle notification tap
});

// Register device
Airship.contact.identify(notificationID.toString());
See Push Notifications for detailed setup and configuration.

Running on Physical Device

To run on a physical iPhone or iPad:
  1. Connect device via USB
  2. Open ios/NewExpensify.xcworkspace in Xcode
  3. Select your device from the target dropdown
  4. Click “Run” or press Cmd + R
See How to Build on Physical iOS Device for detailed instructions.

Development Workflow

Hot Reload

Changes to JavaScript/TypeScript files automatically reload:
  • Fast Refresh: Automatic for most changes
  • Manual Reload: Shake device or press Cmd + R in simulator
  • Debug Menu: Cmd + D in simulator

Debugging

  1. Enable Web Inspector in Safari: Develop > Simulator
  2. Select your app from the list
  3. Use Safari DevTools for debugging

Testing Web in iOS Simulator

To test the web app in Safari on iOS simulator:
# Setup HTTPS certificates and install on simulator
npm run setupNewDotWebForEmulators ios
This installs the development SSL certificate on the simulator so you can test https://dev.new.expensify.com:8082.

Performance Profiling

Enable Source Maps

In Xcode, edit the “Bundle React Native code and images” build phase:
Build Phase Script
export SOURCEMAP_FILE="$(pwd)/../main.jsbundle.map"

export NODE_BINARY=node
../node_modules/react-native/scripts/react-native-xcode.sh

Record Performance Traces

  1. Build app in production mode
  2. Navigate to feature to profile
  3. Four-finger tap to open menu
  4. Select “Use Profiling”
  5. Interact with app
  6. Four-finger tap again to stop
  7. Share the trace file

Symbolicate Traces

# Place Profile<version>.cpuprofile at project root
# Generate source map from same branch
npm run symbolicate-release:ios

# Open Profile_trace_for_<version>-converted.json in:
# - https://www.speedscope.app
# - https://ui.perfetto.dev
# - chrome://tracing

Push Notification Setup (Development)

To receive push notifications while testing against Staging/Production:

HybridApp

Edit Mobile-Expensify/iOS/AirshipConfig/Debug/AirshipConfig.plist:
<key>inProduction</key>
<true/>

Standalone

Replace development keys with production values in ios/AirshipConfig.plist:
<key>developmentAppKey</key>
<string>PRODUCTION_KEY_HERE</string>
<key>developmentAppSecret</key>
<string>PRODUCTION_SECRET_HERE</string>

Troubleshooting

Rock Build Issues

  1. Try reinstalling dependencies:
    npm run i-standalone  # for standalone
    npm install           # for hybrid
    
  2. Run the app again:
    npm run ios-standalone  # or npm run ios
    
  1. Clean the iOS directory:
    git clean -fdx ios/              # standalone
    git clean -fdx ./Mobile-Expensify  # hybrid
    
  2. Try running again
  1. Check if GitHub Actions completed: iOS Builds Workflow
  2. Compare fingerprints:
    npx rock fingerprint -p ios --verbose
    
  3. Look for differences in fingerprint vs GitHub Actions output

CocoaPods Issues

# Clear CocoaPods cache
pod cache clean --all

# Reinstall pods
cd ios
pod deintegrate
pod install

Xcode Build Errors

1

Check Xcode Updates

System Settings > Software Update > Check for Xcode updates
2

Clean Build Folder

Product > Clean Build Folder (or Cmd + Shift + K)
3

Reset Simulator

Device > Erase All Content and Settings
4

Restart Xcode

Quit and reopen Xcode

Metro Bundler Issues

# Clear Metro cache
npm start -- --reset-cache

# Clear watchman
watchman watch-del-all

# Clear temp files
rm -rf $TMPDIR/react-*

Native Modules

Creating Native iOS Modules

For advanced iOS features, create native modules:
ios/RCTShortcutManagerModule.h
#import <React/RCTBridgeModule.h>

@interface RCTShortcutManagerModule : NSObject <RCTBridgeModule>
@end
ios/RCTShortcutManagerModule.m
#import "RCTShortcutManagerModule.h"

@implementation RCTShortcutManagerModule

RCT_EXPORT_MODULE(ShortcutManager);

RCT_EXPORT_METHOD(removeAllDynamicShortcuts)
{
    // Native implementation
}

@end

Build Configurations

Debug vs Release

  • Debug: Fast refresh, Chrome debugging, development warnings
  • Release: Optimized bundle, no debugging, production-ready
# Debug build (default)
npm run ios

# Release build
npm run ios-build

Resources

React Native Docs

Official React Native iOS setup

Xcode Documentation

Apple’s Xcode resources

Rock Documentation

Learn more about Rock build system

Airship iOS SDK

Push notification integration

Next Steps

Receipt Scanning

Learn about mobile receipt capture

Push Notifications

Configure push notifications

Offline Mode

Understand offline functionality

Android Platform

Setup for Android development

Build docs developers (and LLMs) love