Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
React Native Event Emitter for RCTEventEmitter in Objective-C and Swift

React Native Event Emitter with Swift

You are building a React Native app and want to work in Swift as much as possible while minimizing Objective-C. This task can be a real challenge. Since the Objective-C runtime is leveraged for communicating with the JavaScript context and Swift does not support macros it will be necessary to use Objective-C to bridge React Native and Swift.

Read Exporting Swift to get an understanding of how it can be done. First you need an Objective-C implementation file. In this case it is called ReactNativeEventEmitter.m and it has what you need to make a module and method available to React Native. In ReactNativeEventEmitter.swift you will find the actual implementation with the class and function marked with objc so both are available to the Objective-C runtime.

When your app starts up the module and function will be made available to React Native which will create and instance of the module which is ReactNativeEventEmitter and sets the critical bridge property which allows for communicating between Swift and React Native.

Once React Native initializes ReactNativeEventEmitter it will be registered with EventEmitter so it can be used to send events. Be sure to put all of your events into the array of events returned by supportedEvents as it is used to check for valid events on the React Native side. Events which are not recognized will cause an error.


Brennan Stehling - 2017

class EventEmitter
/// Shared Instance.
public static var sharedInstance = EventEmitter()
// ReactNativeEventEmitter is instantiated by React Native with the bridge.
private static var eventEmitter: ReactNativeEventEmitter!
private init() {}
// When React Native instantiates the emitter it is registered here.
func registerEventEmitter(eventEmitter: ReactNativeEventEmitter) {
EventEmitter.eventEmitter = eventEmitter
}
func dispatch(name: String, body: Any?) {
eventEmitter.sendEvent(withName: name, body: body)
}
/// All Events which must be support by React Native.
lazy var allEvents: [String] = {
var allEventNames: [String] = []
// Append all events here
return allEventNames
}()
}
//
// ReactNativeEventEmitter.m
// See: http://facebook.github.io/react-native/releases/0.43/docs/native-modules-ios.html#exporting-swift
//
#import <Foundation/Foundation.h>
#import <React/RCTBridgeModule.h>
#import <React/RCTEventEmitter.h>
@interface RCT_EXTERN_MODULE(ReactNativeEventEmitter, RCTEventEmitter)
RCT_EXTERN_METHOD(supportedEvents)
@end
//
// ReactNativeEventEmitter.swift
//
import Foundation
@objc(ReactNativeEventEmitter)
open class ReactNativeEventEmitter: RCTEventEmitter {
override init() {
super.init()
EventEmitter.sharedInstance.registerEventEmitter(eventEmitter: self)
}
/// Base overide for RCTEventEmitter.
///
/// - Returns: all supported events
@objc open override func supportedEvents() -> [String] {
return EventEmitter.sharedInstance.allEvents
}
}
@liumengdi

This comment has been minimized.

Copy link

commented Jun 13, 2017

thanks

@hfossli

This comment has been minimized.

Copy link

commented Aug 10, 2017

awesome

@silvainSayduck

This comment has been minimized.

Copy link

commented Sep 17, 2017

Works wonders, thanks!

Just a couple typos:

  • EventEmitter.swift#L1: class EventEmitter -> class EventEmitter {
  • EventEmitter.swift#L17: eventEmitter.sendEvent(withName: name, body: body) -> EventEmitter.eventEmitter.sendEvent(withName: name, body: body)
  • Need to import React in ReactNativeEventEmitter.swift
@tirrorex

This comment has been minimized.

Copy link

commented Nov 21, 2017

"ReactNativeEventEmitter is instantiated by React Native with the bridge." Can't find any documentation on that, do you happen to have the source?

@GiladR1979

This comment has been minimized.

Copy link

commented Jan 31, 2018

You need to add
#import <React/RCTEventEmitter.h>
to ReactNativeSeed-Bridging-Header.h

@minhduchua

This comment has been minimized.

Copy link

commented May 18, 2018

This is great, thanks so much!

I think the EventEmitter class could be declared open to be accessed from anywhere in the iOS code: EventEmitter.swift#L1: class EventEmitter -> open class EventEmitter {

I would also like to share the explaination below:

