Skip to content

Commit

Permalink
Make the addUIBlock's Dictionary works correctly (#43060)
Browse files Browse the repository at this point in the history
Summary:
Pull Request resolved: #43060

When inestigating the reason why [`react-native-view-shot`]() ws not working, we realized that there are many libraries that follows this pattern:

```objc
[self.bridge.uiManager addUIBlock:^(RCTUIManager *uiManager, NSDictionary<NSNumber *, UIView *> *viewRegistry) {
    if (UIView *view = viewRegistry[reactTag]) {
      //do something with the View
    }
  }];
```
The problem is that, with the New Architecture, that view registry is usually empty, because the components are registered and tracked in another place.
This make many libraries stop working when used with the New Architecture.

This change introduces a class that behaves like a dictionary but that forward the calls to retrieve the view to the right place, in order to get the view that is needed.

Noticably, this approach allow us also to remove some shenanigans we were applying to make sure that the interop layer could access the views wrapped in it, so the current solution is more general and should work in multiple situations.

## Changelog
[iOS][Fixed] - Make sure that `addUIBlock` provides a Dictionary-look-alike object that returns the right views when queried.

Reviewed By: sammy-SC

Differential Revision: D53826203

fbshipit-source-id: 08d359676d69777b88fa9b18dc141187ac42dbce
  • Loading branch information
cipolleschi authored and facebook-github-bot committed Feb 16, 2024
1 parent 906985b commit f61f87c
Show file tree
Hide file tree
Showing 8 changed files with 120 additions and 37 deletions.
11 changes: 8 additions & 3 deletions packages/react-native/React/Base/RCTBridgeProxy.mm
Original file line number Diff line number Diff line change
Expand Up @@ -414,8 +414,9 @@ - (UIView *)viewForReactTag:(NSNumber *)reactTag
{
[self logWarning:@"Please migrate to RCTViewRegistry: @synthesize viewRegistry_DEPRECATED = _viewRegistry_DEPRECATED."
cmd:_cmd];
return [_viewRegistry viewForReactTag:reactTag] ? [_viewRegistry viewForReactTag:reactTag]
: [_legacyViewRegistry objectForKey:reactTag];
UIView *view = [_viewRegistry viewForReactTag:reactTag] ? [_viewRegistry viewForReactTag:reactTag]
: [_legacyViewRegistry objectForKey:reactTag];
return [RCTUIManager paperViewOrCurrentView:view];
}

- (void)addUIBlock:(RCTViewManagerUIBlock)block
Expand All @@ -428,7 +429,11 @@ - (void)addUIBlock:(RCTViewManagerUIBlock)block
RCTExecuteOnMainQueue(^{
__typeof(self) strongSelf = weakSelf;
if (strongSelf) {
block((RCTUIManager *)strongSelf, strongSelf->_legacyViewRegistry);
RCTUIManager *proxiedManager = (RCTUIManager *)strongSelf;
RCTComposedViewRegistry *composedViewRegistry =
[[RCTComposedViewRegistry alloc] initWithUIManager:proxiedManager
andRegistry:strongSelf->_legacyViewRegistry];
block(proxiedManager, composedViewRegistry);
}
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,12 @@ NS_ASSUME_NONNULL_BEGIN
+ (void)supportLegacyViewManagerWithName:(NSString *)componentName;
+ (void)supportLegacyViewManagersWithPrefix:(NSString *)prefix;

/**
* This method is required for addUIBlock and to let the infra bypass the interop layer
* when providing views from the RCTUIManager. The interop layer should be transparent to the users.
*/
- (UIView *)paperView;

@end

NS_ASSUME_NONNULL_END
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,11 @@ - (void)_setPropsWithUpdateMask:(RNComponentViewUpdateMask)updateMask
}
}

- (UIView *)paperView
{
return _adapter.paperView;
}

#pragma mark - Native Commands

- (void)handleCommand:(const NSString *)commandName args:(const NSArray *)args
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ - (instancetype)initWithCoordinator:(RCTLegacyViewManagerInteropCoordinator *)co

