How to create React Native Module in pure Swift

I have a project on React Native that has a bunch of Native Modules and UI Components. The code is mostly written on Swift but it also has Objective-C code for exporting my swift’s native classes and methods to React Native because Swift doesn’t support C macros, e.g.:

// Calendar.m

#import <React/RCTBridgeModule.h>

@interface RCT_EXTERN_MODULE(Calendar, NSObject)

RCT_EXTERN_METHOD(createEvent:(NSString *)title location:(NSString* )location)

@end
// Calendar.swift

@objc(Calendar)
class Calendar: NSObject {
  
  @objc
  func createEvent(_ title: String, location: String) {
    print(title, location)
  }
}

It’s inconvenient to manage both Objective-C and Swift method interfaces each time of any modification so is there any approach to eliminate Objective-C code?

If you look into React Native macros RCT_EXTERN_MODULE, RCT_EXTERN_METHOD etc. you can find that they produce additional static methods for React Native to register your module and expose your methods:

#define RCT_EXPORT_MODULE_NO_LOAD(js_name, objc_name)                           \
  RCT_EXTERN void RCTRegisterModule(Class);                                     \
  +(NSString *)moduleName                                                       \
  {                                                                             \
    return @ #js_name;                                                          \
  }                                                                             \
  __attribute__((constructor)) static void RCT_CONCAT(initialize_, objc_name)() \
  {                                                                             \
    RCTRegisterModule([objc_name class]);                                       \
  }

#define _RCT_EXTERN_REMAP_METHOD(js_name, method, is_blocking_synchronous_method)                            \
  +(const RCTMethodInfo *)RCT_CONCAT(__rct_export__, RCT_CONCAT(js_name, RCT_CONCAT(__LINE__, __COUNTER__))) \
  {                                                                                                          \
    static RCTMethodInfo config = {#js_name, #method, is_blocking_synchronous_method};                       \
    return &config;                                                                                          \
  }

You can implement these methods in your Swift code:

import React

class Calendar: NSObject, RCTBridgeModule {
  
  @objc func createEvent(_ title: String, location: String) {
    print(title, location)
  }
  
  @objc static func __rct_export__createEvent() -> UnsafePointer<RCTMethodInfo>? {
    struct Static {
      static let jsName = strdup("createEvent")
      static let objcName = strdup("createEvent:(NSString * _Nonnull)title location:(NSString * _Nonnull)location")
      static var methodInfo = RCTMethodInfo(jsName: jsName, objcName: objcName, isSync: false)
    }
    return withUnsafePointer(to: &Static.methodInfo) {
      $0
    }
  }
  
  @objc class func moduleName() -> String! {
    "\(self)"
  }
}

Also you need to register your class manually on app startup to be operable:

RCTRegisterModule(Calendar.self)

Swift Macro

As you can see it’s possible to write a React Native Module manually in pure Swift but it is not convenient at all. But macro has been started supporting from Swift 5.9 (XCode 15) and you can use ReactBridge swift macro library for React Native Modules and UI Components. And with this library your code simply becomes to:

import React
import ReactBridge

@ReactModule
class Calendar: NSObject, RCTBridgeModule {
  
  @ReactMethod
  @objc func createEvent(title: String, location: String) {
    print(title, location)
  }
}

If you are open to using expo I have used Expo Modules API for native modules before they handle all the nasty objc stuff.

They expect you to write your modules in Kotlin and Swift but I believe under the hood they are still just generating all the objc code that you need.

Expo Modules API was a great experience especially if you are wanting to write swift code. I wrote a native module without it and felt the objc stuff was hard to keep in sync with the swift code.

https://docs.expo.dev/modules/overview/

Leave a Comment