PDFTools can be deployed as a native iOS app using Capacitor — a cross-platform runtime that wraps the existing React web app in a native WebView. The result is a real .ipa file you can submit to the App Store.
| Requirement | Notes |
|---|---|
| Mac computer | Xcode only runs on macOS |
| Xcode 15+ | Download from Mac App Store (free) |
| Apple Developer account | $99 USD/year at developer.apple.com |
| Node.js 20+ | Required for Capacitor CLI |
| CocoaPods | sudo gem install cocoapods |
| iOS 15+ device (optional) | For physical device testing |
No Mac? Use MacStadium (cloud Mac), GitHub Actions macOS runners, or Codemagic CI/CD for building without owning a Mac.
React + TypeScript (web app)
│
│ npm run build → frontend/dist/
│
Capacitor CLI
│
│ npx cap add ios
│ npx cap sync
│
ios/ folder (Xcode project)
│
│ Xcode → Archive → App Store Connect
│
App Store → TestFlight → Users
The React app runs inside a WKWebView and calls the same Node.js API (https://api.yourdomain.com). No code changes are needed for the app logic — only native wrappers for file saving, sharing, and notifications.
From the repository root:
# Install Capacitor core + CLI
npm install @capacitor/core @capacitor/cli
# Install native plugins you'll use
npm install @capacitor/ios \
@capacitor/browser \
@capacitor/share \
@capacitor/filesystem \
@capacitor/local-notifications
# Initialise Capacitor (if not already done — capacitor.config.ts exists)
# npx cap init "PDFTools" "com.yourcompany.pdftools" --web-dir frontend/dist
capacitor.config.tsis already committed to this repo — just updateappIdto your own reverse-domain identifier.
cd frontend
VITE_API_URL=https://api.yourdomain.com npm run build
cd ..
Every time you make frontend changes you must re-run this step before syncing.
# Add the iOS native project (only run once)
npx cap add ios
# Sync web assets + plugins into the native project
npx cap sync ios
This creates an ios/ directory containing a complete Xcode project.
npx cap open ios
Xcode opens with the App project. Wait for Swift Package Manager / CocoaPods to finish downloading dependencies.
com.yourcompany.pdftoolsPDFTools1.0.0 and Build → 1Customize LaunchScreen.storyboard or replace it with a simple branded screen.
| Feature | Capability to add |
|---|---|
| File saving to Photos | NSPhotoLibraryAddUsageDescription in Info.plist |
| Camera (for scanning) | NSCameraUsageDescription |
| iCloud Drive sync | iCloud Documents |
npx cap run ios --target "iPhone 15 Pro" # simulator
npx cap run ios # first connected device
com.yourcompany.pdftoolsBefore public release, distribute to testers via TestFlight:
| Asset | Size |
|---|---|
| App icon | 1024×1024 px (no transparency) |
| iPhone 6.7” screenshot | 1290×2796 px |
| iPhone 6.1” screenshot | 1179×2556 px |
| iPad Pro 12.9” screenshot | 2048×2732 px (required if iPad support on) |
With Capacitor, you can update the web layer (React JS/CSS) without re-submitting to the App Store, using Capgo or Ionic AppFlow:
npm install @capgo/capacitor-updater
# In your CI pipeline after a frontend build:
npx @capgo/cli upload --apikey YOUR_KEY
Native code changes (new permissions, new plugins) still require App Store resubmission.
| Problem | Fix |
|---|---|
| CocoaPods not found | sudo gem install cocoapods then npx cap sync ios |
| “No provisioning profile” | Xcode → Signing & Capabilities → enable Automatically manage signing + select your team |
| White screen on device | Check that VITE_API_URL points to an HTTPS endpoint the device can reach |
| App rejected: guideline 2.1 | Add a privacy policy URL in App Store Connect |
| Large binary warning | Enable bitcode and app thinning in Archive settings |
| WebView not loading | Check Info.plist for NSAppTransportSecurity — API must be HTTPS in production |