iOS Setup
The automatic way is one command. The manual way is a short snippet you paste into AppDelegate. Both are documented here.
The automatic path
npx swiftpatch init
cd ios && pod install && cd ..
That's it. Move on to wrapping your app.
Under the hood, swiftpatch init modifies three iOS files. Every change is bounded by // swiftpatch-begin / // swiftpatch-end markers, so swiftpatch unlink reverses them cleanly.
AppDelegate.mm or .m (Objective-C / Objective-C++)
Inserted at the top of bundleURL (or sourceURLForBridge):
- (NSURL *)bundleURL {
// swiftpatch-begin (auto-generated, do not edit between markers)
NSURL *swiftpatchURL = [SwiftPatchModule getBundleURL];
if (swiftpatchURL != nil) {
return swiftpatchURL;
}
// swiftpatch-end
// Your existing code continues unchanged below
#if DEBUG
return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index"];
#else
return [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"];
#endif
}
The inserted block calls the SDK first. If it returns nil (no OTA bundle, missing class, or error), control falls through to the original implementation untouched. This is why swiftpatch unlink and upgrades are safe.
AppDelegate.swift (RN 0.77+)
override func bundleURL() -> URL? {
// swiftpatch-begin (auto-generated, do not edit between markers)
if let url = SwiftPatchModule.getBundleURL() {
return url
}
// swiftpatch-end
#if DEBUG
return RCTBundleURLProvider.sharedSettings().jsBundleURL(forBundleRoot: "index")
#else
return Bundle.main.url(forResource: "main", withExtension: "jsbundle")
#endif
}
Info.plist
<!-- swiftpatch-begin (auto-generated, do not edit between markers) -->
<key>SwiftPatchDeploymentKey</key>
<string>dep_xxx...</string>
<!-- swiftpatch-end -->
<!-- Background sync task registration -->
<key>BGTaskSchedulerPermittedIdentifiers</key>
<array>
<string>com.swiftpatch.bg-refresh</string>
</array>
<key>UIBackgroundModes</key>
<array>
<string>fetch</string>
<string>processing</string>
</array>
The BGTaskSchedulerPermittedIdentifiers and UIBackgroundModes entries power Background sync. The SDK handles task registration; you just need these keys present.
Manual setup
If auto-patching can't run (monorepo quirks, custom AppDelegate, strict review policy), run:
npx swiftpatch init --manual
The CLI still creates your app in the dashboard, generates keys, writes .swiftpatchrc, and updates .gitignore. It then prints the exact snippets to paste.
The steps to apply by hand:
1. Install the pod
cd ios && pod install && cd ..
2. Paste the bundleURL override
Copy the snippet from the relevant AppDelegate section above into your bundleURL method (Swift) or sourceURLForBridge method (Objective-C).
3. Add Info.plist keys
<key>SwiftPatchDeploymentKey</key>
<string>dep_xxx...</string>
<key>BGTaskSchedulerPermittedIdentifiers</key>
<array>
<string>com.swiftpatch.bg-refresh</string>
</array>
<key>UIBackgroundModes</key>
<array>
<string>fetch</string>
<string>processing</string>
</array>
4. (Optional) Wire silent-push-triggered sync
Only needed if you want silent pushes to trigger background update checks. See Background sync.
import SwiftPatch
func application(
_ application: UIApplication,
didReceiveRemoteNotification userInfo: [AnyHashable: Any],
fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void
) {
SilentPushHandler.handleRemoteNotification(userInfo, completionHandler: completionHandler)
}
Minimum iOS version
iOS 13.4+. The SDK uses BGTaskScheduler (iOS 13+) and URLSession resumable downloads. The default IPHONEOS_DEPLOYMENT_TARGET in modern React Native templates already satisfies this.
New Architecture
Swiftpatch ships a TurboModule spec and is fully compatible with Fabric + the New Architecture. See New Architecture.
Troubleshooting
- "SwiftPatchModule not found" — you forgot
pod installafter runningswiftpatch init. Runcd ios && pod install. - App loads the old bundle after an update — check that your
bundleURLoverride is returning the OTA URL. CallgetNativeConfig()from JS and verifydeploymentKeyis non-null. - Build error about duplicate
UIBackgroundModes— your existingInfo.plistalready hasUIBackgroundModes;swiftpatch initmerges by default, but a hand-edited plist may trip the parser. Remove the duplicate block and re-runinit.
For more, see Troubleshooting.
Next steps
- Android setup — the Android equivalents.
- Provider reference — wrap your app.
- Background sync — iOS
BGTaskScheduler+ silent push.