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:
- Share business logic between platforms
- Use familiar web technologies for the UI
- Native performance through Rust
- Small binary sizes
- No need to learn Swift or Kotlin
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:
- macOS with Xcode (not just Command Line Tools) installed from the App Store
- Rust with iOS targets:
rustup target add aarch64-apple-ios aarch64-apple-ios-sim - Node.js for the frontend tooling
- CocoaPods and libimobiledevice:
brew install cocoapods libimobiledevice
Xcode Configuration
This is where many people get stuck. You need to configure Xcode for development signing:
- Open Xcode → Settings → Accounts → add your Apple ID
- Create a new Tauri project:
npm create tauri-app@latest - Initialize iOS support:
npm run tauri ios init - Open the generated Xcode project at
src-tauri/gen/apple/time-tracker-ios.xcodeproj - Select your target → Signing & Capabilities
- Enable "Automatically manage signing"
- 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:
- Vite must listen on all interfaces (
host: trueinvite.config.ts) - Your Mac and iPhone must be on the same network
- The iPhone needs "Local Network" permission enabled for your app
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:
- Vite is configured with
host: true - Your Mac's IP hasn't changed (check with
ifconfig | grep "inet " | grep -v 127.0.0.1) - The iPhone has "Local Network" permission enabled in Settings → Privacy & Security → Local Network
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:
- Better error handling
- Easier testing
- No CORS issues
- Consistent behavior across platforms
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
- Unit tests: Rust tests for the state machine, API client, and utility functions
- Integration tests: Mock HTTP server to validate API contracts
- Manual tests: Physical device testing for the complete user flow
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:
-
Start with production builds earlier: Development mode is convenient, but production builds reveal issues you won't see in dev mode.
-
Implement proper Keychain access: The JSON file workaround is fine for personal apps, but production apps should use the iOS Keychain.
-
Add error boundaries: The WebView can crash if the Rust backend panics. Error boundaries would provide a better user experience.
-
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:
- Xcode signing configuration (steep learning curve)
- iOS-specific quirks (Keychain, permissions)
- Development vs production build differences
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!