Skip to content

Commit

Permalink
fix(iOS): migrate to RCT_EXPORT_METHOD.
Browse files Browse the repository at this point in the history
  • Loading branch information
asafkorem committed Jan 13, 2025
1 parent ece4dc3 commit 0197120
Show file tree
Hide file tree
Showing 13 changed files with 319 additions and 302 deletions.
6 changes: 3 additions & 3 deletions detox/test/e2e/06.device.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ describe('Device', () => {
it(':ios: launchApp in a different language', async () => {
let languageAndLocale = {
language: "es-MX",
locale: "es-MX"
locale: "en_MX"
};

await device.launchApp({newInstance: true, languageAndLocale});
Expand All @@ -53,7 +53,7 @@ describe('Device', () => {

languageAndLocale = {
language: "en-US",
locale: "en-US"
locale: "en_US"
};

await device.launchApp({newInstance: true, languageAndLocale});
Expand All @@ -75,7 +75,7 @@ describe('Device', () => {
await device.reloadReactNative();
await element(by.text('Shake')).tap();
await device.shake();
await expect(element(by.text('Shaken, not stirred'))).toBeVisible();
await expect(element(by.text('Shaken, not stirred'))).toExist();
});

it(':android: device back button - should show popup back pressed when back button is pressed', async () => {
Expand Down
16 changes: 16 additions & 0 deletions detox/test/ios/ReactModules/NativeModule.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
//
// NativeModule.h (example)
// Created by Asaf Korem (Wix.com) on 2025.
//

#import <React/RCTBridgeModule.h>
#import <React/RCTEventEmitter.h>
#import <UIKit/UIKit.h>

@interface NativeModule : NSObject <RCTBridgeModule>

@property (nonatomic, strong) UIWindow *overlayWindow;
@property (nonatomic, strong) UIView *overlayView;
@property (nonatomic, assign) NSInteger callCounter;

@end
189 changes: 189 additions & 0 deletions detox/test/ios/ReactModules/NativeModule.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
//
// NativeModule.m (example)
// Created by Asaf Korem (Wix.com) on 2025.
//

#import "NativeModule.h"
#import <React/RCTRootView.h>
#import <React/RCTBridge.h>

@implementation NativeModule

RCT_EXPORT_MODULE();

- (instancetype)init {
if (self = [super init]) {
self.callCounter = 0;
}
return self;
}

+ (BOOL)requiresMainQueueSetup {
return YES;
}

// MARK: - Locale Methods
RCT_EXPORT_METHOD(getUserLocale:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject) {
NSLocale *currentLocale = [NSLocale currentLocale];
NSString *localeIdentifier = [currentLocale localeIdentifier];
resolve(localeIdentifier);
}

RCT_EXPORT_METHOD(getUserLanguage:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject) {
NSString *languageCode = [[NSLocale preferredLanguages] firstObject];
if (languageCode) {
resolve(languageCode);
} else {
NSError *error = [NSError errorWithDomain:@"NativeModule"
code:1
userInfo:@{NSLocalizedDescriptionKey: @"Could not determine user language"}];
reject(@"no_language", @"Could not determine user language", error);
}
}

// MARK: - Echo Methods
RCT_EXPORT_METHOD(echoWithoutResponse:(NSString *)str) {
self.callCounter++;
}

RCT_EXPORT_METHOD(echoWithResponse:(NSString *)str
resolver:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject) {
self.callCounter++;
resolve(str);
}

// MARK: - Timing Methods
RCT_EXPORT_METHOD(nativeSetTimeout:(NSTimeInterval)delay
block:(RCTResponseSenderBlock)block) {
dispatch_time_t dispatchTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delay * NSEC_PER_SEC));
dispatch_after(dispatchTime, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
dispatch_async(dispatch_get_main_queue(), ^{
block(@[]);
});
});
}

// MARK: - Navigation Methods
RCT_EXPORT_METHOD(switchToNativeRoot) {
dispatch_async(dispatch_get_main_queue(), ^{
UIViewController *newRoot = [self createNativeRootViewController];
[self updateRootViewController:newRoot];
});
}

RCT_EXPORT_METHOD(switchToMultipleReactRoots) {
dispatch_async(dispatch_get_main_queue(), ^{
UITabBarController *tabController = [self createTabBarControllerWithBridge];
[self updateRootViewController:tabController];
});
}

// MARK: - Notification Methods
RCT_EXPORT_METHOD(sendNotification:(NSString *)notification
name:(NSString *)name) {
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:notification
object:nil
userInfo:@{@"name": name}];
});
}

// MARK: - Overlay Methods
RCT_EXPORT_METHOD(presentOverlayWindow) {
dispatch_async(dispatch_get_main_queue(), ^{
[self setupAndShowOverlayWindow];
});
}

RCT_EXPORT_METHOD(presentOverlayView) {
dispatch_async(dispatch_get_main_queue(), ^{
[self setupAndShowOverlayView];
});
}

// MARK: - Private Helper Methods
- (UIViewController *)createNativeRootViewController {
UIViewController *newRoot = [[UIViewController alloc] init];
newRoot.view.backgroundColor = [UIColor whiteColor];

UILabel *label = [[UILabel alloc] init];
label.text = @"this is a new native root";
[label sizeToFit];
label.center = newRoot.view.center;
[newRoot.view addSubview:label];

return newRoot;
}

- (UITabBarController *)createTabBarControllerWithBridge {
RCTBridge *bridge = [self getCurrentBridge];
if (!bridge) {
return nil;
}

NSMutableArray *viewControllers = [NSMutableArray array];
for (NSInteger i = 1; i <= 4; i++) {
UIViewController *vc = [self createReactRootViewControllerWithBridge:bridge
title:[NSString stringWithFormat:@"%ld", (long)i]];
[viewControllers addObject:vc];
}

UITabBarController *tabController = [[UITabBarController alloc] init];
tabController.viewControllers = viewControllers;
return tabController;
}

- (UIViewController *)createReactRootViewControllerWithBridge:(RCTBridge *)bridge title:(NSString *)title {
UIViewController *viewController = [[UIViewController alloc] init];
viewController.view = [[RCTRootView alloc] initWithBridge:bridge
moduleName:@"example"
initialProperties:nil];
viewController.tabBarItem.title = title;
return viewController;
}

- (RCTBridge *)getCurrentBridge {
id appDelegate = [[UIApplication sharedApplication] delegate];
if ([appDelegate respondsToSelector:@selector(window)]) {
UIWindow *window = [appDelegate window];
RCTRootView *rootView = (RCTRootView *)window.rootViewController.view;
if ([rootView isKindOfClass:[RCTRootView class]]) {
return rootView.bridge;
}
}
return nil;
}

- (void)updateRootViewController:(UIViewController *)viewController {
id appDelegate = [[UIApplication sharedApplication] delegate];
if ([appDelegate respondsToSelector:@selector(window)]) {
UIWindow *window = [appDelegate window];
window.rootViewController = viewController;
[window makeKeyAndVisible];
}
}

- (void)setupAndShowOverlayWindow {
CGRect screenBounds = [[UIScreen mainScreen] bounds];
self.overlayWindow = [[UIWindow alloc] initWithFrame:screenBounds];
self.overlayWindow.accessibilityIdentifier = @"OverlayWindow";
self.overlayWindow.windowLevel = UIWindowLevelStatusBar;
self.overlayWindow.hidden = NO;
[self.overlayWindow makeKeyAndVisible];
}

- (void)setupAndShowOverlayView {
UIWindow *keyWindow = [[UIApplication sharedApplication] keyWindow];
if (!keyWindow) return;

CGRect screenBounds = [[UIScreen mainScreen] bounds];
self.overlayView = [[UIView alloc] initWithFrame:screenBounds];
self.overlayView.userInteractionEnabled = YES;
self.overlayView.accessibilityIdentifier = @"OverlayView";
[keyWindow addSubview:self.overlayView];
}

@end
Loading

0 comments on commit 0197120

Please sign in to comment.