알람앱을 만들면서 ReactNative의 DeviceEventEmitter 를 직접적으로 커스터마이징이 필요한 상황이 오게 되었다.
IOS 에서 어떻게 RCTEmitter 를 커스터마이징하여 적용할지 찾아보았다.
흔들기 기능을 추가하려면 흔들기 감지하고 그것을 실시간 보내주어야 하는데, 그부분을 모듈화하여 ReactNative 안에서 실행하려고 했다.
🚀 React Native iOS 커스텀 RCTEmitter 생성하기
React Native에서 iOS용 커스텀 이벤트 모듈을 생성하려면 RCTEventEmitter를 활용하면 돼.
iOS 네이티브 코드(Objective-C 또는 Swift)에서 이벤트를 발생시키고, React Native(JavaScript)에서 이를 **구독(listen)**할 수 있어!
🟢 1. RCTEventEmitter란?
RCTEventEmitter는 iOS 네이티브 코드에서 JS로 이벤트를 전달하는 인터페이스야.
iOS의 네이티브 기능(예: 센서 값, 네트워크 상태 변화, Bluetooth 이벤트 등)을 JavaScript에서 감지할 때 사용해.
✅ 사용 예시:
- 네이티브 센서 값 변화 → React Native에서 감지
- 네이티브 네트워크 상태 변경 → React Native에서 감지
- 커스텀 네이티브 이벤트 실행 후 JS로 결과 전달
🔵 2. iOS 네이티브 모듈(RCTEventEmitter) 생성하기
✅ 1) 네이티브 모듈 생성 (MyCustomEventEmitter.m)
#import <React/RCTBridgeModule.h>
#import <React/RCTEventEmitter.h>
@interface RCT_EXTERN_MODULE(MyCustomEventEmitter, RCTEventEmitter)
RCT_EXTERN_METHOD(supportedEvents)
RCT_EXTERN_METHOD(triggerEvent:(NSString *)message)
@end
import Foundation
import React
@objc(MyCustomEventEmitter)
class MyCustomEventEmitter: RCTEventEmitter {
// 지원하는 이벤트 목록 정의
override func supportedEvents() -> [String]! {
return ["CustomEvent"]
}
// iOS에서 이벤트를 발생시키는 함수
@objc func triggerEvent(_ message: String) {
sendEvent(withName: "CustomEvent", body: ["message": message])
}
// iOS에서는 기본적으로 구독자가 있을 때만 이벤트 전송이 가능
override func startObserving() {
print("✅ 이벤트 리스너 시작됨")
}
override func stopObserving() {
print("🛑 이벤트 리스너 중지됨")
}
}
📌 설명
✔ supportedEvents() → JS에서 구독할 이벤트 이름 정의
✔ sendEvent(withName:body:) → JS로 이벤트를 전송하는 함수
✔ triggerEvent(_ message: String) → 외부에서 이벤트를 실행하는 함수
✔ startObserving() / stopObserving() → JS에서 이벤트 리스너 등록/해제 시 실행됨
✅ 2) React Native에 모듈 등록 (MyCustomEventEmitter.h)
#import <React/RCTBridgeModule.h>
#import <React/RCTEventEmitter.h>
@interface MyCustomEventEmitter : RCTEventEmitter <RCTBridgeModule>
@end
#import "MyCustomEventEmitter.h"
@implementation MyCustomEventEmitter
RCT_EXPORT_MODULE();
- (NSArray<NSString *> *)supportedEvents {
return @[@"CustomEvent"];
}
@end
📌 설명
✔ RCT_EXPORT_MODULE(); → React Native에서 이 모듈을 사용할 수 있도록 등록
✔ supportedEvents → CustomEvent를 React Native에서 감지 가능하도록 설정
🔴 3. JavaScript에서 네이티브 이벤트 사용하기
✅ 1) 이벤트 구독 (React Native)
이제 JS에서 네이티브 이벤트를 받을 준비를 해야 해!
📌 App.js 또는 이벤트를 받을 컴포넌트에 추가
import { NativeModules, NativeEventEmitter } from 'react-native';
import { useEffect } from 'react';
const { MyCustomEventEmitter } = NativeModules;
const eventEmitter = new NativeEventEmitter(MyCustomEventEmitter);
const App = () => {
useEffect(() => {
// 1️⃣ 네이티브 이벤트 리스너 등록
const eventListener = eventEmitter.addListener("CustomEvent", (event) => {
console.log("📢 네이티브 이벤트 수신:", event.message);
});
// 2️⃣ 네이티브 이벤트 직접 호출 (테스트용)
MyCustomEventEmitter.triggerEvent("안녕하세요, iOS에서 왔어요!");
// 3️⃣ 언마운트 시 리스너 제거
return () => {
eventListener.remove();
};
}, []);
return null;
};
export default App;
📌 설명
✔ NativeEventEmitter(MyCustomEventEmitter) → 네이티브 모듈을 JS에서 이벤트 리스너로 변환
✔ eventEmitter.addListener("CustomEvent", callback) → iOS에서 발생한 이벤트를 감지
✔ MyCustomEventEmitter.triggerEvent("메시지") → JS에서 직접 네이티브 이벤트 실행
✔ useEffect() 내부에서 이벤트 리스너 등록 & 제거 (컴포넌트가 언마운트되면 리스너도 정리)
우선 , .Swift 와 .h 을 만들어야한다 . 그리고 흔들기 기능관련된 부분을 추가해준다.

