Overview
Developing Apple Targets follows a prebuild → develop → prebuild cycle that leverages Continuous Native Generation to keep your project clean and reproducible.Initial Setup
1. Create Your Target
Use the interactive CLI to scaffold a new target:- Target directory in
/targets/<name>/ expo-target.config.jsconfiguration- Template Swift files
- Auto-installs
@bacons/apple-targetsif needed
2. Configure Your Target
Edit the generatedexpo-target.config.js:
3. Set Your Apple Team ID
Add your Apple Team ID toapp.json:
Find your Team ID in Xcode under Signing & Capabilities or in your Apple Developer account settings.
4. Run Prebuild
Generate the Xcode project:--clean flag ensures a fresh build by removing the existing ios/ directory.
Development Cycle
Opening in Xcode
Open your project:expo:targets folder in the project navigator:
The
expo:targets folder is a virtual folder that links to ../targets/. Changes are saved outside the ios/ directory.Making Changes
✅ Safe to Edit in Xcode
- Any file inside
expo:targets/ - Swift source files
- Asset catalogs
- Storyboards and XIBs
- Info.plist (some keys)
❌ Avoid Editing in Xcode
- Build settings (use config instead)
- Target membership
- Frameworks (use
frameworksconfig) - Entitlements (use
entitlementsconfig) - Bundle identifiers
- Team IDs
Iteration Workflow
When to Run Prebuild
Runnpx expo prebuild -p ios --clean when you:
- Change
expo-target.config.js- Bundle ID, entitlements, frameworks, etc. - Change
app.json- Bundle ID, team ID, version, etc. - Add/remove target directories - New targets or deleted targets
- Add/remove files in
_shared/- Shared code between targets - Update Expo SDK - To regenerate with latest templates
- Experience Xcode issues - Fresh start can resolve corruption
Testing Your Target
Building Individual Targets
In Xcode, select your target from the scheme dropdown:⌘+B) or run (⌘+R).
Testing Widgets
- Build and run the main app first
- Long-press the home screen
- Tap the
+button - Find your app’s widget
- Add to home screen
iOS 18+ allows transforming the app icon into a widget. Long-press the app icon and select Widget Options.
Testing App Clips
App Clips require special setup:- Build via TestFlight - Local testing doesn’t support deep linking
- Configure AASA file - Add
.well-known/apple-app-site-association - Set up App Clip Experience - In App Store Connect
- Test QR codes - Generate with Apple’s App Clip Code Generator
Testing Share Extensions
- Build and run the main app
- Open Safari or Photos
- Tap the Share button
- Find your extension in the share sheet
- Test the sharing flow
Debugging
Console Logs
In Swift, use standard logging:Breakpoints
- Open your Swift file in Xcode
- Click the gutter to add a breakpoint
- Run the target
- Trigger the code path
WidgetCenter.shared.reloadAllTimelines() to trigger updates.
Common Issues
Widget doesn't appear on home screen
Widget doesn't appear on home screen
Solution:
- Use iOS 18+ (better widget debugging)
- Long-press app icon → Widget Options
- Check
PRODUCT_BUNDLE_IDENTIFIERmatches - Verify
Info.plisthas correct widget configuration
Swift compiler errors in widget
Swift compiler errors in widget
Solution:
- Clear SwiftUI previews:
xcrun simctl --set previews delete all - Use blank template:
npx expo prebuild --template ./node_modules/@bacons/apple-targets/prebuild-blank.tgz - Check framework imports match config
Changes not reflected after prebuild
Changes not reflected after prebuild
Solution:
- Ensure you edited files in
expo:targets/folder - Check if files are in git (should be tracked)
- Verify
--cleanflag was used - Close and reopen Xcode
Code signing errors
Code signing errors
Solution:
- Open Xcode signing tab to fix profiles
- Verify
appleTeamIdinapp.json - Check entitlements are valid
- Use Automatic signing in Xcode first time
Advanced Workflows
Using the Blank Template
For faster iteration without React Native:Sharing Code with _shared/
Create a _shared/ directory to share code between targets:
Re-run
npx expo prebuild --clean after adding/removing files in _shared/ directories.CocoaPods Integration
Add apods.rb file in your target directory:
React Native in App Clips
SetexportJs: true in your App Clip config:
pods.rb to include React Native:
Version Control
What to Commit
Recommended .gitignore
CI/CD Integration
EAS Build
Youreas.json automatically works:
- Runs
npx expo prebuild - Installs CocoaPods
- Builds all targets
- Signs with provisioning profiles
Entitlements are automatically synced to EAS credentials. See Code Signing.
Manual CI
For custom CI (GitHub Actions, etc.):Performance Tips
Faster Builds
- Use Xcode schemes - Build only the target you’re testing
- Disable unused targets - Comment out in
app.jsonplugin config - Use blank template - For extension development
- Enable Hermes - Faster JS execution in App Clips
Faster Iteration
- Minimize prebuilds - Only when config changes
- Use SwiftUI previews - Test UI without building
- Use simulator - Faster than device builds
- Cache derived data - In CI/CD pipelines
Next Steps
Code Signing
Configure provisioning and entitlements
Widget Guide
Build your first home screen widget