Skip to main content

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):

ios/YourApp/AppDelegate.mm
- (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+)

ios/YourApp/AppDelegate.swift
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

ios/YourApp/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 install after running swiftpatch init. Run cd ios && pod install.
  • App loads the old bundle after an update — check that your bundleURL override is returning the OTA URL. Call getNativeConfig() from JS and verify deploymentKey is non-null.
  • Build error about duplicate UIBackgroundModes — your existing Info.plist already has UIBackgroundModes; swiftpatch init merges by default, but a hand-edited plist may trip the parser. Remove the duplicate block and re-run init.

For more, see Troubleshooting.

Next steps