Skip to content

Commit 8b37b3f

Browse files
authored
fix(ios): wait a bit before declaring a view unresolvable (#3962)
1 parent 81844b9 commit 8b37b3f

File tree

5 files changed

+194
-22
lines changed

5 files changed

+194
-22
lines changed

ios/RNMBX/RNMBXMapViewModule.h

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#import <Foundation/Foundation.h>
22
#import <UIKit/UIKit.h>
3+
#import "RNMBXViewResolver.h"
34

45
#ifdef RCT_NEW_ARCH_ENABLED
56
#import "rnmapbox_maps_specs.h"
@@ -9,9 +10,9 @@
910

1011
@interface RNMBXMapViewModule : NSObject
1112
#ifdef RCT_NEW_ARCH_ENABLED
12-
<NativeMapViewModuleSpec>
13+
<NativeMapViewModuleSpec, RNMBXViewResolverDelegate>
1314
#else
14-
<RCTBridgeModule>
15+
<RCTBridgeModule, RNMBXViewResolverDelegate>
1516
#endif
1617

1718
@end

ios/RNMBX/RNMBXMapViewModule.mm

Lines changed: 10 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
#endif // RCT_NEW_ARCH_ENABLED
99

1010
#import "rnmapbox_maps-Swift.pre.h"
11+
#import "RNMBXViewResolver.h"
1112

1213
@implementation RNMBXMapViewModule
1314

@@ -27,26 +28,16 @@ - (dispatch_queue_t)methodQueue
2728

2829
- (void)withMapView:(nonnull NSNumber*)viewRef block:(void (^)(RNMBXMapView *))block reject:(RCTPromiseRejectBlock)reject methodName:(NSString *)methodName
2930
{
30-
// void (^upperBlock)(void) = ^{
31-
#ifdef RCT_NEW_ARCH_ENABLED
32-
[self.viewRegistry_DEPRECATED addUIBlock:^(RCTViewRegistry *viewRegistry) {
33-
RNMBXMapViewComponentView *componentView = [self.viewRegistry_DEPRECATED viewForReactTag:viewRef];
34-
RNMBXMapView *view = componentView.contentView;
35-
36-
#else
37-
[self.bridge.uiManager
38-
addUIBlock:^(RCTUIManager *uiManager, NSDictionary<NSNumber *, UIView *> *viewRegistry) {
39-
RNMBXMapView *view = [uiManager viewForReactTag:viewRef];
40-
#endif // RCT_NEW_ARCH_ENABLED
41-
if (view != nil) {
42-
block(view);
43-
} else {
44-
reject(methodName, [NSString stringWithFormat:@"Unknown reactTag: %@", viewRef], nil);
45-
}
46-
}];
31+
[RNMBXViewResolver withViewRef:viewRef
32+
delegate:self
33+
expectedClass:[RNMBXMapView class]
34+
block:^(UIView *view) {
35+
block((RNMBXMapView *)view);
36+
}
37+
reject:reject
38+
methodName:methodName];
4739
}
4840

49-
5041
RCT_EXPORT_METHOD(takeSnap:(nonnull NSNumber*)viewRef writeToDisk:(BOOL)writeToDisk resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject)
5142
{
5243
[self withMapView:viewRef block:^(RNMBXMapView *view) {
@@ -72,7 +63,7 @@ - (void)withMapView:(nonnull NSNumber*)viewRef block:(void (^)(RNMBXMapView *))b
7263
[self withMapView:viewRef block:^(RNMBXMapView *view) {
7364
NSNumber* a = [atPoint objectAtIndex:0];
7465
NSNumber* b = [atPoint objectAtIndex:1];
75-
66+
7667
[RNMBXMapViewManager getCoordinateFromView:view atPoint:CGPointMake(a.floatValue, b.floatValue) resolver:resolve rejecter:reject];
7768
} reject:reject methodName:@"getCoordinateFromView"];
7869
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
//
2+
// RNMBXViewResolver.h
3+
//
4+
// A utility class for resolving React Native views across both old and new architecture
5+
// This eliminates code duplication found in multiple RNMBX modules.
6+
//
7+
8+
#import <Foundation/Foundation.h>
9+
#import <React/RCTBridge.h>
10+
#import <React/RCTUIManager.h>
11+
#import <React/RCTUIManagerUtils.h>
12+
13+
NS_ASSUME_NONNULL_BEGIN
14+
15+
@protocol RNMBXViewResolverDelegate <NSObject>
16+
17+
#ifdef RCT_NEW_ARCH_ENABLED
18+
@property (nonatomic, weak, nullable) RCTViewRegistry *viewRegistry_DEPRECATED;
19+
#endif
20+
@property (nonatomic, weak, nullable) RCTBridge *bridge;
21+
22+
@end
23+
24+
@interface RNMBXViewResolver : NSObject
25+
26+
/**
27+
* Universal view resolution method with optional type checking
28+
*
29+
* @param viewRef The React tag number to resolve
30+
* @param delegate The module that conforms to RNMBXViewResolverDelegate
31+
* @param expectedClass The expected view class for type checking (pass nil to skip type checking)
32+
* @param block The block to execute with the resolved view
33+
* @param reject The rejection block for promise-based methods
34+
* @param methodName The name of the calling method for error reporting
35+
*/
36+
+ (void)withViewRef:(NSNumber *)viewRef
37+
delegate:(id<RNMBXViewResolverDelegate>)delegate
38+
expectedClass:(nullable Class)expectedClass
39+
block:(void (^)(UIView *view))block
40+
reject:(RCTPromiseRejectBlock)reject
41+
methodName:(NSString *)methodName;
42+
43+
@end
44+
45+
NS_ASSUME_NONNULL_END
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
//
2+
// RNMBXViewResolver.mm
3+
//
4+
// Implementation of cross-architecture view resolver utility
5+
//
6+
7+
#import "RNMBXViewResolver.h"
8+
9+
@implementation RNMBXViewResolver
10+
11+
+ (void)withViewRef:(NSNumber *)viewRef
12+
delegate:(id<RNMBXViewResolverDelegate>)delegate
13+
expectedClass:(nullable Class)expectedClass
14+
block:(void (^)(UIView *view))block
15+
reject:(RCTPromiseRejectBlock)reject
16+
methodName:(NSString *)methodName {
17+
18+
if (!delegate) {
19+
reject(@"no_delegate", @"Delegate is required", nil);
20+
return;
21+
}
22+
23+
if (!viewRef) {
24+
reject(@"no_view_ref", @"View reference is required", nil);
25+
return;
26+
}
27+
28+
if (!block) {
29+
reject(@"no_block", @"Completion block is required", nil);
30+
return;
31+
}
32+
33+
#ifdef RCT_NEW_ARCH_ENABLED
34+
[delegate.viewRegistry_DEPRECATED addUIBlock:^(RCTViewRegistry *viewRegistry) {
35+
[self resolveViewWithPolling:viewRef
36+
delegate:delegate
37+
expectedClass:expectedClass
38+
block:block
39+
reject:reject
40+
methodName:methodName
41+
attemptCount:0
42+
startTime:nil];
43+
}];
44+
#else
45+
[delegate.bridge.uiManager addUIBlock:^(RCTUIManager *uiManager, NSDictionary<NSNumber *, UIView *> *viewRegistry) {
46+
[self resolveViewWithPolling:viewRef
47+
delegate:delegate
48+
expectedClass:expectedClass
49+
block:block
50+
reject:reject
51+
methodName:methodName
52+
attemptCount:0
53+
startTime:nil];
54+
}];
55+
#endif
56+
}
57+
58+
+ (void)resolveViewWithPolling:(NSNumber *)viewRef
59+
delegate:(id<RNMBXViewResolverDelegate>)delegate
60+
expectedClass:(nullable Class)expectedClass
61+
block:(void (^)(UIView *view))block
62+
reject:(RCTPromiseRejectBlock)reject
63+
methodName:(NSString *)methodName
64+
attemptCount:(NSInteger)attemptCount
65+
startTime:(nullable NSDate *)startTime {
66+
67+
68+
UIView *view = [self resolveView:viewRef delegate:delegate];
69+
70+
if (view) {
71+
if (expectedClass && ![view isKindOfClass:expectedClass]) {
72+
reject(@"wrong_view_type",
73+
[NSString stringWithFormat:@"View with tag %@ is not of expected type %@ in %@. Found: %@",
74+
viewRef, NSStringFromClass(expectedClass), methodName, NSStringFromClass([view class])],
75+
nil);
76+
return;
77+
}
78+
block(view);
79+
return;
80+
}
81+
82+
if (!startTime) {
83+
startTime = [NSDate date];
84+
}
85+
86+
NSTimeInterval elapsed = [[NSDate date] timeIntervalSinceDate:startTime];
87+
if (elapsed >= 0.2) { // 200ms timeout
88+
NSString *errorMsg = [NSString stringWithFormat:@"Could not find view with tag %@ in %@ after %d attempts over %.1fms",
89+
viewRef, methodName, (int)attemptCount + 1, elapsed * 1000];
90+
NSLog(@"%@", errorMsg);
91+
reject(@"view_not_found", errorMsg, nil);
92+
return;
93+
}
94+
95+
int64_t delay = (attemptCount == 0) ? (NSEC_PER_MSEC/5) : 10 * NSEC_PER_MSEC;
96+
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, delay), dispatch_get_main_queue(), ^{
97+
[self resolveViewWithPolling:viewRef
98+
delegate:delegate
99+
expectedClass:expectedClass
100+
block:block
101+
reject:reject
102+
methodName:methodName
103+
attemptCount:attemptCount + 1
104+
startTime:startTime];
105+
});
106+
}
107+
108+
#pragma mark - Private Methods
109+
110+
+ (UIView *)resolveView:(NSNumber *)viewRef delegate:(id<RNMBXViewResolverDelegate>)delegate {
111+
#ifdef RCT_NEW_ARCH_ENABLED
112+
if (delegate.viewRegistry_DEPRECATED) {
113+
UIView *componentView = [delegate.viewRegistry_DEPRECATED viewForReactTag:viewRef];
114+
if (componentView) {
115+
if ([componentView respondsToSelector:@selector(contentView)]) {
116+
return [componentView performSelector:@selector(contentView)];
117+
}
118+
return componentView;
119+
}
120+
}
121+
122+
if (delegate.bridge) {
123+
return [delegate.bridge.uiManager viewForReactTag:viewRef];
124+
}
125+
#else
126+
if (delegate.bridge) {
127+
return [delegate.bridge.uiManager viewForReactTag:viewRef];
128+
}
129+
#endif
130+
131+
return nil;
132+
}
133+
134+
@end

rnmapbox-maps.podspec

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -259,7 +259,8 @@ Pod::Spec.new do |s|
259259
case $RNMapboxMapsImpl
260260
when 'mapbox'
261261
sp.source_files = "ios/RNMBX/**/*.{h,m,mm,swift}"
262-
sp.private_header_files = 'ios/RNMBX/RNMBXFabricHelpers.h', 'ios/RNMBX/RNMBXFabricPropConvert.h', 'ios/RNMBX/rnmapbox_maps-Swift.pre.h', 'ios/RNMBX/Utils/RNMBXFollyConvert.h'
262+
sp.private_header_files = 'ios/RNMBX/RNMBXFabricHelpers.h', 'ios/RNMBX/RNMBXFabricPropConvert.h', 'ios/RNMBX/rnmapbox_maps-Swift.pre.h', 'ios/RNMBX/Utils/RNMBXFollyConvert.h', 'ios/RNMBX/Utils/RNMBXViewResolver.h'
263+
263264
if new_arch_enabled
264265
sp.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES' }
265266
install_modules_dependencies(sp)

0 commit comments

Comments
 (0)