Refactored subview management

Reviewed By: javache

Differential Revision: D3392214

fbshipit-source-id: 6f16841df5cf866dda5ac27dd244e266ec85a86e
This commit is contained in:
Nick Lockwood 2016-06-06 10:27:36 -07:00 committed by Facebook Github Bot 4
parent 4e3a8343b3
commit 44c9cf3a91
16 changed files with 150 additions and 236 deletions

View File

@ -77,8 +77,8 @@
@"Expect to have 5 react subviews after calling manage children \
with 5 tags to add, instead have %lu", (unsigned long)[[containerView reactSubviews] count]);
for (UIView *view in addedViews) {
XCTAssertTrue([view superview] == containerView,
@"Expected to have manage children successfully add children");
XCTAssertTrue([view reactSuperview] == containerView,
@"Expected to have manage children successfully add children");
[view removeFromSuperview];
}
}
@ -95,7 +95,7 @@
}
for (NSInteger i = 2; i < 20; i++) {
UIView *view = _uiManager.viewRegistry[@(i)];
[containerView addSubview:view];
[containerView insertReactSubview:view atIndex:containerView.reactSubviews.count];
}
// Remove views 1-5 from view 20
@ -112,7 +112,7 @@
with 5 tags to remove and 18 prior children, instead have %zd",
containerView.reactSubviews.count);
for (UIView *view in removedViews) {
XCTAssertTrue([view superview] == nil,
XCTAssertTrue([view reactSuperview] == nil,
@"Expected to have manage children successfully remove children");
// After removing views are unregistered - we need to reregister
_uiManager.viewRegistry[view.reactTag] = view;
@ -155,7 +155,7 @@
for (NSInteger i = 1; i < 11; i++) {
UIView *view = _uiManager.viewRegistry[@(i)];
[containerView addSubview:view];
[containerView insertReactSubview:view atIndex:containerView.reactSubviews.count];
}
[_uiManager _manageChildren:@20

View File

@ -27,7 +27,6 @@ static void collectNonTextDescendants(RCTText *view, NSMutableArray *nonTextDesc
@implementation RCTText
{
NSTextStorage *_textStorage;
NSMutableArray<UIView *> *_reactSubviews;
CAShapeLayer *_highlightLayer;
}
@ -35,7 +34,6 @@ static void collectNonTextDescendants(RCTText *view, NSMutableArray *nonTextDesc
{
if ((self = [super initWithFrame:frame])) {
_textStorage = [NSTextStorage new];
_reactSubviews = [NSMutableArray array];
self.isAccessibilityElement = YES;
self.accessibilityTraits |= UIAccessibilityTraitStaticText;
@ -68,19 +66,9 @@ static void collectNonTextDescendants(RCTText *view, NSMutableArray *nonTextDesc
self.backgroundColor = inheritedBackgroundColor;
}
- (void)insertReactSubview:(UIView *)subview atIndex:(NSInteger)atIndex
- (void)reactUpdateSubviews
{
[_reactSubviews insertObject:subview atIndex:atIndex];
}
- (void)removeReactSubview:(UIView *)subview
{
[_reactSubviews removeObject:subview];
}
- (NSArray<UIView *> *)reactSubviews
{
return _reactSubviews;
// Do nothing, as subviews are managed by `setTextStorage:` method
}
- (void)setTextStorage:(NSTextStorage *)textStorage
@ -88,6 +76,7 @@ static void collectNonTextDescendants(RCTText *view, NSMutableArray *nonTextDesc
if (_textStorage != textStorage) {
_textStorage = textStorage;
// Update subviews
NSMutableArray *nonTextDescendants = [NSMutableArray new];
collectNonTextDescendants(self, nonTextDescendants);
NSArray *subviews = self.subviews;

View File

@ -17,7 +17,6 @@
@implementation RCTTextField
{
RCTEventDispatcher *_eventDispatcher;
NSMutableArray<UIView *> *_reactSubviews;
BOOL _jsRequestingFirstResponder;
NSInteger _nativeEventCount;
BOOL _submitted;
@ -35,7 +34,6 @@
[self addTarget:self action:@selector(textFieldEndEditing) forControlEvents:UIControlEventEditingDidEnd];
[self addTarget:self action:@selector(textFieldSubmitEditing) forControlEvents:UIControlEventEditingDidEndOnExit];
[self addObserver:self forKeyPath:@"selectedTextRange" options:0 context:nil];
_reactSubviews = [NSMutableArray new];
_blurOnSubmit = YES;
}
return self;
@ -112,30 +110,6 @@ static void RCTUpdatePlaceholder(RCTTextField *self)
RCTUpdatePlaceholder(self);
}
- (NSArray<UIView *> *)reactSubviews
{
// TODO: do we support subviews of textfield in React?
// In any case, we should have a better approach than manually
// maintaining array in each view subclass like this
return _reactSubviews;
}
- (void)removeReactSubview:(UIView *)subview
{
// TODO: this is a bit broken - if the TextField inserts any of
// its own views below or between React's, the indices won't match
[_reactSubviews removeObject:subview];
[subview removeFromSuperview];
}
- (void)insertReactSubview:(UIView *)view atIndex:(NSInteger)atIndex
{
// TODO: this is a bit broken - if the TextField inserts any of
// its own views below or between React's, the indices won't match
[_reactSubviews insertObject:view atIndex:atIndex];
[super insertSubview:view atIndex:atIndex];
}
- (CGRect)caretRectForPosition:(UITextPosition *)position
{
if (_caretHidden) {

View File

@ -67,7 +67,6 @@
NSInteger _nativeEventCount;
RCTText *_richTextView;
NSAttributedString *_pendingAttributedText;
NSMutableArray<UIView *> *_subviews;
BOOL _blockTextShouldChange;
UITextRange *_previousSelectionRange;
NSUInteger _previousTextLength;
@ -97,7 +96,6 @@
_previousSelectionRange = _textView.selectedTextRange;
_subviews = [NSMutableArray new];
[self addSubview:_scrollView];
}
return self;
@ -106,19 +104,14 @@
RCT_NOT_IMPLEMENTED(- (instancetype)initWithFrame:(CGRect)frame)
RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:(NSCoder *)aDecoder)
- (NSArray<UIView *> *)reactSubviews
{
return _subviews;
}
- (void)insertReactSubview:(UIView *)subview atIndex:(NSInteger)index
{
[super insertReactSubview:subview atIndex:index];
if ([subview isKindOfClass:[RCTText class]]) {
if (_richTextView) {
RCTLogError(@"Tried to insert a second <Text> into <TextInput> - there can only be one.");
}
_richTextView = (RCTText *)subview;
[_subviews insertObject:_richTextView atIndex:index];
// If this <TextInput> is in rich text editing mode, and the child <Text> node providing rich text
// styling has a backgroundColor, then the attributedText produced by the child <Text> node will have an
@ -131,23 +124,22 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:(NSCoder *)aDecoder)
attrs[NSBackgroundColorAttributeName] = subview.backgroundColor;
_textView.typingAttributes = attrs;
}
} else {
[_subviews insertObject:subview atIndex:index];
[self insertSubview:subview atIndex:index];
}
}
- (void)removeReactSubview:(UIView *)subview
{
[super removeReactSubview:subview];
if (_richTextView == subview) {
[_subviews removeObject:_richTextView];
_richTextView = nil;
} else {
[_subviews removeObject:subview];
[subview removeFromSuperview];
}
}
- (void)reactUpdateSubviews
{
// Do nothing, as we don't allow non-text subviews
}
- (void)setMostRecentEventCount:(NSInteger)mostRecentEventCount
{
_mostRecentEventCount = mostRecentEventCount;

View File

@ -337,7 +337,7 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:(NSCoder *)aDecoder)
RCT_NOT_IMPLEMENTED(-(instancetype)initWithFrame:(CGRect)frame)
RCT_NOT_IMPLEMENTED(-(instancetype)initWithCoder:(nonnull NSCoder *)aDecoder)
- (void)insertReactSubview:(id<RCTComponent>)subview atIndex:(NSInteger)atIndex
- (void)insertReactSubview:(UIView *)subview atIndex:(NSInteger)atIndex
{
[super insertReactSubview:subview atIndex:atIndex];
RCTPerformanceLoggerEnd(RCTPLTTI);

View File

@ -893,16 +893,18 @@ static void RCTSetChildren(NSNumber *containerTag,
[container insertReactSubview:view atIndex:index++];
}
}
[container didUpdateReactSubviews];
}
RCT_EXPORT_METHOD(manageChildren:(nonnull NSNumber *)containerReactTag
RCT_EXPORT_METHOD(manageChildren:(nonnull NSNumber *)containerTag
moveFromIndices:(NSArray<NSNumber *> *)moveFromIndices
moveToIndices:(NSArray<NSNumber *> *)moveToIndices
addChildReactTags:(NSArray<NSNumber *> *)addChildReactTags
addAtIndices:(NSArray<NSNumber *> *)addAtIndices
removeAtIndices:(NSArray<NSNumber *> *)removeAtIndices)
{
[self _manageChildren:containerReactTag
[self _manageChildren:containerTag
moveFromIndices:moveFromIndices
moveToIndices:moveToIndices
addChildReactTags:addChildReactTags
@ -911,7 +913,7 @@ RCT_EXPORT_METHOD(manageChildren:(nonnull NSNumber *)containerReactTag
registry:(NSMutableDictionary<NSNumber *, id<RCTComponent>> *)_shadowViewRegistry];
[self addUIBlock:^(RCTUIManager *uiManager, NSDictionary<NSNumber *, UIView *> *viewRegistry){
[uiManager _manageChildren:containerReactTag
[uiManager _manageChildren:containerTag
moveFromIndices:moveFromIndices
moveToIndices:moveToIndices
addChildReactTags:addChildReactTags
@ -921,7 +923,7 @@ RCT_EXPORT_METHOD(manageChildren:(nonnull NSNumber *)containerReactTag
}];
}
- (void)_manageChildren:(NSNumber *)containerReactTag
- (void)_manageChildren:(NSNumber *)containerTag
moveFromIndices:(NSArray<NSNumber *> *)moveFromIndices
moveToIndices:(NSArray<NSNumber *> *)moveToIndices
addChildReactTags:(NSArray<NSNumber *> *)addChildReactTags
@ -929,7 +931,7 @@ RCT_EXPORT_METHOD(manageChildren:(nonnull NSNumber *)containerReactTag
removeAtIndices:(NSArray<NSNumber *> *)removeAtIndices
registry:(NSMutableDictionary<NSNumber *, id<RCTComponent>> *)registry
{
id<RCTComponent> container = registry[containerReactTag];
id<RCTComponent> container = registry[containerTag];
RCTAssert(moveFromIndices.count == moveToIndices.count, @"moveFromIndices had size %tu, moveToIndices had size %tu", moveFromIndices.count, moveToIndices.count);
RCTAssert(addChildReactTags.count == addAtIndices.count, @"there should be at least one React child to add");
@ -963,6 +965,8 @@ RCT_EXPORT_METHOD(manageChildren:(nonnull NSNumber *)containerReactTag
[container insertReactSubview:destinationsToChildrenToAdd[reactIndex]
atIndex:reactIndex.integerValue];
}
[container didUpdateReactSubviews];
}
RCT_EXPORT_METHOD(createView:(nonnull NSNumber *)reactTag

View File

@ -43,6 +43,11 @@ typedef void (^RCTBubblingEventBlock)(NSDictionary *body);
*/
- (void)didSetProps:(NSArray<NSString *> *)changedProps;
/**
* Called each time subviews have been updated
*/
- (void)didUpdateReactSubviews;
// TODO: Deprecate this
// This method is called after layout has been performed for all views known
// to the RCTViewManager. It is only called on UIViews, not shadow views.

View File

@ -23,7 +23,6 @@ const CGFloat RCTMapZoomBoundBuffer = 0.01;
{
UIView *_legalLabel;
CLLocationManager *_locationManager;
NSMutableArray<UIView *> *_reactSubviews;
}
- (instancetype)init
@ -31,7 +30,6 @@ const CGFloat RCTMapZoomBoundBuffer = 0.01;
if ((self = [super init])) {
_hasStartedRendering = NO;
_reactSubviews = [NSMutableArray new];
// Find Apple link label
for (UIView *subview in self.subviews) {
@ -51,19 +49,9 @@ const CGFloat RCTMapZoomBoundBuffer = 0.01;
[_regionChangeObserveTimer invalidate];
}
- (void)insertReactSubview:(UIView *)subview atIndex:(NSInteger)atIndex
- (void)reactUpdateSubviews
{
[_reactSubviews insertObject:subview atIndex:atIndex];
}
- (void)removeReactSubview:(UIView *)subview
{
[_reactSubviews removeObject:subview];
}
- (NSArray<UIView *> *)reactSubviews
{
return _reactSubviews;
// Do nothing, as annotation views are managed by `setAnnotations:` method
}
- (void)layoutSubviews

View File

@ -55,14 +55,10 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:coder)
}
}
- (NSArray<UIView *> *)reactSubviews
{
return _reactSubview ? @[_reactSubview] : @[];
}
- (void)insertReactSubview:(UIView *)subview atIndex:(__unused NSInteger)atIndex
- (void)insertReactSubview:(UIView *)subview atIndex:(NSInteger)atIndex
{
RCTAssert(_reactSubview == nil, @"Modal view can only have one subview");
[super insertReactSubview:subview atIndex:atIndex];
[subview addGestureRecognizer:_touchHandler];
subview.autoresizingMask = UIViewAutoresizingFlexibleHeight |
UIViewAutoresizingFlexibleWidth;
@ -74,11 +70,16 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:coder)
- (void)removeReactSubview:(UIView *)subview
{
RCTAssert(subview == _reactSubview, @"Cannot remove view other than modal view");
[super removeReactSubview:subview];
[subview removeGestureRecognizer:_touchHandler];
[subview removeFromSuperview];
_reactSubview = nil;
}
- (void)didUpdateReactSubviews
{
// Do nothing, as subview (singular) is managed by `insertReactSubview:atIndex:`
}
- (void)dismissModalViewController
{
if (_isPresented) {

View File

@ -217,7 +217,6 @@ NSInteger kNeverProgressed = -10000;
// Previous views are only mainted in order to detect incorrect
// addition/removal of views below the `requestedTopOfStack`
@property (nonatomic, copy, readwrite) NSArray<RCTNavItem *> *previousViews;
@property (nonatomic, readwrite, strong) NSMutableArray<RCTNavItem *> *currentViews;
@property (nonatomic, readwrite, strong) RCTNavigationController *navigationController;
/**
* Display link is used to get high frequency sample rate during
@ -299,7 +298,6 @@ NSInteger kNeverProgressed = -10000;
_dummyView = [[UIView alloc] initWithFrame:CGRectZero];
_previousRequestedTopOfStack = kNeverRequested; // So that we initialize with a push.
_previousViews = @[];
_currentViews = [[NSMutableArray alloc] initWithCapacity:0];
__weak RCTNavigator *weakSelf = self;
_navigationController = [[RCTNavigationController alloc] initWithScrollCallback:^{
[weakSelf dispatchFakeScrollEvent];
@ -351,7 +349,7 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:(NSCoder *)aDecoder)
- (void)setInteractivePopGestureEnabled:(BOOL)interactivePopGestureEnabled
{
_interactivePopGestureEnabled = interactivePopGestureEnabled;
_navigationController.interactivePopGestureRecognizer.delegate = self;
_navigationController.interactivePopGestureRecognizer.enabled = interactivePopGestureEnabled;
@ -402,8 +400,8 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:(NSCoder *)aDecoder)
return;
}
NSUInteger indexOfFrom = [_currentViews indexOfObject:fromController.navItem];
NSUInteger indexOfTo = [_currentViews indexOfObject:toController.navItem];
NSUInteger indexOfFrom = [self.reactSubviews indexOfObject:fromController.navItem];
NSUInteger indexOfTo = [self.reactSubviews indexOfObject:toController.navItem];
CGFloat destination = indexOfFrom < indexOfTo ? 1.0 : -1.0;
_dummyView.frame = (CGRect){{destination, 0}, CGSizeZero};
_currentlyTransitioningFrom = indexOfFrom;
@ -433,7 +431,7 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:(NSCoder *)aDecoder)
- (void)freeLock
{
_navigationController.navigationLock = RCTNavigationLockNone;
// Unless the pop gesture has been explicitly disabled (RCTPopGestureStateDisabled),
// Set interactivePopGestureRecognizer.enabled to YES
// If the popGestureState is RCTPopGestureStateDefault the default behavior will be maintained
@ -452,12 +450,12 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:(NSCoder *)aDecoder)
_navigationController.navigationLock == RCTNavigationLockJavaScript,
@"Cannot change subviews from JS without first locking."
);
[_currentViews insertObject:view atIndex:atIndex];
[super insertReactSubview:view atIndex:atIndex];
}
- (NSArray<RCTNavItem *> *)reactSubviews
- (void)didUpdateReactSubviews
{
return _currentViews;
// Do nothing, as subviews are managed by `reactBridgeDidFinishTransaction`
}
- (void)layoutSubviews
@ -469,11 +467,11 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:(NSCoder *)aDecoder)
- (void)removeReactSubview:(RCTNavItem *)subview
{
if (_currentViews.count <= 0 || subview == _currentViews[0]) {
if (self.reactSubviews.count <= 0 || subview == self.reactSubviews[0]) {
RCTLogError(@"Attempting to remove invalid RCT subview of RCTNavigator");
return;
}
[_currentViews removeObject:subview];
[super removeReactSubview:subview];
}
- (void)handleTopOfStackChanged
@ -497,7 +495,8 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:(NSCoder *)aDecoder)
- (UIView *)reactSuperview
{
RCTAssert(!_bridge.isValid || self.superview != nil, @"put reactNavSuperviewLink back");
return self.superview ? self.superview : self.reactNavSuperviewLink;
UIView *superview = [super reactSuperview];
return superview ?: self.reactNavSuperviewLink;
}
- (void)reactBridgeDidFinishTransaction
@ -545,14 +544,14 @@ BOOL jsGettingtooSlow =
jsGettingtooSlow)) {
RCTLogError(@"JS has only made partial progress to catch up to UIKit");
}
if (currentReactCount > _currentViews.count) {
if (currentReactCount > self.reactSubviews.count) {
RCTLogError(@"Cannot adjust current top of stack beyond available views");
}
// Views before the previous React count must not have changed. Views greater than previousReactCount
// up to currentReactCount may have changed.
for (NSUInteger i = 0; i < MIN(_currentViews.count, MIN(_previousViews.count, previousReactCount)); i++) {
if (_currentViews[i] != _previousViews[i]) {
for (NSUInteger i = 0; i < MIN(self.reactSubviews.count, MIN(_previousViews.count, previousReactCount)); i++) {
if (self.reactSubviews[i] != _previousViews[i]) {
RCTLogError(@"current view should equal previous view");
}
}
@ -561,7 +560,7 @@ BOOL jsGettingtooSlow =
}
if (jsGettingAhead) {
if (reactPushOne) {
UIView *lastView = _currentViews.lastObject;
UIView *lastView = self.reactSubviews.lastObject;
RCTWrapperViewController *vc = [[RCTWrapperViewController alloc] initWithNavItem:(RCTNavItem *)lastView];
vc.navigationListener = self;
_numberOfViewControllerMovesToIgnore = 1;
@ -580,7 +579,7 @@ BOOL jsGettingtooSlow =
return;
}
_previousViews = [_currentViews copy];
_previousViews = [self.reactSubviews copy];
_previousRequestedTopOfStack = _requestedTopOfStack;
}

View File

@ -418,8 +418,9 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:(NSCoder *)aDecoder)
// Does nothing
}
- (void)insertReactSubview:(UIView *)view atIndex:(__unused NSInteger)atIndex
- (void)insertReactSubview:(UIView *)view atIndex:(NSInteger)atIndex
{
[super insertReactSubview:view atIndex:atIndex];
if ([view isKindOfClass:[RCTRefreshControl class]]) {
_scrollView.refreshControl = (RCTRefreshControl*)view;
} else {
@ -431,21 +432,18 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:(NSCoder *)aDecoder)
- (void)removeReactSubview:(UIView *)subview
{
[super removeReactSubview:subview];
if ([subview isKindOfClass:[RCTRefreshControl class]]) {
_scrollView.refreshControl = nil;
} else {
RCTAssert(_contentView == subview, @"Attempted to remove non-existent subview");
_contentView = nil;
[subview removeFromSuperview];
}
}
- (NSArray<UIView *> *)reactSubviews
- (void)didUpdateReactSubviews
{
if (_contentView && _scrollView.refreshControl) {
return @[_contentView, _scrollView.refreshControl];
}
return _contentView ? @[_contentView] : @[];
// Do nothing, as subviews are managed by `insertReactSubview:atIndex:`
}
- (BOOL)centerContent

View File

@ -297,6 +297,11 @@ static void RCTProcessMetaProps(const float metaProps[META_PROP_COUNT], float st
return RCTIsReactRootView(self.reactTag);
}
- (void)didUpdateReactSubviews
{
// Does nothing by default
}
- (void)dealloc
{
free_css_node(_cssNode);

View File

@ -26,13 +26,11 @@
{
BOOL _tabsChanged;
UITabBarController *_tabController;
NSMutableArray<RCTTabBarItem *> *_tabViews;
}
- (instancetype)initWithFrame:(CGRect)frame
{
if ((self = [super initWithFrame:frame])) {
_tabViews = [NSMutableArray new];
_tabController = [UITabBarController new];
_tabController.delegate = self;
[self addSubview:_tabController.view];
@ -53,31 +51,31 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:(NSCoder *)aDecoder)
[_tabController removeFromParentViewController];
}
- (NSArray<RCTTabBarItem *> *)reactSubviews
- (void)insertReactSubview:(RCTTabBarItem *)subview atIndex:(NSInteger)atIndex
{
return _tabViews;
}
- (void)insertReactSubview:(RCTTabBarItem *)view atIndex:(NSInteger)atIndex
{
if (![view isKindOfClass:[RCTTabBarItem class]]) {
if (![subview isKindOfClass:[RCTTabBarItem class]]) {
RCTLogError(@"subview should be of type RCTTabBarItem");
return;
}
[_tabViews insertObject:view atIndex:atIndex];
[super insertReactSubview:subview atIndex:atIndex];
_tabsChanged = YES;
}
- (void)removeReactSubview:(RCTTabBarItem *)subview
{
if (_tabViews.count == 0) {
if (self.reactSubviews.count == 0) {
RCTLogError(@"should have at least one view to remove a subview");
return;
}
[_tabViews removeObject:subview];
[super removeReactSubview:subview];
_tabsChanged = YES;
}
- (void)didUpdateReactSubviews
{
// Do nothing, as subviews are managed by `reactBridgeDidFinishTransaction`
}
- (void)layoutSubviews
{
[super layoutSubviews];
@ -106,8 +104,9 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:(NSCoder *)aDecoder)
_tabsChanged = NO;
}
[_tabViews enumerateObjectsUsingBlock:
^(RCTTabBarItem *tab, NSUInteger index, __unused BOOL *stop) {
[self.reactSubviews enumerateObjectsUsingBlock:^(UIView *view, NSUInteger index, __unused BOOL *stop) {
RCTTabBarItem *tab = (RCTTabBarItem *)view;
UIViewController *controller = _tabController.viewControllers[index];
if (_unselectedTintColor) {
[tab.barItem setTitleTextAttributes:@{NSForegroundColorAttributeName: _unselectedTintColor} forState:UIControlStateNormal];
@ -165,7 +164,7 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:(NSCoder *)aDecoder)
- (BOOL)tabBarController:(UITabBarController *)tabBarController shouldSelectViewController:(UIViewController *)viewController
{
NSUInteger index = [tabBarController.viewControllers indexOfObject:viewController];
RCTTabBarItem *tab = _tabViews[index];
RCTTabBarItem *tab = (RCTTabBarItem *)self.reactSubviews[index];
if (tab.onPress) tab.onPress(nil);
return NO;
}

View File

@ -97,7 +97,6 @@ static NSString *RCTRecursiveAccessibilityLabel(UIView *view)
@implementation RCTView
{
NSMutableArray<UIView *> *_reactSubviews;
UIColor *_backgroundColor;
}
@ -275,76 +274,31 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:unused)
- (void)react_remountAllSubviews
{
if (_reactSubviews) {
NSUInteger index = 0;
for (UIView *view in _reactSubviews) {
if (_removeClippedSubviews) {
for (UIView *view in self.reactSubviews) {
if (view.superview != self) {
if (index < self.subviews.count) {
[self insertSubview:view atIndex:index];
} else {
[self addSubview:view];
}
[self addSubview:view];
[view react_remountAllSubviews];
}
index++;
}
} else {
// If react_subviews is nil, we must already be showing all subviews
// If _removeClippedSubviews is false, we must already be showing all subviews
[super react_remountAllSubviews];
}
}
- (void)remountSubview:(UIView *)view
{
// Calculate insertion index for view
NSInteger index = 0;
for (UIView *subview in _reactSubviews) {
if (subview == view) {
[self insertSubview:view atIndex:index];
break;
}
if (subview.superview) {
// View is mounted, so bump the index
index++;
}
}
}
- (void)mountOrUnmountSubview:(UIView *)view withClipRect:(CGRect)clipRect relativeToView:(UIView *)clipView
{
if (!CGRectIsEmpty(CGRectIntersection(clipRect, view.frame))) {
// View is at least partially visible, so remount it if unmounted
if (view.superview == nil) {
[self remountSubview:view];
}
// Then test its subviews
if (CGRectContainsRect(clipRect, view.frame)) {
[view react_remountAllSubviews];
} else {
[view react_updateClippedSubviewsWithClipRect:clipRect relativeToView:clipView];
}
} else if (view.superview) {
// View is completely outside the clipRect, so unmount it
[view removeFromSuperview];
}
}
- (void)react_updateClippedSubviewsWithClipRect:(CGRect)clipRect relativeToView:(UIView *)clipView
{
// TODO (#5906496): for scrollviews (the primary use-case) we could
// optimize this by only doing a range check along the scroll axis,
// instead of comparing the whole frame
if (_reactSubviews == nil) {
if (!_removeClippedSubviews) {
// Use default behavior if unmounting is disabled
return [super react_updateClippedSubviewsWithClipRect:clipRect relativeToView:clipView];
}
if (_reactSubviews.count == 0) {
if (self.reactSubviews.count == 0) {
// Do nothing if we have no subviews
return;
}
@ -360,63 +314,46 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:unused)
clipView = self;
// Mount / unmount views
for (UIView *view in _reactSubviews) {
[self mountOrUnmountSubview:view withClipRect:clipRect relativeToView:clipView];
for (UIView *view in self.reactSubviews) {
if (!CGRectIsEmpty(CGRectIntersection(clipRect, view.frame))) {
// View is at least partially visible, so remount it if unmounted
[self addSubview:view];
// Then test its subviews
if (CGRectContainsRect(clipRect, view.frame)) {
// View is fully visible, so remount all subviews
[view react_remountAllSubviews];
} else {
// View is partially visible, so update clipped subviews
[view react_updateClippedSubviewsWithClipRect:clipRect relativeToView:clipView];
}
} else if (view.superview) {
// View is completely outside the clipRect, so unmount it
[view removeFromSuperview];
}
}
}
- (void)setRemoveClippedSubviews:(BOOL)removeClippedSubviews
{
if (removeClippedSubviews && !_reactSubviews) {
_reactSubviews = [self.subviews mutableCopy];
} else if (!removeClippedSubviews && _reactSubviews) {
if (!removeClippedSubviews && _removeClippedSubviews) {
[self react_remountAllSubviews];
_reactSubviews = nil;
}
_removeClippedSubviews = removeClippedSubviews;
}
- (BOOL)removeClippedSubviews
- (void)didUpdateReactSubviews
{
return _reactSubviews != nil;
}
- (void)insertReactSubview:(UIView *)view atIndex:(NSInteger)atIndex
{
if (_reactSubviews == nil) {
[self insertSubview:view atIndex:atIndex];
if (_removeClippedSubviews) {
[self updateClippedSubviews];
} else {
[_reactSubviews insertObject:view atIndex:atIndex];
// Find a suitable view to use for clipping
UIView *clipView = [self react_findClipView];
if (clipView) {
// If possible, don't add subviews if they are clipped
[self mountOrUnmountSubview:view withClipRect:clipView.bounds relativeToView:clipView];
} else {
// Fallback if we can't find a suitable clipView
[self remountSubview:view];
}
[super didUpdateReactSubviews];
}
}
- (void)removeReactSubview:(UIView *)subview
{
[_reactSubviews removeObject:subview];
[subview removeFromSuperview];
}
- (NSArray<UIView *> *)reactSubviews
{
// The _reactSubviews array is only used when we have hidden
// offscreen views. If _reactSubviews is nil, we can assume
// that [self reactSubviews] and [self subviews] are the same
return _reactSubviews ?: self.subviews;
}
- (void)updateClippedSubviews
{
// Find a suitable view to use for clipping
@ -435,7 +372,7 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:unused)
[super layoutSubviews];
if (_reactSubviews) {
if (_removeClippedSubviews) {
[self updateClippedSubviews];
}
}

View File

@ -17,8 +17,19 @@
@interface UIView (React) <RCTComponent>
- (NSArray<UIView *> *)reactSubviews;
- (UIView *)reactSuperview;
/**
* RCTComponent interface.
*/
- (NSArray<UIView *> *)reactSubviews NS_REQUIRES_SUPER;
- (UIView *)reactSuperview NS_REQUIRES_SUPER;
- (void)insertReactSubview:(UIView *)subview atIndex:(NSInteger)atIndex NS_REQUIRES_SUPER;
- (void)removeReactSubview:(UIView *)subview NS_REQUIRES_SUPER;
/**
* Updates the subviews array based on the reactSubviews. Default behavior is
* to insert the reactSubviews into the UIView.
*/
- (void)didUpdateReactSubviews;
/**
* Used by the UIIManager to set the view frame.

View File

@ -56,20 +56,9 @@
return view.reactTag;
}
- (void)insertReactSubview:(UIView *)subview atIndex:(NSInteger)atIndex
{
[self insertSubview:subview atIndex:atIndex];
}
- (void)removeReactSubview:(UIView *)subview
{
RCTAssert(subview.superview == self, @"%@ is a not a subview of %@", subview, self);
[subview removeFromSuperview];
}
- (NSArray<UIView *> *)reactSubviews
{
return self.subviews;
return objc_getAssociatedObject(self, _cmd);
}
- (UIView *)reactSuperview
@ -77,6 +66,29 @@
return self.superview;
}
- (void)insertReactSubview:(UIView *)subview atIndex:(NSInteger)atIndex
{
NSMutableArray *reactSubviews = (NSMutableArray *)self.reactSubviews;
if (!reactSubviews) {
reactSubviews = [NSMutableArray new];
objc_setAssociatedObject(self, @selector(reactSubviews), reactSubviews, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
[reactSubviews insertObject:subview atIndex:atIndex];
}
- (void)removeReactSubview:(UIView *)subview
{
[(NSMutableArray *)self.reactSubviews removeObject:subview];
[subview removeFromSuperview];
}
- (void)didUpdateReactSubviews
{
for (UIView *subview in self.reactSubviews) {
[self addSubview:subview];
}
}
- (void)reactSetFrame:(CGRect)frame
{
// These frames are in terms of anchorPoint = topLeft, but internally the