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/