Skip to content

Commit 5f745ef

Browse files
authored
Merge pull request #4841 from wix/bugfix/4760-Swift-Briding
#4760 - reloadApp in swift - using reflection
2 parents 0659af9 + 244d14a commit 5f745ef

File tree

5 files changed

+123
-3
lines changed

5 files changed

+123
-3
lines changed

detox/ios/Detox.xcodeproj/project.pbxproj

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,8 @@
134134
60C1961B271F11C4000172DD /* fishhook.c in Sources */ = {isa = PBXBuildFile; fileRef = 60C19619271F11C4000172DD /* fishhook.c */; };
135135
60E149C72759038F00519EE4 /* UIResponder+First.h in Headers */ = {isa = PBXBuildFile; fileRef = 60E149C52759038F00519EE4 /* UIResponder+First.h */; };
136136
60E149C82759038F00519EE4 /* UIResponder+First.m in Sources */ = {isa = PBXBuildFile; fileRef = 60E149C62759038F00519EE4 /* UIResponder+First.m */; };
137+
7D3919BE2E890DBB00D16E3A /* DetoxSwiftBridge.h in Headers */ = {isa = PBXBuildFile; fileRef = 7D3919BA2E890DBB00D16E3A /* DetoxSwiftBridge.h */; };
138+
7D3919C02E890F0E00D16E3A /* DetoxSwiftBridge.m in Sources */ = {isa = PBXBuildFile; fileRef = 7D3919BF2E890F0E00D16E3A /* DetoxSwiftBridge.m */; };
137139
AD4781082636F7CF006774CD /* NSURL+DetoxUtils.h in Headers */ = {isa = PBXBuildFile; fileRef = AD4781062636F7CE006774CD /* NSURL+DetoxUtils.h */; settings = {ATTRIBUTES = (Private, ); }; };
138140
AD4781092636F7CF006774CD /* NSURL+DetoxUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = AD4781072636F7CF006774CD /* NSURL+DetoxUtils.m */; };
139141
/* End PBXBuildFile section */
@@ -346,6 +348,8 @@
346348
60C19619271F11C4000172DD /* fishhook.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = fishhook.c; path = DetoxSync/DetoxSync/fishhook/fishhook.c; sourceTree = SOURCE_ROOT; };
347349
60E149C52759038F00519EE4 /* UIResponder+First.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "UIResponder+First.h"; sourceTree = "<group>"; };
348350
60E149C62759038F00519EE4 /* UIResponder+First.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "UIResponder+First.m"; sourceTree = "<group>"; };
351+
7D3919BA2E890DBB00D16E3A /* DetoxSwiftBridge.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DetoxSwiftBridge.h; sourceTree = "<group>"; };
352+
7D3919BF2E890F0E00D16E3A /* DetoxSwiftBridge.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DetoxSwiftBridge.m; sourceTree = "<group>"; };
349353
AD4781062636F7CE006774CD /* NSURL+DetoxUtils.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "NSURL+DetoxUtils.h"; sourceTree = "<group>"; };
350354
AD4781072636F7CF006774CD /* NSURL+DetoxUtils.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "NSURL+DetoxUtils.m"; sourceTree = "<group>"; };
351355
/* End PBXFileReference section */
@@ -488,6 +492,7 @@
488492
3980D130244C42E7004812DD /* Utilities */ = {
489493
isa = PBXGroup;
490494
children = (
495+
7D3919BC2E890DBB00D16E3A /* SwiftSupport */,
491496
397CA7982483F07D005E8A71 /* ApproximateEquality.swift */,
492497
39E91C06247D6BB00099F8F3 /* DetoxCrashHandler.h */,
493498
39FFD9451FD730A600C97030 /* DetoxCrashHandler.mm */,
@@ -724,6 +729,15 @@
724729
name = Runtime;
725730
sourceTree = "<group>";
726731
};
732+
7D3919BC2E890DBB00D16E3A /* SwiftSupport */ = {
733+
isa = PBXGroup;
734+
children = (
735+
7D3919BA2E890DBB00D16E3A /* DetoxSwiftBridge.h */,
736+
7D3919BF2E890F0E00D16E3A /* DetoxSwiftBridge.m */,
737+
);
738+
path = SwiftSupport;
739+
sourceTree = "<group>";
740+
};
727741
/* End PBXGroup section */
728742

729743
/* Begin PBXHeadersBuildPhase section */
@@ -755,6 +769,7 @@
755769
3980D10F2448B52C004812DD /* UIApplication+DTXAdditions.h in Headers */,
756770
3980D1172448B52C004812DD /* DTXRunLoopSpinner.h in Headers */,
757771
AD4781082636F7CF006774CD /* NSURL+DetoxUtils.h in Headers */,
772+
7D3919BE2E890DBB00D16E3A /* DetoxSwiftBridge.h in Headers */,
758773
6062B5E32720323700CBDBF0 /* NSArray+Utils.h in Headers */,
759774
3980D135244C4373004812DD /* UIView+DetoxMatchers.h in Headers */,
760775
3980D16D244DDCD7004812DD /* UIScrollView+DetoxActions.h in Headers */,
@@ -1003,6 +1018,7 @@
10031018
3990BA36245AC98C00B608C8 /* DTXAssertionHandler+Swift.swift in Sources */,
10041019
12BDDA602971652D00FDBBA8 /* UIApplication+DetoxActions.swift in Sources */,
10051020
39CA978D245B13CB00A7FC43 /* UIDevice+DetoxActions.m in Sources */,
1021+
7D3919C02E890F0E00D16E3A /* DetoxSwiftBridge.m in Sources */,
10061022
396D455225238BB90096E7FA /* UIView+Drawing.m in Sources */,
10071023
39CE25AC22197F0900D78AA1 /* DetoxInstrumentsManager.m in Sources */,
10081024
3980D1162448B52C004812DD /* DTXTouchInfo.m in Sources */,

detox/ios/Detox/Utilities/ReactNativeSupport/ReactNativeSupport.m

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
#import "ReactNativeSupport.h"
1010
#import "ReactNativeHeaders.h"
11+
#import "DetoxSwiftBridge.h"
1112

1213
#include <dlfcn.h>
1314
#include <stdatomic.h>
@@ -68,16 +69,21 @@ + (NSObject *) getReactHost {
6869
NSObject<UIApplicationDelegate> *appDelegate = UIApplication.sharedApplication.delegate;
6970

7071
NSObject *rootViewFactory = nil;
72+
7173
@try {
7274
rootViewFactory = [appDelegate valueForKey:@"rootViewFactory"];
7375
} @catch (NSException *exception) {
7476
@try {
7577
NSObject *reactNativeFactory = [appDelegate valueForKey:@"reactNativeFactory"];
7678
rootViewFactory = [reactNativeFactory valueForKey:@"rootViewFactory"];
7779
} @catch (NSException *exception) {
78-
[NSException raise:@"Invalid AppDelegate" format:@"Could not access rootViewFactory. Make sure your AppDelegate either: Inherits from RCTAppDelegate Or defines 'reactNativeFactory'" ];
80+
rootViewFactory = [DetoxSwiftBridge getRootViewFactory];
81+
if (rootViewFactory == nil) {
82+
[NSException raise:@"Invalid AppDelegate" format:@"Could not access rootViewFactory. Make sure your AppDelegate either: Inherits from RCTAppDelegate or defines 'reactNativeFactory'" ];
83+
}
7984
}
8085
}
86+
8187
return [rootViewFactory valueForKey:@"reactHost"];
8288
}
8389

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
//
2+
// DetoxSwiftBridge.h
3+
// Detox
4+
//
5+
// Created by Mark de Vocht on 28/09/2025.
6+
// Copyright © 2025 Wix. All rights reserved.
7+
//
8+
9+
#import <Foundation/Foundation.h>
10+
11+
NS_ASSUME_NONNULL_BEGIN
12+
13+
@interface DetoxSwiftBridge : NSObject
14+
+ (nullable NSObject *)getRootViewFactory;
15+
+ (nullable NSObject *)getReactNativeFactory;
16+
@end
17+
18+
NS_ASSUME_NONNULL_END
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
//
2+
// DetoxSwiftBridge.m
3+
// Detox
4+
//
5+
// Created by Mark de Vocht on 28/09/2025.
6+
// Copyright © 2025 Wix. All rights reserved.
7+
//
8+
9+
#import "DetoxSwiftBridge.h"
10+
@import UIKit;
11+
@import ObjectiveC;
12+
13+
DTX_CREATE_LOG(SwiftBridge);
14+
15+
@implementation DetoxSwiftBridge
16+
17+
+ (nullable NSObject *)getRootViewFactory
18+
{
19+
NSObject<UIApplicationDelegate> *appDelegate = UIApplication.sharedApplication.delegate;
20+
if (!appDelegate) {
21+
return nil;
22+
}
23+
24+
NSObject *rootViewFactory = [self getPropertyFromObject:appDelegate named:@"rootViewFactory"];
25+
if (rootViewFactory) {
26+
return rootViewFactory;
27+
}
28+
29+
NSObject *reactNativeFactory = [self getPropertyFromObject:appDelegate named:@"reactNativeFactory"];
30+
if (reactNativeFactory) {
31+
return [self getPropertyFromObject:reactNativeFactory named:@"rootViewFactory"];
32+
}
33+
34+
return nil;
35+
}
36+
37+
+ (nullable NSObject *)getReactNativeFactory
38+
{
39+
NSObject<UIApplicationDelegate> *appDelegate = UIApplication.sharedApplication.delegate;
40+
if (!appDelegate) {
41+
return nil;
42+
}
43+
44+
return [self getPropertyFromObject:appDelegate named:@"reactNativeFactory"];
45+
}
46+
47+
+ (nullable NSObject *)getPropertyFromObject:(NSObject *)object named:(NSString *)propertyName
48+
{
49+
if (!object || !propertyName) {
50+
return nil;
51+
}
52+
53+
Class objectClass = [object class];
54+
while (objectClass) {
55+
unsigned int ivarCount;
56+
Ivar *ivars = class_copyIvarList(objectClass, &ivarCount);
57+
58+
for (unsigned int i = 0; i < ivarCount; i++) {
59+
const char *ivarName = ivar_getName(ivars[i]);
60+
if (ivarName) {
61+
NSString *ivarNameString = [NSString stringWithUTF8String:ivarName];
62+
if ([ivarNameString isEqualToString:propertyName] ||
63+
[ivarNameString isEqualToString:[@"_" stringByAppendingString:propertyName]]) {
64+
NSObject *value = object_getIvar(object, ivars[i]);
65+
if (value) {
66+
free(ivars);
67+
return value;
68+
}
69+
}
70+
}
71+
}
72+
73+
free(ivars);
74+
objectClass = class_getSuperclass(objectClass);
75+
}
76+
77+
return nil;
78+
}
79+
80+
@end

detox/test/ios/example.xcodeproj/project.pbxproj

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,11 @@
2525
60A403A92D21EAE3004344C3 /* UIViewController+Shake.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60A403A82D21EADE004344C3 /* UIViewController+Shake.swift */; };
2626
60A403AA2D21EAE3004344C3 /* UIViewController+Shake.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60A403A82D21EADE004344C3 /* UIViewController+Shake.swift */; };
2727
60FDFD722D2E9EC400804B82 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 60FDFD6E2D2E9EC400804B82 /* AppDelegate.m */; };
28-
60FDFD742D2E9EC400804B82 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 60FDFD6E2D2E9EC400804B82 /* AppDelegate.m */; };
2928
60FDFD762D2E9ED500804B82 /* AppDelegate+OverlayView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60FDFD752D2E9ED500804B82 /* AppDelegate+OverlayView.swift */; };
3029
60FDFD772D2E9ED500804B82 /* AppDelegate+OverlayView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60FDFD752D2E9ED500804B82 /* AppDelegate+OverlayView.swift */; };
3130
60FDFD932D2EAF5300804B82 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 60FDFD922D2EAF5300804B82 /* main.m */; };
3231
60FDFD942D2EAF5300804B82 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 60FDFD922D2EAF5300804B82 /* main.m */; };
32+
7D2B472D2E8958A0002EA9EF /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 60FDFD6E2D2E9EC400804B82 /* AppDelegate.m */; };
3333
81AB9BB82411601600AC10FF /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */; };
3434
90CCD0BA1322566EB1285DC2 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB81A68108700A75B9A /* PrivacyInfo.xcprivacy */; };
3535
96E5C284421F3B8DEE84764F /* libPods-example-ci.a in Frameworks */ = {isa = PBXBuildFile; fileRef = AB365D90AFEA3B194709EC80 /* libPods-example-ci.a */; };
@@ -511,10 +511,10 @@
511511
files = (
512512
60FDFD942D2EAF5300804B82 /* main.m in Sources */,
513513
60A403AA2D21EAE3004344C3 /* UIViewController+Shake.swift in Sources */,
514+
7D2B472D2E8958A0002EA9EF /* AppDelegate.m in Sources */,
514515
6051227F2D356C5900781AE2 /* ShakeEventEmitter.m in Sources */,
515516
608868AB2D1AA19B0070D199 /* NativeScreenManager.swift in Sources */,
516517
608868D32D1D696E0070D199 /* OverlayMessageView.swift in Sources */,
517-
60FDFD742D2E9EC400804B82 /* AppDelegate.m in Sources */,
518518
60FDFD762D2E9ED500804B82 /* AppDelegate+OverlayView.swift in Sources */,
519519
6051227C2D356C3B00781AE2 /* NativeModule.m in Sources */,
520520
608868B22D1AA1FB0070D199 /* CustomKeyboardDelegate.swift in Sources */,

0 commit comments

Comments
 (0)