Building an iOS App with Tauri: A Practical Guide

After building a macOS menu bar app with Tauri, I decided to extend the project to iOS. The goal was simple: create a companion app for my iPhone that could start and stop timers from my self-hosted time tracking service. Here's what I learned along the way.

Why Tauri for iOS?

Tauri 2 introduced mobile support, allowing you to build iOS and Android apps using the same Rust backend and web frontend you'd use for desktop apps. The promise is compelling:

For a companion app that's essentially a remote control for a web service, Tauri seemed like the perfect fit.

Prerequisites and Setup

Before diving in, you'll need:

Xcode Configuration

This is where many people get stuck. You need to configure Xcode for development signing:

  1. Open Xcode → Settings → Accounts → add your Apple ID
  2. Create a new Tauri project: npm create tauri-app@latest
  3. Initialize iOS support: npm run tauri ios init
  4. Open the generated Xcode project at src-tauri/gen/apple/time-tracker-ios.xcodeproj
  5. Select your target → Signing & Capabilities
  6. Enable "Automatically manage signing"
  7. Select your Apple ID from the Team dropdown

Development vs Production

Understanding the difference between development and production builds is crucial:

Development Mode

npm run tauri ios dev

This starts a Vite dev server on your Mac and builds the Rust binary in debug mode. The iPhone connects to your Mac's local network IP (e.g., http://192.168.1.100:1420) to load the WebView content.

Key requirements:

Production Mode

npm run build
npx tauri ios build -t aarch64

This bundles the frontend directly into the app binary. No dev server needed. The IPA is generated at src-tauri/gen/apple/build/arm64/YourApp.ipa.

Common Pitfalls

1. "Could not connect to dev server"

If you see this error, check:

2. "No Account for Team"

This means Xcode signing isn't configured. Open the Xcode project directly and configure signing as described in the setup section.

3. App won't launch after install

After installing via xcrun devicectl, the app might fail to launch with a "Locked" error. This happens when the device screen is off. Simply unlock your iPhone and open the app manually.

4. Developer profile not trusted

On first install, go to Settings → General → VPN & Device Management and trust your developer profile. You'll also need to enable Developer Mode in Settings → Privacy & Security → Developer Mode (requires a restart).

Storage Considerations

One surprise was the state of iOS Keychain support in the Rust ecosystem. The keyring crate, which works great on macOS, requires explicit keychain store configuration on iOS that isn't available in the current version.

Solution: For sensitive data like API tokens, store them in a local JSON file within the app's data directory. This isn't ideal from a security perspective, but it works for personal apps. For production apps, you'd want to implement direct Keychain access via Security.framework.

The Build Process

Installing on a Physical Device

# Find your device ID
xcrun devicectl list devices

# Uninstall previous version (optional)
xcrun devicectl device uninstall app --device <DEVICE_ID> com.yourcompany.yourapp

# Install the IPA
xcrun devicectl device install app --device <DEVICE_ID> "src-tauri/gen/apple/build/arm64/YourApp.ipa"

Personal Builds vs App Store

For personal use, you can sideload apps using your free Apple Developer account. The catch: you need to re-sign the app every 7 days. For distribution beyond yourself, you'd need a paid Apple Developer account ($99/year) and go through the App Store review process.

Architecture Decisions

Rust Owns the Network

All HTTP requests happen in Rust, not in the WebView. This gives you:

The WebView only renders UI and sends commands to Rust via Tauri's invoke system.

Foreground-Only Polling

For a companion app, background polling isn't necessary. The app polls every 15 seconds while in the foreground. This avoids the complexity of background modes and keeps the app simple.

Vanilla TypeScript for UI

For a small app, React or Vue would be overkill. Vanilla TypeScript with a simple state machine keeps the bundle small and the code straightforward.

Testing Strategy

Automated UI testing for iOS is possible but requires additional setup. For a personal app, manual testing on a physical device is sufficient.

What I'd Do Differently

Looking back, here are some things I'd change:

  1. Start with production builds earlier: Development mode is convenient, but production builds reveal issues you won't see in dev mode.

  2. Implement proper Keychain access: The JSON file workaround is fine for personal apps, but production apps should use the iOS Keychain.

  3. Add error boundaries: The WebView can crash if the Rust backend panics. Error boundaries would provide a better user experience.

  4. Consider offline support: Even a simple offline queue for pending actions would improve the app's resilience.

Conclusion

Building an iOS app with Tauri is a viable alternative to native development, especially for companion apps that interact with web services. The tooling is mature enough for personal projects, and the ability to share code between platforms is a significant advantage.

The main challenges are:

But once you understand these pitfalls, the development experience is smooth. The hot reload in development mode is excellent, and the production builds are small and fast.

If you're considering Tauri for iOS, my advice is: start simple, test on a physical device early, and don't be afraid to read the Tauri source code when things don't work as expected. The community is small but helpful, and the documentation is improving rapidly.

Happy building!