//
//  This is a singleton instance registering the ReactNativeEventEmitter from
//  which events can be dispatched to React Native from native iOS:
//   - ReactNativeEventEmitter subclasses RCTEventEmitter,
//   - classes subclassing RCTEventEmitter should not be initialized from iOS code because it
//     is automatically instantiated by React Native (see https://stackoverflow.com/a/48716140/5612908)
//   - initializing classes subclassing RCTEventEmitter in the iOS code results in error
//     "Bridge is not set. This is probably because you've explicitly synthesized the bridge in
//     <className> even though it's inherited from RCTEventEmitter sendEvent"
//   - the EventEmitter singleton does not initialize ReactNativeEventEmitter, thus avoiding the
//     error above, it waits for React Native to initialize it and registers the instance instead
//   - the EventEmitter singleton can then be called from anywhere in iOS code, e.g
//       EventEmitter.sharedInstance.dispatch(
//         withName: <eventName>,
//         body: ["<eventPayload1>": <eventPayload1>, "<eventPayload2>": <eventPayload2>...]
//     )
//

Thanks again!

@abhaynpai

This comment has been minimized.

Copy link

commented Jun 29, 2018

I am getting an error

Sending 'message' with no listeners registered

where message is the event that I am listening in React and this is working perfectly fine in Android but not with iOS. What can be the issue here ?

@fritzherald

This comment has been minimized.

Copy link

commented Jul 2, 2018

@abhaypai2611 this could be an issue on your JS side -- on iOS you need to make sure to register your listener with NativeEventEmitter rather than DeviceEventEmitter...

import { DeviceEventEmitter, NativeEventEmitter, NativeModules, Platform } from 'react-native'
// ...
const emitter = Platform.OS === 'ios' ? new NativeEventEmitter(NativeModules.ReactNativeEventEmitter) : DeviceEventEmitter
const listener = emitter.addListener(<eventName>, <eventHandler>)
@rjruizes

This comment has been minimized.

Copy link

commented Oct 2, 2018

I found this guide also helpful in sending Swift to Javascript events.

@bitbldr

This comment has been minimized.

Copy link

commented Nov 2, 2018

This really helped me solve an issue I was having trying to relay native events from iOS to JS, thanks so much!

I needed to fire events from my UIViewController to JavaScript and struggled to find a method that actually worked until I stumbled upon this. I had to do the following to get this to work out-of-the-box:

  1. In EventEmitter.swift, make eventEmitter non-static by changing:
private static var eventEmitter: ReactNativeEventEmitter!

to

private var eventEmitter: ReactNativeEventEmitter!

Then in my UIViewController where I was responding to a native event, I had to reference the singleton using EventEmitter.sharedInstance

EventEmitter.sharedInstance.dispatch(name: "Test", body: "Hello")

👏 👏 👏

@b-asaf

This comment has been minimized.

Copy link

commented Jan 22, 2019

@brennanMKE thanks for the this code!
I think @silvainSayduck comment is important as well and it should be inserted

I did the following and I have couple of questions
1st Question

  1. I wrote main module with all the businesses logic.
  2. I added eventEmitter as described above, so I will have a dedicated class that handle the events.
  3. How do I expose it now to the client side, currently my js API of the native module is:
import { NativeModules } from 'react-native';
var myModule = NativeModules.MyModule;

class myModule {

    async start() {
        return await myModule.start()
    }

    checkState() {
        myModule.checkState();
    }
}
module.exports = new myModule();

2nd Question
When adding a new event I am doing it like this:
in my myModule.swift:
EventEmitter.eventEmitter.sendEvent(withName: "eventName", body: "value")
and in EventEmitter.swift:

lazy var allEvents: [String] = {
  var allEventNames: [String] = [
    "eventNameA"
  ]

  // Append all events here
  return allEventNames
}()
@crusshapp

This comment has been minimized.

Copy link

commented Mar 4, 2019

Hello guys,

If you are getting bridge is not set error, please take a look at this link Youtube

@Gopinatha03

This comment has been minimized.

Copy link

commented Oct 4, 2019

when I implemented the same in objective c , the event emitter subclass: stopObserving methods executed immediately after the startObserving methods called .

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.