Refactored subview management
Reviewed By: javache Differential Revision: D3392214 fbshipit-source-id: 6f16841df5cf866dda5ac27dd244e266ec85a86e
This commit is contained in:
parent
4e3a8343b3
commit
44c9cf3a91
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue