diff --git a/React/Modules/RCTUIManager.m b/React/Modules/RCTUIManager.m index a22437a10..99a539232 100644 --- a/React/Modules/RCTUIManager.m +++ b/React/Modules/RCTUIManager.m @@ -66,6 +66,9 @@ NSString *const RCTUIManagerWillUpdateViewsDueToContentSizeMultiplierChangeNotif NSMutableDictionary *_shadowViewRegistry; // RCT thread only NSMutableDictionary *_viewRegistry; // Main thread only + NSMapTable *> *_shadowViewsWithUpdatedProps; // UIManager queue only. + NSHashTable *_shadowViewsWithUpdatedChildren; // UIManager queue only. + // Keyed by viewName NSDictionary *_componentDataByName; } @@ -138,6 +141,9 @@ RCT_EXPORT_MODULE() _shadowViewRegistry = [NSMutableDictionary new]; _viewRegistry = [NSMutableDictionary new]; + _shadowViewsWithUpdatedProps = [NSMapTable weakToStrongObjectsMapTable]; + _shadowViewsWithUpdatedChildren = [NSHashTable weakObjectsHashTable]; + // Internal resources _pendingUIBlocks = [NSMutableArray new]; _rootViewTags = [NSMutableSet new]; @@ -839,6 +845,8 @@ RCT_EXPORT_METHOD(setChildren:(nonnull NSNumber *)containerTag RCTSetChildren(containerTag, reactTags, (NSDictionary> *)viewRegistry); }]; + + [self _shadowViewDidReceiveUpdatedChildren:_shadowViewRegistry[containerTag]]; } static void RCTSetChildren(NSNumber *containerTag, @@ -879,6 +887,8 @@ RCT_EXPORT_METHOD(manageChildren:(nonnull NSNumber *)containerTag removeAtIndices:removeAtIndices registry:(NSMutableDictionary> *)viewRegistry]; }]; + + [self _shadowViewDidReceiveUpdatedChildren:_shadowViewRegistry[containerTag]]; } - (void)_manageChildren:(NSNumber *)containerTag @@ -972,6 +982,13 @@ RCT_EXPORT_METHOD(createView:(nonnull NSNumber *)reactTag #endif } }); + + [self addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary *viewRegistry) { + UIView *view = viewRegistry[reactTag]; + [componentData setProps:props forView:view]; + }]; + + [self _shadowView:shadowView didReceiveUpdatedProps:[props allKeys]]; } RCT_EXPORT_METHOD(updateView:(nonnull NSNumber *)reactTag @@ -986,6 +1003,8 @@ RCT_EXPORT_METHOD(updateView:(nonnull NSNumber *)reactTag UIView *view = viewRegistry[reactTag]; [componentData setProps:props forView:view]; }]; + + [self _shadowView:shadowView didReceiveUpdatedProps:[props allKeys]]; } - (void)synchronouslyUpdateViewOnUIThread:(NSNumber *)reactTag @@ -1067,6 +1086,9 @@ RCT_EXPORT_METHOD(dispatchViewManagerCommand:(nonnull NSNumber *)reactTag [self addUIBlock:uiBlock]; } + [self _dispatchPropsDidChangeEvents]; + [self _dispatchChildrenDidChangeEvents]; + [_observerCoordinator uiManagerWillPerformLayout:self]; // Perform layout @@ -1135,6 +1157,75 @@ RCT_EXPORT_METHOD(dispatchViewManagerCommand:(nonnull NSNumber *)reactTag } } +- (void)_shadowView:(RCTShadowView *)shadowView didReceiveUpdatedProps:(NSArray *)props +{ + // We collect a set with changed `shadowViews` and its changed props, + // so we have to maintain this collection properly. + NSArray *previousProps; + if ((previousProps = [_shadowViewsWithUpdatedProps objectForKey:shadowView])) { + // Merging already registred changed props and new ones. + NSMutableSet *set = [NSMutableSet setWithArray:previousProps]; + [set addObjectsFromArray:props]; + props = [set allObjects]; + } + + [_shadowViewsWithUpdatedProps setObject:props forKey:shadowView]; +} + +- (void)_shadowViewDidReceiveUpdatedChildren:(RCTShadowView *)shadowView +{ + [_shadowViewsWithUpdatedChildren addObject:shadowView]; +} + +- (void)_dispatchChildrenDidChangeEvents +{ + if (_shadowViewsWithUpdatedChildren.count == 0) { + return; + } + + NSHashTable *shadowViews = _shadowViewsWithUpdatedChildren; + _shadowViewsWithUpdatedChildren = [NSHashTable weakObjectsHashTable]; + + NSMutableArray *tags = [NSMutableArray arrayWithCapacity:shadowViews.count]; + + for (RCTShadowView *shadowView in shadowViews) { + [shadowView didUpdateReactSubviews]; + [tags addObject:shadowView.reactTag]; + } + + [self addUIBlock:^(RCTUIManager *uiManager, NSDictionary *viewRegistry) { + for (NSNumber *tag in tags) { + UIView *view = viewRegistry[tag]; + [view didUpdateReactSubviews]; + } + }]; +} + +- (void)_dispatchPropsDidChangeEvents +{ + if (_shadowViewsWithUpdatedProps.count == 0) { + return; + } + + NSMapTable *> *shadowViews = _shadowViewsWithUpdatedProps; + _shadowViewsWithUpdatedProps = [NSMapTable weakToStrongObjectsMapTable]; + + NSMapTable *> *tags = [NSMapTable strongToStrongObjectsMapTable]; + + for (RCTShadowView *shadowView in shadowViews) { + NSArray *props = [shadowViews objectForKey:shadowView]; + [shadowView didSetProps:props]; + [tags setObject:props forKey:shadowView.reactTag]; + } + + [self addUIBlock:^(RCTUIManager *uiManager, NSDictionary *viewRegistry) { + for (NSNumber *tag in tags) { + UIView *view = viewRegistry[tag]; + [view didSetProps:[tags objectForKey:tag]]; + } + }]; +} + RCT_EXPORT_METHOD(measure:(nonnull NSNumber *)reactTag callback:(RCTResponseSenderBlock)callback) { diff --git a/React/Views/RCTComponent.h b/React/Views/RCTComponent.h index 1d543ae69..499bd9e0e 100644 --- a/React/Views/RCTComponent.h +++ b/React/Views/RCTComponent.h @@ -36,8 +36,6 @@ typedef void (^RCTBubblingEventBlock)(NSDictionary *body); // View/ShadowView is a root view - (BOOL)isReactRootView; -@optional - /** * Called each time props have been set. * Not all props have to be set - React can set only changed ones. diff --git a/React/Views/RCTComponentData.m b/React/Views/RCTComponentData.m index 436091a13..e094b1b7b 100644 --- a/React/Views/RCTComponentData.m +++ b/React/Views/RCTComponentData.m @@ -355,10 +355,6 @@ static RCTPropBlock createNSInvocationSetter(NSMethodSignature *typeSignature, S [props enumerateKeysAndObjectsUsingBlock:^(NSString *key, id json, __unused BOOL *stop) { [self propBlockForKey:key isShadowView:NO](view, json); }]; - - if ([view respondsToSelector:@selector(didSetProps:)]) { - [view didSetProps:[props allKeys]]; - } } - (void)setProps:(NSDictionary *)props forShadowView:(RCTShadowView *)shadowView @@ -370,10 +366,6 @@ static RCTPropBlock createNSInvocationSetter(NSMethodSignature *typeSignature, S [props enumerateKeysAndObjectsUsingBlock:^(NSString *key, id json, __unused BOOL *stop) { [self propBlockForKey:key isShadowView:YES](shadowView, json); }]; - - if ([shadowView respondsToSelector:@selector(didSetProps:)]) { - [shadowView didSetProps:[props allKeys]]; - } } - (NSDictionary *)viewConfig diff --git a/React/Views/RCTShadowView.m b/React/Views/RCTShadowView.m index 404dddd3a..23c619ff7 100644 --- a/React/Views/RCTShadowView.m +++ b/React/Views/RCTShadowView.m @@ -41,7 +41,6 @@ typedef NS_ENUM(unsigned int, meta_prop_t) { BOOL _recomputePadding; BOOL _recomputeMargin; BOOL _recomputeBorder; - BOOL _didUpdateSubviews; YGValue _paddingMetaProps[META_PROP_COUNT]; YGValue _marginMetaProps[META_PROP_COUNT]; YGValue _borderMetaProps[META_PROP_COUNT]; @@ -263,19 +262,6 @@ static void RCTProcessMetaPropsBorder(const YGValue metaProps[META_PROP_COUNT], - (NSDictionary *)processUpdatedProperties:(NSMutableSet *)applierBlocks parentProperties:(NSDictionary *)parentProperties { - // TODO: we always refresh all propagated properties when propagation is - // dirtied, but really we should track which properties have changed and - // only update those. - - if (_didUpdateSubviews) { - _didUpdateSubviews = NO; - [self didUpdateReactSubviews]; - [applierBlocks addObject:^(NSDictionary *viewRegistry) { - UIView *view = viewRegistry[self->_reactTag]; - [view didUpdateReactSubviews]; - }]; - } - return parentProperties; } @@ -435,7 +421,6 @@ static void RCTProcessMetaPropsBorder(const YGValue metaProps[META_PROP_COUNT], YGNodeInsertChild(_yogaNode, subview.yogaNode, (uint32_t)atIndex); } subview->_superview = self; - _didUpdateSubviews = YES; [self dirtyText]; [self dirtyPropagation]; } @@ -444,7 +429,6 @@ static void RCTProcessMetaPropsBorder(const YGValue metaProps[META_PROP_COUNT], { [subview dirtyText]; [subview dirtyPropagation]; - _didUpdateSubviews = YES; subview->_superview = nil; [_reactSubviews removeObject:subview]; if (![self isYogaLeafNode]) { diff --git a/React/Views/UIView+React.h b/React/Views/UIView+React.h index 345570a03..068f41138 100644 --- a/React/Views/UIView+React.h +++ b/React/Views/UIView+React.h @@ -60,6 +60,12 @@ */ - (void)didUpdateReactSubviews; +/** + * Called each time props have been set. + * The default implementation does nothing. + */ +- (void)didSetProps:(NSArray *)changedProps; + /** * Used by the UIIManager to set the view frame. * May be overriden to disable animation, etc. diff --git a/React/Views/UIView+React.m b/React/Views/UIView+React.m index 038706625..306af7ba6 100644 --- a/React/Views/UIView+React.m +++ b/React/Views/UIView+React.m @@ -172,6 +172,11 @@ } } +- (void)didSetProps:(__unused NSArray *)changedProps +{ + // The default implementation does nothing. +} + - (void)reactSetFrame:(CGRect)frame { // These frames are in terms of anchorPoint = topLeft, but internally the