- (void)dealloc
{
[_coordinator removeViewFromRegistryWithTag:_tag];
[_paperView removeFromSuperview];
[_coordinator removeObserveForTag:_tag];
}
Expand All @@ -42,7 +41,6 @@ - (UIView *)paperView
weakSelf.eventInterceptor(eventName, event);
}
}];
[_coordinator addViewToRegistry:_paperView withTag:_tag];
}
return _paperView;
}
Expand Down
32 changes: 32 additions & 0 deletions packages/react-native/React/Modules/RCTUIManager.h
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,14 @@ RCT_EXTERN NSString *const RCTUIManagerWillUpdateViewsDueToContentSizeMultiplier
*/
- (void)setNeedsLayout;

/**
* This method is used to extract the wrapped view
* from a view that might be the Interop Layer view wrapper.
* If the view passed as parameter is the Interop Layer wrapper, this method returns the wrapped view
* Otherwise, it returns the view itself.
*/
+ (UIView *)paperViewOrCurrentView:(UIView *)view;

/**
* Dedicated object for subscribing for UIManager events.
* See `RCTUIManagerObserver` protocol for more details.
Expand All @@ -180,6 +188,30 @@ RCT_EXTERN NSString *const RCTUIManagerWillUpdateViewsDueToContentSizeMultiplier

@end

/**
* This is a composed ViewRegistry which implement the same behavior of a Dictionary.
* We need this because, when libraries use `addUIBlock` they receives both the UIManager and a Dictionary which maps
* reactTags to Views. The problem is that in the New Architecture that dictionary is always empty and many libraries
* broke because they want to access the dictionary. Instead, they should use the `uiManager viewForReactTag` method to
* retrieve the views they need.
*
* The `RCTComposedViewRegistry` follows the composite pattern and receives as inputs the `RCTUIManager`and the
* dictionary that was passed to the libraries. It extends `NSDictionary` to make sure that it has the same interface of
* the parameter that is passed. This class fixes the problem because we override the`objectForKeyedSubscript:` method
* which is used to access the dictionary. That method is now implemented looking in the dictionary registry first and
* then using the`viewForReactTag` method.
*/
@interface RCTComposedViewRegistry : NSMutableDictionary

- (instancetype)initWithUIManager:(RCTUIManager *)uiManager andRegistry:(NSDictionary<NSNumber *, UIView *> *)registry;

@end

// This protocol is needed to silence the "unknown selector" warning
@protocol RCTRendererInteropLayerAdapting
- (UIView *)paperView;
@end

RCT_EXTERN NSMutableDictionary<NSString *, id> *RCTModuleConstantsForDestructuredComponent(
NSMutableDictionary<NSString *, NSDictionary *> *directEvents,
NSMutableDictionary<NSString *, NSDictionary *> *bubblingEvents,
Expand Down
71 changes: 69 additions & 2 deletions packages/react-native/React/Modules/RCTUIManager.m
Original file line number Diff line number Diff line change
Expand Up @@ -374,7 +374,7 @@ - (UIView *)viewForReactTag:(NSNumber *)reactTag
if (!view) {
view = _viewRegistry[reactTag];
}
return view;
return [RCTUIManager paperViewOrCurrentView:view];
}

- (RCTShadowView *)shadowViewForReactTag:(NSNumber *)reactTag
Expand Down Expand Up @@ -1157,7 +1157,9 @@ - (void)flushUIBlocksWithCompletion:(void (^)(void))completion

@try {
for (RCTViewManagerUIBlock block in previousPendingUIBlocks) {
block(strongSelf, strongSelf->_viewRegistry);
RCTComposedViewRegistry *composedViewRegistry =
[[RCTComposedViewRegistry alloc] initWithUIManager:strongSelf andRegistry:strongSelf->_viewRegistry];
block(strongSelf, composedViewRegistry);
}
} @catch (NSException *exception) {
RCTLogError(@"Exception thrown while executing UI block: %@", exception);
Expand Down Expand Up @@ -1639,6 +1641,19 @@ + (UIView *)JSResponder
return _jsResponder;
}