import Foundation
import UIKit
import AVFoundation
import React
import CoreMotion
@objc(ShakeModule)
class ShakeModule : RCTEventEmitter {
var motionManager: CMMotionManager!
override init() {
super.init()
self.motionManager = CMMotionManager()
self.motionManager.accelerometerUpdateInterval = 0.2
}
override class func requiresMainQueueSetup() -> Bool {
return true
}
override func supportedEvents() -> [String] {
return ["ShakeEvent"]
}
@objc
func startAccelerometerUpdates() {
self.motionManager.startAccelerometerUpdates(to: .main) { [weak self] data, error in
guard let x = data?.acceleration.x,
let y = data?.acceleration.y,
let z = data?.acceleration.z else {
return
}
let speed = sqrt(pow(x, 2)) + sqrt(pow(y, 2)) + sqrt(pow(z, 2))
if speed > 2.0 {
self?.sendEvent(withName: "ShakeEvent", body: ["isShake": true])
} else {
self?.sendEvent(withName: "ShakeEvent", body: ["isShake": false])
}
}
}
@objc
func stopAccelerometerUpdates() {
self.motionManager.stopAccelerometerUpdates()
}
}

#import <Foundation/Foundation.h>
#import <React/RCTBridgeModule.h>
#import <React/RCTEventEmitter.h>
#import <React/RCTEventDispatcher.h>
@interface RCT_EXTERN_REMAP_MODULE(RNShake ,ShakeModule, RCTEventEmitter)
_RCT_EXTERN_REMAP_METHOD(startShakeInit, startAccelerometerUpdates, false)
_RCT_EXTERN_REMAP_METHOD(stopShakeInit, stopAccelerometerUpdates, false)
@end
그리고 React Native에서

위와 같이 EventEmitter라고 선언해주고 그 값들을 형변환하여 가져온다.

그리고 위와 같이 사용하면 커스터마이징 된 Emitter를 사용할 수 있게 된다 !!
끝!!
참고 *
ReactNative IOS 에서 UIViewController 도 역시 커스터마이징 가능하다

NSDictionary *initProps = [self prepareInitialProps];
UIView *rootView = RCTAppSetupDefaultRootView(bridge, @"alarm", initProps);
rootView.backgroundColor = [UIColor whiteColor];
self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
MainViewController *rootViewController = [MainViewController new];
rootViewController.view = rootView;
self.myAlarm = [[Alarm alloc] init];
[self.myAlarm initBridgeWithBridge:bridge];
[application setApplicationSupportsShakeToEdit:YES];
[application registerForRemoteNotifications];
[application setMinimumBackgroundFetchInterval:UIApplicationBackgroundFetchIntervalMinimum];
UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
center.delegate = self;
AVAudioSession * session = [AVAudioSession sharedInstance];
[session setCategory: AVAudioSessionCategoryPlayback error: nil];
[FIRApp configure];
if (@available(iOS 13.0, *)) {
[[BGTaskScheduler sharedScheduler] registerForTaskWithIdentifier:@"com.alarm.backgroundTasking" usingQueue:nil launchHandler:^(BGTask *task) {
[self.myAlarm appTerminated];
}];
[[BGTaskScheduler sharedScheduler] registerForTaskWithIdentifier:@"com.alarm.backgroundAppRefresh" usingQueue:nil launchHandler:^(BGTask *task) {
}];
} else {
UIApplication* app = [UIApplication sharedApplication];
__block UIBackgroundTaskIdentifier backgroundTask;
backgroundTask = [app beginBackgroundTaskWithExpirationHandler:^{
[app endBackgroundTask:backgroundTask];
}];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[app endBackgroundTask:backgroundTask];
});
}

'개발 > ReactNative' 카테고리의 다른 글
ReactNative_ AOS 커스텀 RCTEmitter 생성 및 적용 (0) | 2024.03.05 |
---|---|
ReactNative_ IOS 빌드 환경 분리 (0) | 2024.03.04 |
ReactNative_ AOS 의 빌드 환경 분리 (0) | 2024.03.04 |