Writing React Native iOS Modules in Swift
React Native speaks Objective-C on iOS, so Swift needs an Objective-C wrapper to join. Write logic in Swift, expose it via an Objective-C class, and register with RCT_EXPORT_MODULE. Skip the bridging header and the compiler never sees your Swift code.
WHY IT EXISTS: React Native's core iOS infrastructure is built on Objective-C and Objective-C++. The native module system expects classes and methods that conform to this runtime. Swift is the modern default for iOS development, but it cannot directly interface with the React Native bridge because the bridge relies on macros, dynamic dispatch, and C++ types that Swift does not natively expose. Bridging exists to let teams write native modules in Swift without rewriting React Native itself.
THE MENTAL MODEL: Think of Swift as a guest at a party where everyone speaks Objective-C. Swift can attend, but only if an Objective-C host introduces it. The host is a thin Objective-C wrapper class that translates between the JavaScript bridge and your Swift implementation. The Swift code does the real work, but the Objective-C wrapper holds the name tag that React Native recognizes.
HOW IT WORKS: First, you enable a bridging header in your Xcode project so Objective-C can see Swift classes. Then you write your module logic in a Swift class. Next, you create an Objective-C wrapper that imports the generated Swift header and implements RCTBridgeModule. You expose methods using RCT_EXPORT_METHOD. The wrapper forwards calls to your Swift instance and returns results. The JavaScript layer invokes the Objective-C wrapper, which delegates to Swift behind the scenes.
WHEN TO USE IT: Use this pattern when you have an existing Swift library to expose, when your iOS team prefers Swift over Objective-C, or when you need modern iOS APIs like SwiftUI or Combine that are painful to access from Objective-C. It is also useful when you want to share business logic between a React Native app and a pure Swift iOS target.
WHEN NOT TO USE IT: Do not use this pattern if you only need simple synchronous calls that are already covered by community modules. If your module is performance-critical and called thousands of times per second, the extra hop through Objective-C can add overhead. Also avoid it if you are not comfortable debugging build issues in Xcode, because mixing Swift and Objective-C++ in the same project can produce cryptic linker errors when header search paths are misconfigured.
ONE CANONICAL EXAMPLE: Imagine you are building a module that reads Apple Health data. You write a HealthKitManager class in Swift that queries step counts. You then create HealthKitBridge.m and HealthKitBridge.h in Objective-C. The header declares RCT_EXPORT_MODULE and an RCT_EXPORT_METHOD method called getDailySteps. The implementation imports YourProject-Swift.h, instantiates HealthKitManager, and forwards the JavaScript call. When JavaScript calls NativeModules.HealthKit.getDailySteps, the bridge finds HealthKitBridge, which delegates to your Swift HealthKitManager and returns the step count through a resolver.
Get five bites like this every day.
Tezvyn delivers a daily feed of 60-second tech bites with quizzes to lock in what you learn.