iOS implementation
Let's use XCode, to write iOS code. Open XCode, by running this command from the root directory of your app:
xed ios
When workspace is opened, locate Pods
project and expand it. Search for Development Pods
and find ScreenOrientationPackage
inside. When it's expanded, it will show all files that we created under screen-orientation-package/ios
directory.
- ObjC++ & Swift
- ObjC++ only
The complete flow when the orientation changes will look like that:
Orientation notification is emitted -> Notification handler is receiving the notification -> Notification handler is determining if the event should be emitted -> Notification handler emits event to the Delegate class
ScreenOrientationModuleImpl.swift
import Foundation
@objc(ScreenOrientationModuleDelegate)
public protocol ScreenOrientationModuleDelegate {
func sendEvent(name: String, payload: Dictionary<String, Any>)
}
/**
* Native module's shared implementation
*/
@objc(ScreenOrientationModuleImpl)
public class ScreenOrientationModuleImpl : NSObject {
@objc public weak var delegate: ScreenOrientationModuleDelegate? = nil
@objc func handleOrientationChange(notification: NSNotification) {
//
}
}
Here, we declare ScreenOrientationModuleImpl
class together with ScreenOrientationModuleDelegate
protocol.
To make Swift elements accessible to Objective-C world, we have to do 4 things:
- make class extending
NSObject
- mark class and its methods (at least those methods that are meant to be exposed) as public
- mark class with
@objc(exported-objc-name)
decorator - mark exposed methods with
@objc
decorator
The class will have weak delegate property and a method that will be called with the notification parameter every time device is changing its orientation.
To listen to orientation events, we will register this class as an observer on the default instance of NotificationCenter
.
Additionally, to trigger device orientation notifications, we need to explicitly start/stop them
with beginGeneratingDeviceOrientationNotifications
and endGeneratingDeviceOrientationNotifications
methods.
Ok, now let's connect to the orientation notifications
// ...
/**
* Native module's shared implementation
*/
@objc(ScreenOrientationModuleImpl)
public class ScreenOrientationModuleImpl : NSObject {
@objc public weak var delegate: ScreenOrientationModuleDelegate? = nil
public override init() {
super.init()
NotificationCenter.default.addObserver(self, selector: #selector(self.handleOrientationChange(notification:)), name: UIDevice.orientationDidChangeNotification, object: UIDevice.current)
DispatchQueue.main.async {
UIDevice.current.beginGeneratingDeviceOrientationNotifications()
}
}
deinit {
DispatchQueue.main.async {
UIDevice.current.endGeneratingDeviceOrientationNotifications()
}
NotificationCenter.default.removeObserver(self)
}
@objc func handleOrientationChange(notification: NSNotification) {
//
}
}
You can notice, that ScreenOrientationModuleImpl
class got initializer and deinitializer.
Inside initializer, we register the module as an observer of the notification (via addObserver(_:selector:name:object:)
) and we start generating orientation notifications.
When the class ends its life, those operations should be reverted. And that's done in the deinit
block - orientation notifications are stopped, and the listener is unregistered (via removeObserver(_:)
).
Next step is to handle the notification and emit the event.
// ...
/**
* Native module's shared implementation
*/
@objc(ScreenOrientationModuleImpl)
public class ScreenOrientationModuleImpl : NSObject {
@objc public weak var delegate: ScreenOrientationModuleDelegate? = nil
/**
* Example usage:
*
* ```swift
* self.sendEvent(name: Event.onScreenOrientationModuleChange.rawValue, payload: [ "value" : result ])
* ````
*/
private func sendEvent(name: String, payload: Dictionary<String, Any>) {
self.delegate?.sendEvent(name: name, payload: payload)
}
private var lastOrientation = "unknown"
public override init() {
super.init()
NotificationCenter.default.addObserver(self, selector: #selector(self.handleOrientationChange(notification:)), name: UIDevice.orientationDidChangeNotification, object: UIDevice.current)
DispatchQueue.main.async {
UIDevice.current.beginGeneratingDeviceOrientationNotifications()
}
}
deinit {
DispatchQueue.main.async {
UIDevice.current.endGeneratingDeviceOrientationNotifications()
}
NotificationCenter.default.removeObserver(self)
}
@objc func handleOrientationChange(notification: NSNotification) {
let currentOrientation = UIDevice.current.orientation
var orientation = "unknown"
if currentOrientation == UIDeviceOrientation.portrait || currentOrientation == UIDeviceOrientation.portraitUpsideDown {
orientation = "portrait"
} else if currentOrientation == UIDeviceOrientation.landscapeLeft || currentOrientation == UIDeviceOrientation.landscapeRight {
orientation = "landscape"
}
if lastOrientation == orientation {
return
}
lastOrientation = orientation
self.sendEvent(name: Event.onScreenOrientationModuleChange.rawValue, payload: ["orientation": orientation])
}
}
extension ScreenOrientationModuleImpl {
enum Event: String, CaseIterable {
case onScreenOrientationModuleChange
}
@objc(supportedEvents)
public static var supportedEvents: [String] {
return Event.allCases.map(\.rawValue);
}
}
We added 3 snippets of code, the 1st one adds a convienient helper for emitting events to the delegate and a variable that keeps last emitted value.
The 2nd snippet is adding the logic that compares current orientation value with the previous one and sends an event when there's new value to be emitted.
The last snippet declares an extension on ScreenOrientationModuleImpl
with Event
enum and supportedEvents
static getter.
For more on extensions or enums in Swift, visit Swift's docs.
ScreenOrientationModule.h
Now, let's go to the module to glue the native events and constants to the JS code:
#import <React/RCTEventEmitter.h>
/**
* When using Swift classes in ObjC header, the class must have its
* "forward declaration"
*
* @see https://developer.apple.com/documentation/swift/importing-swift-into-objective-c#Include-Swift-Classes-in-Objective-C-Headers-Using-Forward-Declarations
*/
@class ScreenOrientationModuleImpl;
/**
* Declare the ObjC interface for that native module class.
*
* It must extend NSObject (like every class in ObjC) and
* implement RCTBridgeModule (like each RN native module).
*
* If the module emits events, it must extend RCTEventEmitter class.
*/
@interface ScreenOrientationModule : RCTEventEmitter
@end
In the header file for ScreenOrientationModule
class we declare our class extending RCTEventEmitter
(as each RN iOS module with events emitting capability).
Additionally, to use Swift ScreenOrientationModuleImpl
class, we must do "forward-declaration" (check out Apple's Swift-ObjC interop dedicated docs section).
ScreenOrientationModule.mm
#import "ScreenOrientationModule.h"
/**
* When using Swift classes in ObjC implementation, the classes must be imported
* from generated Objective-C Interface Header
*
* @see https://developer.apple.com/documentation/swift/importing-swift-into-objective-c#Import-Code-Within-an-App-Target
*/
#import "ScreenOrientationPackage-Swift.h"
#if RCT_NEW_ARCH_ENABLED
/**
* Import header file with codegenerated protocols based on the JS specification
*
* The name of the header matches the name provided in codegenConfig's `name` field in package.json
*/
#import "ScreenOrientationPackage.h"
// Each turbo module extends codegenerated spec class
@interface ScreenOrientationModule () <NativeScreenOrientationModuleSpec>
@end
#endif
// Declare the ObjC implementation for that native module class
@implementation ScreenOrientationModule
// Return the name of the module - it should match the name provided in JS specification
RCT_EXPORT_MODULE(ScreenOrientationModule)
#if RCT_NEW_ARCH_ENABLED
// Implement RCTTurboModule protocol
- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:
(const facebook::react::ObjCTurboModule::InitParams &)params
{
return std::make_shared<facebook::react::NativeScreenOrientationModuleSpecJSI>(params);
}
#endif
@end
This is the implementation file for ScreenOrientationModule
class.
Before declaring the implementation block, we have to import Swift-ObjC generated interface header (check out dedicated docs section),
and we have to implement code-generated spec protocol for new architecture (code between #if RCT_NEW_ARCH_ENABLED
& #endif
directives).
After that, inside implementation block we have RCT_EXPORT_MODULE
macro invoked with ScreenOrientationModule
argument. This basically registers the module with provided name on iOS side, so that it's accessible in the JS world.
Also, for new architecture, it implements - getTurboModule:
method (from RCTTurboModule
protocol).
If you won't implement that method, XCode will complain that the code-generated spec protocol methods are not implemented.
Let's use ScreenOrientationModuleImpl
class
// ...
@interface ScreenOrientationModule () <ScreenOrientationModuleDelegate>
@end
// Declare the ObjC implementation for that native module class
@implementation ScreenOrientationModule {
ScreenOrientationModuleImpl *moduleImpl;
BOOL hasListeners;
}
// Return the name of the module - it should match the name provided in JS specification
RCT_EXPORT_MODULE(ScreenOrientationModule)
- (instancetype)init {
self = [super init];
if (self) {
moduleImpl = [ScreenOrientationModuleImpl new];
moduleImpl.delegate = self;
}
return self;
}
// Declare if module should be initialized on the main queue
+ (BOOL)requiresMainQueueSetup
{
return NO;
}
// ...
@end
First, we declare moduleImpl
private variable on ScreenOrientationModule
that will be initialized in the - init
method.
We can use ScreenOrientationModuleImpl
Swift class, because it's imported from ScreenOrientationPackage-Swift.h
.
We also declare hasListeners
variable, we will use it to check if there're any JS listeners registered to the module.
Next, we override static method + requiresMainQueueSetup
, which must be done whenever - init
method on the RN's wrapper class is overriden.
To handle events-specific methods from parent RCTEventEmitter
class, we need to override 3 methods:
// ...
@implementation ScreenOrientationModule {
ScreenOrientationModuleImpl *moduleImpl;
BOOL hasListeners;
}
// ...
// Declare if module should be initialized on the main queue
+ (BOOL)requiresMainQueueSetup
{
return NO;
}
// Declare which events will be emitted by the module
- (NSArray<NSString *> *)supportedEvents
{
return [ScreenOrientationModuleImpl supportedEvents];
}
- (void)startObserving
{
hasListeners = YES;
}
- (void)stopObserving
{
hasListeners = NO;
}
// ...
@end
To mark which events will be emitted by the module, we need to override - supportedEvents
getter - we use the value from ScreenOrientationModuleImpl
class as a source of truth.
Two other methods, are called when the first/last JS observer is registered/unregistered.
We still have to emit the events received from ScreenOrientationModuleImpl
- as you may noticed, XCode displayed a warning about ScreenOrientationModuleDelegate
.
The module class is marked to be implementing the delegate protocol,
the ScreenOrientationModuleImpl
delegate property is set to this module instance,
but we still need to implement delegate's methods.
// ...
@implementation ScreenOrientationModule {
ScreenOrientationModuleImpl *moduleImpl;
BOOL hasListeners;
}
// ...
- (void)stopObserving
{
hasListeners = NO;
}
- (void)sendEventWithName:(NSString * _Nonnull)eventName
payload:(NSDictionary<NSString *,id> * _Nonnull)payload
{
if (hasListeners) {
[self sendEventWithName:eventName body:payload];
}
}
// ...
@end
In the delegate's method, the module checks if there's any receiver that listens to events and if yes, invokes - sendEventWithName:body:
method from RCTEventEmitter
base class.
The last thing to do is to export constants to the JS world.
// ...
@implementation ScreenOrientationModule {
ScreenOrientationModuleImpl *moduleImpl;
BOOL hasListeners;
}
// ...
- (void)sendEventWithName:(NSString * _Nonnull)eventName
payload:(NSDictionary<NSString *,id> * _Nonnull)payload
{
if (hasListeners) {
[self sendEventWithName:eventName body:payload];
}
}
/**
* Declare constants exported by the module
*/
#if RCT_NEW_ARCH_ENABLED
- (facebook::react::ModuleConstants<JS::NativeScreenOrientationModule::Constants::Builder>)constantsToExport
{
return [self getConstants];
}
- (facebook::react::ModuleConstants<JS::NativeScreenOrientationModule::Constants::Builder>)getConstants {
return facebook::react::typedConstants<JS::NativeScreenOrientationModule::Constants::Builder>({
.PORTRAIT = @"portrait",
.LANDSCAPE = @"landscape"
});
}
#else
- (NSDictionary *)constantsToExport
{
return @{ @"PORTRAIT": @"portrait", @"LANDSCAPE": @"landscape" };
}
#endif
// ...
@end
In old architecture mode, it's super simple, we have to return an object (NSDictionary *
) from - constantsToExport
method.
In new architecture mode, it looks a bit more complex:
- we use code-generated type-safe C++ structs instead of
NSDictionary *
type - together with
- constantsToExport
we have to override- getConstants
method (both return type-safe C++ structs) - to produce type-safe struct, we use
facebook::react::typedConstants
function
If you're wondering where all those C++ structs are defined, you can find #import "ScreenOrientationPackage.h"
in XCode, Cmd+Click on it and select "Jump to Definition" action.
Complete ScreenOrientationModule.mm
file
#import "ScreenOrientationModule.h"
/**
* When using Swift classes in ObjC implementation, the classes must be imported
* from generated Objective-C Interface Header
*
* @see https://developer.apple.com/documentation/swift/importing-swift-into-objective-c#Import-Code-Within-an-App-Target
*/
#import "ScreenOrientationPackage-Swift.h"
#if RCT_NEW_ARCH_ENABLED
/**
* Import header file with codegenerated protocols based on the JS specification
*
* The name of the header matches the name provided in codegenConfig's `name` field in package.json
*/
#import "ScreenOrientationPackage.h"
// Each turbo module extends codegenerated spec class
@interface ScreenOrientationModule () <NativeScreenOrientationModuleSpec>
@end
#endif
@interface ScreenOrientationModule () <ScreenOrientationModuleDelegate>
@end
// Declare the ObjC implementation for that native module class
@implementation ScreenOrientationModule {
ScreenOrientationModuleImpl *moduleImpl;
BOOL hasListeners;
}
// Return the name of the module - it should match the name provided in JS specification
RCT_EXPORT_MODULE(ScreenOrientationModule)
- (instancetype)init {
self = [super init];
if (self) {
moduleImpl = [ScreenOrientationModuleImpl new];
moduleImpl.delegate = self;
}
return self;
}
// Declare if module should be initialized on the main queue
+ (BOOL)requiresMainQueueSetup
{
return NO;
}
// Declare which events will be emitted by the module
- (NSArray<NSString *> *)supportedEvents
{
return [ScreenOrientationModuleImpl supportedEvents];
}
- (void)startObserving
{
hasListeners = YES;
}
- (void)stopObserving
{
hasListeners = NO;
}
- (void)sendEventWithName:(NSString * _Nonnull)eventName
payload:(NSDictionary<NSString *,id> * _Nonnull)payload
{
if (hasListeners) {
[self sendEventWithName:eventName body:payload];
}
}
/**
* Declare constants exported by the module
*/
#if RCT_NEW_ARCH_ENABLED
- (facebook::react::ModuleConstants<JS::NativeScreenOrientationModule::Constants::Builder>)constantsToExport
{
return [self getConstants];
}
- (facebook::react::ModuleConstants<JS::NativeScreenOrientationModule::Constants::Builder>)getConstants {
return facebook::react::typedConstants<JS::NativeScreenOrientationModule::Constants::Builder>({
.PORTRAIT = @"portrait",
.LANDSCAPE = @"landscape"
});
}
#else
- (NSDictionary *)constantsToExport
{
return @{ @"PORTRAIT": @"portrait", @"LANDSCAPE": @"landscape" };
}
#endif
#if RCT_NEW_ARCH_ENABLED
// Implement RCTTurboModule protocol
- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:
(const facebook::react::ObjCTurboModule::InitParams &)params
{
return std::make_shared<facebook::react::NativeScreenOrientationModuleSpecJSI>(params);
}
#endif
@end
The complete flow when the orientation changes will look like that:
Orientation notification is emitted -> Notification handler is receiving the notification -> Notification handler is determining if the event should be emitted -> Notification handler emits event to the Delegate class
ScreenOrientationModuleImpl.h
#import <Foundation/Foundation.h>
typedef NS_ENUM(NSUInteger, ScreenOrientationModuleEvent) {
OnScreenOrientationModuleChange,
};
extern NSString * _Nonnull ScreenOrientationModuleEventName(ScreenOrientationModuleEvent event);
@protocol ScreenOrientationModuleDelegate
- (void)sendEventWithName:(NSString * _Nonnull)eventName
payload:(NSDictionary<NSString *, id> * _Nonnull)payload;
@end
@interface ScreenOrientationModuleImpl : NSObject
@property (nonatomic, weak) id <ScreenOrientationModuleDelegate> _Nullable delegate;
@end
@interface ScreenOrientationModuleImpl ()
+ (NSArray<NSString *> * _Nonnull)supportedEvents;
@end
Here, we declare ScreenOrientationModuleImpl
class together with ScreenOrientationModuleDelegate
protocol.
The class will have weak delegate property and a static getter. Also at the top, we declare an enum for possible events (in this case there will only be one).
Now let's implement the class in the implementation file
ScreenOrientationModuleImpl.mm
Let's start by connecting to the orientation notifications.
To listen to orientation events, we will register this class as an observer on the default instance of NSNotificationCenter
.
Additionally, to trigger device orientation notifications, we need to explicitly start/stop them
with beginGeneratingDeviceOrientationNotifications
and endGeneratingDeviceOrientationNotifications
methods.
#import "ScreenOrientationModuleImpl.h"
/**
* Native module's shared implementation
*/
@implementation ScreenOrientationModuleImpl {
NSString *lastOrientation;
}
- (instancetype)init
{
self = [super init];
if (self) {
lastOrientation = @"unknown";
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleOrientationChange:) name:UIDeviceOrientationDidChangeNotification object:[UIDevice currentDevice]];
dispatch_async(dispatch_get_main_queue(), ^{
[[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications];
});
}
return self;
}
- (void)dealloc
{
dispatch_async(dispatch_get_main_queue(), ^{
[[UIDevice currentDevice] endGeneratingDeviceOrientationNotifications];
});
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
- (void)handleOrientationChange:(NSNotification *)notification
{
//
}
@end
You can notice, that ScreenOrientationModuleImpl
class overrides initializer and deinitializer.
Inside initializer, we register the module as an observer of the notification (via addObserver(_:selector:name:object:)
) and we start generating orientation notifications.
When the class ends its life, those operations should be reverted. And that's done in the dealloc
function - orientation notifications are stopped, and the listener is unregistered (via removeObserver(_:)
).
Received notification will be handled inside - handleOrientationChange
, which will be implemented in the next step.
#import "ScreenOrientationModuleImpl.h"
NSString *ScreenOrientationModuleEventName(ScreenOrientationModuleEvent event)
{
switch (event) {
case OnScreenOrientationModuleChange:
return @"onScreenOrientationModuleChange";
}
}
/**
* Native module's shared implementation
*/
@implementation ScreenOrientationModuleImpl {
NSString *lastOrientation;
}
- (instancetype)init
{
self = [super init];
if (self) {
lastOrientation = @"unknown";
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleOrientationChange:) name:UIDeviceOrientationDidChangeNotification object:[UIDevice currentDevice]];
dispatch_async(dispatch_get_main_queue(), ^{
[[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications];
});
}
return self;
}
- (void)dealloc
{
dispatch_async(dispatch_get_main_queue(), ^{
[[UIDevice currentDevice] endGeneratingDeviceOrientationNotifications];
});
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
/**
* Example usage:
*
* ```objc
* [self sendEvent:<%- moduleName %>EventName(On<%- moduleName %>Change) payload:@{ @"value" : result }];
* ````
*/
- (void)sendEvent:(NSString *)name payload:(NSDictionary<NSString *, id> *)payload {
[self.delegate sendEventWithName:name payload:payload];
}
+ (NSArray<NSString *> *)supportedEvents
{
return @[ScreenOrientationModuleEventName(OnScreenOrientationModuleChange)];
}
- (void)handleOrientationChange:(NSNotification *)notification
{
UIDeviceOrientation currentOrientation = [[UIDevice currentDevice] orientation];
NSString *orientation = @"unknown";
if (currentOrientation == UIDeviceOrientationPortrait || currentOrientation == UIDeviceOrientationPortraitUpsideDown) {
orientation = @"portrait";
} else if (currentOrientation == UIDeviceOrientationLandscapeLeft || currentOrientation == UIDeviceOrientationLandscapeRight) {
orientation = @"landscape";
}
if (lastOrientation == orientation) {
return;
}
lastOrientation = orientation;
[self sendEvent:ScreenOrientationModuleEventName(OnScreenOrientationModuleChange)
payload:@{@"orientation": orientation}];
}
@end
We added 3 snippets of code, the 1st one implements helper function that translates Objective-C enum to the string.
The 2nd snippet adds a convienient helper for emitting events to the delegate and implements supportedEvents
static getter.
The last snippet is adding the logic that compares current orientation value with the previous one and sends an event when there's new value to be emitted.
ScreenOrientationModule.h
Now, let's go to the module to glue the native events and constants to the JS code:
#import <React/RCTEventEmitter.h>
/**
* Declare the ObjC interface for that native module class.
*
* It must extend NSObject (like every class in ObjC) and
* implement RCTBridgeModule (like each RN native module).
*
* If the module emits events, it must extend RCTEventEmitter class.
*/
@interface ScreenOrientationModule : RCTEventEmitter
@end
In the header file for ScreenOrientationModule
class we declare our class extending RCTEventEmitter
(as each RN iOS module with events emitting capability).
ScreenOrientationModule.mm
#import "ScreenOrientationModule.h"
#import "ScreenOrientationModuleImpl.h"
#if RCT_NEW_ARCH_ENABLED
/**
* Import header file with codegenerated protocols based on the JS specification
*
* The name of the header matches the name provided in codegenConfig's `name` field in package.json
*/
#import "ScreenOrientationPackage.h"
// Each turbo module extends codegenerated spec class
@interface ScreenOrientationModule () <NativeScreenOrientationModuleSpec>
@end
#endif
// Declare the ObjC implementation for that native module class
@implementation ScreenOrientationModule
// Return the name of the module - it should match the name provided in JS specification
RCT_EXPORT_MODULE(ScreenOrientationModule)
#if RCT_NEW_ARCH_ENABLED
// Implement RCTTurboModule protocol
- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:
(const facebook::react::ObjCTurboModule::InitParams &)params
{
return std::make_shared<facebook::react::NativeScreenOrientationModuleSpecJSI>(params);
}
#endif
@end
This is the implementation file for ScreenOrientationModule
class.
Before declaring the implementation block, we have to import ScreenOrientationModuleImpl
header,
and we have to implement code-generated spec protocol for new architecture (code between #if RCT_NEW_ARCH_ENABLED
& #endif
directives).
After that, inside implementation block we have RCT_EXPORT_MODULE
macro invoked with ScreenOrientationModule
argument. This basically registers the module with provided name on iOS side, so that it's accessible in the JS world.
Also, for new architecture, it implements - getTurboModule:
method (from RCTTurboModule
protocol).
If you won't implement that method, XCode will complain that the code-generated spec protocol methods are not implemented.
Let's use ScreenOrientationModuleImpl
class
// ...
@interface ScreenOrientationModule () <ScreenOrientationModuleDelegate>
@end
// Declare the ObjC implementation for that native module class
@implementation ScreenOrientationModule {
ScreenOrientationModuleImpl *moduleImpl;
BOOL hasListeners;
}
// Return the name of the module - it should match the name provided in JS specification
RCT_EXPORT_MODULE(ScreenOrientationModule)
- (instancetype)init {
self = [super init];
if (self) {
moduleImpl = [ScreenOrientationModuleImpl new];
moduleImpl.delegate = self;
}
return self;
}
// Declare if module should be initialized on the main queue
+ (BOOL)requiresMainQueueSetup
{
return NO;
}
// ...
@end
First, we declare moduleImpl
private variable on ScreenOrientationModule
that will be initialized in the - init
method.
We can use ScreenOrientationModuleImpl
Swift class, because it's imported from ScreenOrientationPackage-Swift.h
.
We also declare hasListeners
variable, we will use it to check if there're any JS listeners registered to the module.
Next, we override static method + requiresMainQueueSetup
, which must be done whenever - init
method on the RN's wrapper class is overriden.
To handle events-specific methods from parent RCTEventEmitter
class, we need to override 3 methods:
// ...
@implementation ScreenOrientationModule {
ScreenOrientationModuleImpl *moduleImpl;
BOOL hasListeners;
}
// ...
// Declare if module should be initialized on the main queue
+ (BOOL)requiresMainQueueSetup
{
return NO;
}
// Declare which events will be emitted by the module
- (NSArray<NSString *> *)supportedEvents
{
return [ScreenOrientationModuleImpl supportedEvents];
}
- (void)startObserving
{
hasListeners = YES;
}
- (void)stopObserving
{
hasListeners = NO;
}
// ...
@end
To mark which events will be emitted by the module, we need to override - supportedEvents
getter - we use the value from ScreenOrientationModuleImpl
class as a source of truth.
Two other methods, are called when the first/last JS observer is registered/unregistered.
We still have to emit the events received from ScreenOrientationModuleImpl
- as you may noticed, XCode displayed a warning about ScreenOrientationModuleDelegate
.
The module class is marked to be implementing the delegate protocol,
the ScreenOrientationModuleImpl
delegate property is set to this module instance,
but we still need to implement delegate's methods.
// ...
@implementation ScreenOrientationModule {
ScreenOrientationModuleImpl *moduleImpl;
BOOL hasListeners;
}
// ...
- (void)stopObserving
{
hasListeners = NO;
}
- (void)sendEventWithName:(NSString * _Nonnull)eventName
payload:(NSDictionary<NSString *,id> * _Nonnull)payload
{
if (hasListeners) {
[self sendEventWithName:eventName body:payload];
}
}
// ...
@end
In the delegate's method, the module checks if there's any receiver that listens to events and if yes, invokes - sendEventWithName:body:
method from RCTEventEmitter
base class.
The last thing to do is to export constants to the JS world.
// ...
@implementation ScreenOrientationModule {
ScreenOrientationModuleImpl *moduleImpl;
BOOL hasListeners;
}
// ...
- (void)sendEventWithName:(NSString * _Nonnull)eventName
payload:(NSDictionary<NSString *,id> * _Nonnull)payload
{
if (hasListeners) {
[self sendEventWithName:eventName body:payload];
}
}
/**
* Declare constants exported by the module
*/
#if RCT_NEW_ARCH_ENABLED
- (facebook::react::ModuleConstants<JS::NativeScreenOrientationModule::Constants::Builder>)constantsToExport
{
return [self getConstants];
}
- (facebook::react::ModuleConstants<JS::NativeScreenOrientationModule::Constants::Builder>)getConstants {
return facebook::react::typedConstants<JS::NativeScreenOrientationModule::Constants::Builder>({
.PORTRAIT = @"portrait",
.LANDSCAPE = @"landscape"
});
}
#else
- (NSDictionary *)constantsToExport
{
return @{ @"PORTRAIT": @"portrait", @"LANDSCAPE": @"landscape" };
}
#endif
// ...
@end
In old architecture mode, it's super simple, we have to return an object (NSDictionary *
) from - constantsToExport
method.
In new architecture mode, it looks a bit more complex:
- we use code-generated type-safe C++ structs instead of
NSDictionary *
type - together with
- constantsToExport
we have to override- getConstants
method (both return type-safe C++ structs) - to produce type-safe struct, we use
facebook::react::typedConstants
function
If you're wondering where all those C++ structs are defined, you can find #import "ScreenOrientationPackage.h"
in XCode, Cmd+Click on it and select "Jump to Definition" action.
Complete ScreenOrientationModule.mm
file
#import "ScreenOrientationModule.h"
#import "ScreenOrientationModuleImpl.h"
#if RCT_NEW_ARCH_ENABLED
/**
* Import header file with codegenerated protocols based on the JS specification
*
* The name of the header matches the name provided in codegenConfig's `name` field in package.json
*/
#import "ScreenOrientationPackage.h"
// Each turbo module extends codegenerated spec class
@interface ScreenOrientationModule () <NativeScreenOrientationModuleSpec>
@end
#endif
@interface ScreenOrientationModule () <ScreenOrientationModuleDelegate>
@end
// Declare the ObjC implementation for that native module class
@implementation ScreenOrientationModule {
ScreenOrientationModuleImpl *moduleImpl;
BOOL hasListeners;
}
// Return the name of the module - it should match the name provided in JS specification
RCT_EXPORT_MODULE(ScreenOrientationModule)
- (instancetype)init {
self = [super init];
if (self) {
moduleImpl = [ScreenOrientationModuleImpl new];
moduleImpl.delegate = self;
}
return self;
}
// Declare if module should be initialized on the main queue
+ (BOOL)requiresMainQueueSetup
{
return NO;
}
// Declare which events will be emitted by the module
- (NSArray<NSString *> *)supportedEvents
{
return [ScreenOrientationModuleImpl supportedEvents];
}
- (void)startObserving
{
hasListeners = YES;
}
- (void)stopObserving
{
hasListeners = NO;
}
- (void)sendEventWithName:(NSString * _Nonnull)eventName
payload:(NSDictionary<NSString *,id> * _Nonnull)payload
{
if (hasListeners) {
[self sendEventWithName:eventName body:payload];
}
}
/**
* Declare constants exported by the module
*/
#if RCT_NEW_ARCH_ENABLED
- (facebook::react::ModuleConstants<JS::NativeScreenOrientationModule::Constants::Builder>)constantsToExport
{
return [self getConstants];
}
- (facebook::react::ModuleConstants<JS::NativeScreenOrientationModule::Constants::Builder>)getConstants {
return facebook::react::typedConstants<JS::NativeScreenOrientationModule::Constants::Builder>({
.PORTRAIT = @"portrait",
.LANDSCAPE = @"landscape"
});
}
#else
- (NSDictionary *)constantsToExport
{
return @{ @"PORTRAIT": @"portrait", @"LANDSCAPE": @"landscape" };
}
#endif
#if RCT_NEW_ARCH_ENABLED
// Implement RCTTurboModule protocol
- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:
(const facebook::react::ObjCTurboModule::InitParams &)params
{
return std::make_shared<facebook::react::NativeScreenOrientationModuleSpecJSI>(params);
}
#endif
@end
You can check training repo for Objective-C & Swift implementation here and Objective-C-only implementation here.
That's iOS part, now let's go to Android!