+ (UIView *)paperViewOrCurrentView:(UIView *)view
{
if ([view respondsToSelector:@selector(paperView)]) {
return [view performSelector:@selector(paperView)];
}
return view;
}

- (void)removeViewFromRegistry:(NSNumber *)reactTag
{
[_viewRegistry removeObjectForKey:reactTag];
}

@end

@implementation RCTBridge (RCTUIManager)
Expand All @@ -1649,3 +1664,55 @@ - (RCTUIManager *)uiManager
}

@end

@implementation RCTComposedViewRegistry {
__weak RCTUIManager *_uiManager;
NSDictionary<NSNumber *, UIView *> *_registry;
}

- (instancetype)initWithUIManager:(RCTUIManager *)uiManager andRegistry:(NSDictionary<NSNumber *, UIView *> *)registry
{
self = [super init];
if (self) {
self->_uiManager = uiManager;
self->_registry = registry;
}
return self;
}

- (id)objectForKey:(id)key
{
if (![key isKindOfClass:[NSNumber class]]) {
return [super objectForKeyedSubscript:key];
}

NSNumber *index = (NSNumber *)key;
UIView *view = [_uiManager viewForReactTag:index];
if (view) {
return [RCTUIManager paperViewOrCurrentView:view];
}
view = _registry[index];
if (view) {
return [RCTUIManager paperViewOrCurrentView:view];
}
return [super objectForKeyedSubscript:key];
}

- (void)removeObjectForKey:(id)key
{
if (![key isKindOfClass:[NSNumber class]]) {
return [super removeObjectForKey:key];
}
NSNumber *tag = (NSNumber *)key;

if (_registry[key]) {
NSMutableDictionary *mutableRegistry = (NSMutableDictionary *)_registry;
[mutableRegistry removeObjectForKey:tag];
} else if ([_uiManager viewForReactTag:tag]) {
[_uiManager removeViewFromRegistry:tag];
} else {
[super removeObjectForKey:key];
}
}

@end
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,6 @@ typedef void (^InterceptorBlock)(std::string eventName, folly::dynamic event);
args:(NSArray *)args
reactTag:(NSInteger)tag
paperView:(UIView *)paperView;

- (void)removeViewFromRegistryWithTag:(NSInteger)tag;

- (void)addViewToRegistry:(UIView *)view withTag:(NSInteger)tag;

@end

NS_ASSUME_NONNULL_END
Original file line number Diff line number Diff line change
Expand Up @@ -147,31 +147,6 @@ - (void)handleCommand:(NSString *)commandName
}
}

- (void)addViewToRegistry:(UIView *)view withTag:(NSInteger)tag
{
[self _addUIBlock:^(RCTUIManager *uiManager, NSDictionary<NSNumber *, UIView *> *viewRegistry) {
if ([viewRegistry objectForKey:@(tag)] != NULL) {
return;
}
NSMutableDictionary<NSNumber *, UIView *> *mutableViewRegistry =
(NSMutableDictionary<NSNumber *, UIView *> *)viewRegistry;
[mutableViewRegistry setObject:view forKey:@(tag)];
}];
}

- (void)removeViewFromRegistryWithTag:(NSInteger)tag
{
[self _addUIBlock:^(RCTUIManager *uiManager, NSDictionary<NSNumber *, UIView *> *viewRegistry) {
if ([viewRegistry objectForKey:@(tag)] == NULL) {
return;
}

NSMutableDictionary<NSNumber *, UIView *> *mutableViewRegistry =
(NSMutableDictionary<NSNumber *, UIView *> *)viewRegistry;
[mutableViewRegistry removeObjectForKey:@(tag)];
}];
}

#pragma mark - Private
- (void)_handleCommandsOnBridge:(id<RCTBridgeMethod>)method withArgs:(NSArray *)newArgs
{
Expand Down

0 comments on commit f61f87c

Please sign in to comment.