-
-
Notifications
You must be signed in to change notification settings - Fork 632
feat(iOS, Stack v5): Align Stack implementation with RFC 753 #3774
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
9cc66e4
18f4f6e
358089c
cfe9831
fd4a750
0a95ece
c087dd7
1cc9dba
863f126
ab5861b
6c58f04
3bf8c72
a11b361
9fb4c7a
7b0c9ab
2e04cc5
425972c
d8a04d2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
This file was deleted.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -10,21 +10,20 @@ | |
| #import "RNSDefines.h" | ||
| #import "RNSLog.h" | ||
|
|
||
| #import "RNSStackNavigationController.h" | ||
| #import "RNSStackOperationCoordinator.h" | ||
| #import "RNSStackScreenComponentView.h" | ||
| #import "Swift-Bridging.h" | ||
|
|
||
| namespace react = facebook::react; | ||
|
|
||
| static void dumpStackHostSubviewsState(NSArray<RNSStackScreenComponentView *> *reactSubviews); | ||
|
|
||
| @interface RNSStackHostComponentView () <RCTMountingTransactionObserving> | ||
| @end | ||
|
|
||
| @implementation RNSStackHostComponentView { | ||
| RNSStackController *_Nonnull _controller; | ||
| NSMutableArray<RNSStackScreenComponentView *> *_Nonnull _reactSubviews; | ||
|
|
||
| bool _hasModifiedReactSubviewsInCurrentTransaction; | ||
| RNSStackNavigationController *_Nonnull _stackNavigationController; | ||
| RNSStackOperationCoordinator *_Nonnull _stackOperationCoordinator; | ||
| NSMutableArray<RNSStackScreenComponentView *> *_Nonnull _renderedScreens; | ||
| } | ||
|
|
||
| - (instancetype)initWithFrame:(CGRect)frame | ||
|
|
@@ -37,19 +36,18 @@ - (instancetype)initWithFrame:(CGRect)frame | |
|
|
||
| - (void)initState | ||
| { | ||
| _controller = [[RNSStackController alloc] initWithStackHostComponentView:self]; | ||
| _hasModifiedReactSubviewsInCurrentTransaction = false; | ||
| _reactSubviews = [NSMutableArray new]; | ||
| _stackNavigationController = [RNSStackNavigationController new]; | ||
| _stackOperationCoordinator = [RNSStackOperationCoordinator new]; | ||
| _renderedScreens = [NSMutableArray new]; | ||
| } | ||
|
|
||
| - (void)didMoveToWindow | ||
| { | ||
| RCTAssert(_controller != nil, @"[RNScreens] Controller must not be nil while attaching to window"); | ||
|
|
||
| [self reactAddControllerToClosestParent:_controller]; | ||
| RNSLog(@"[RNScreens] StackHost [%ld] attached to window", self.tag); | ||
| [self reactAddControllerToClosestParent:_stackNavigationController]; | ||
| } | ||
|
|
||
| - (void)reactAddControllerToClosestParent:(UIViewController *)controller | ||
| - (void)reactAddControllerToClosestParent:(nonnull UIViewController *)controller | ||
| { | ||
| if (!controller.parentViewController) { | ||
| UIView *parentView = (UIView *)self.reactSuperview; | ||
|
|
@@ -66,68 +64,64 @@ - (void)reactAddControllerToClosestParent:(UIViewController *)controller | |
| } | ||
| } | ||
|
|
||
| RNS_IGNORE_SUPER_CALL_BEGIN | ||
| - (nonnull NSMutableArray<RNSStackScreenComponentView *> *)reactSubviews | ||
| { | ||
| return _reactSubviews; | ||
| } | ||
| RNS_IGNORE_SUPER_CALL_END | ||
|
|
||
| - (nonnull RNSStackController *)stackController | ||
| { | ||
| RCTAssert(_controller != nil, @"[RNScreens] Controller must not be nil"); | ||
| return _controller; | ||
| } | ||
|
|
||
| #pragma mark - Communication with StackScreen | ||
|
|
||
| - (void)stackScreenChangedActivityMode:(nonnull RNSStackScreenComponentView *)stackScreen | ||
| { | ||
| [_controller setNeedsUpdateOfChildViewControllers]; | ||
| switch (stackScreen.activityMode) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. should we add error handling?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What do you mean? |
||
| case RNSStackScreenActivityModeAttached: | ||
| [_stackOperationCoordinator addPushOperation:stackScreen]; | ||
| break; | ||
| case RNSStackScreenActivityModeDetached: | ||
| [_stackOperationCoordinator addPopOperation:stackScreen]; | ||
| break; | ||
| } | ||
| } | ||
|
|
||
| #pragma mark - RCTComponentViewProtocol | ||
|
|
||
| - (void)mountChildComponentView:(UIView<RCTComponentViewProtocol> *)childComponentView index:(NSInteger)index | ||
| { | ||
| RCTAssert( | ||
| [childComponentView isKindOfClass:RNSStackScreenComponentView.class], | ||
| @"[RNScreens] Attempt to mount child of unsupported type: %@, expected %@", | ||
| childComponentView.class, | ||
| RNSStackScreenComponentView.class); | ||
| RCTAssert([childComponentView isKindOfClass:RNSStackScreenComponentView.class], | ||
| @"[RNScreens] Attempt to mount child of unsupported type: %@, expected %@", | ||
| childComponentView.class, | ||
| RNSStackScreenComponentView.class); | ||
|
|
||
| auto *childScreen = static_cast<RNSStackScreenComponentView *>(childComponentView); | ||
| childScreen.stackHost = self; | ||
| [_reactSubviews insertObject:childScreen atIndex:index]; | ||
| _hasModifiedReactSubviewsInCurrentTransaction = true; | ||
|
|
||
| RNSLog( | ||
| @"StackHost [%ld] mount: StackScreen [%ld] (%@) at %ld", | ||
| self.tag, | ||
| childComponentView.tag, | ||
| childScreen.screenKey, | ||
| index); | ||
| [_renderedScreens insertObject:childScreen atIndex:index]; | ||
| [self addPushOperationIfNeeded:childScreen]; | ||
| } | ||
|
|
||
| - (void)addPushOperationIfNeeded:(nonnull RNSStackScreenComponentView *)stackScreen | ||
| { | ||
| if (stackScreen.activityMode == RNSStackScreenActivityModeAttached) { | ||
| [_stackOperationCoordinator addPushOperation:stackScreen]; | ||
| } | ||
| } | ||
|
|
||
| - (void)unmountChildComponentView:(UIView<RCTComponentViewProtocol> *)childComponentView index:(NSInteger)index | ||
| { | ||
| RCTAssert( | ||
| [childComponentView isKindOfClass:RNSStackScreenComponentView.class], | ||
| @"[RNScreens] Attempt to unmount child of unsupported type: %@, expected %@", | ||
| childComponentView.class, | ||
| RNSStackScreenComponentView.class); | ||
| RCTAssert([childComponentView isKindOfClass:RNSStackScreenComponentView.class], | ||
| @"[RNScreens] Attempt to unmount child of unsupported type: %@, expected %@", | ||
| childComponentView.class, | ||
| RNSStackScreenComponentView.class); | ||
|
|
||
| auto *childScreen = static_cast<RNSStackScreenComponentView *>(childComponentView); | ||
| [_reactSubviews removeObject:childScreen]; | ||
| [_renderedScreens removeObject:childScreen]; | ||
| childScreen.stackHost = nil; | ||
| _hasModifiedReactSubviewsInCurrentTransaction = true; | ||
|
|
||
| RNSLog( | ||
| @"StackHost [%ld] unmount: StackScreen [%ld] (%@) at %ld", | ||
| self.tag, | ||
| childComponentView.tag, | ||
| childScreen.screenKey, | ||
| index); | ||
| [self addPopOperationIfNeeded:childScreen]; | ||
| } | ||
|
|
||
| - (void)addPopOperationIfNeeded:(nonnull RNSStackScreenComponentView *)stackScreen | ||
| { | ||
| if (stackScreen.activityMode == RNSStackScreenActivityModeAttached && !stackScreen.isNativelyDismissed) { | ||
| // This shouldn't happen in typical scenarios but it can happen with fast-refresh. | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Comment taken from Android, should still be relevant |
||
| [_stackOperationCoordinator addPopOperation:stackScreen]; | ||
| } else { | ||
| RNSLog(@"[RNScreens] ignoring pop operation of %@, already not attached or natively dismissed", | ||
| stackScreen.screenKey); | ||
| } | ||
| } | ||
|
|
||
| + (react::ComponentDescriptorProvider)componentDescriptorProvider | ||
|
|
@@ -144,21 +138,11 @@ + (BOOL)shouldBeRecycled | |
|
|
||
| #pragma mark - RCTMountingTransactionObserving | ||
|
|
||
| - (void)mountingTransactionWillMount:(const facebook::react::MountingTransaction &)transaction | ||
| withSurfaceTelemetry:(const facebook::react::SurfaceTelemetry &)surfaceTelemetry | ||
| { | ||
| _hasModifiedReactSubviewsInCurrentTransaction = false; | ||
| [_controller reactMountingTransactionWillMount]; | ||
| } | ||
|
|
||
| - (void)mountingTransactionDidMount:(const facebook::react::MountingTransaction &)transaction | ||
| withSurfaceTelemetry:(const facebook::react::SurfaceTelemetry &)surfaceTelemetry | ||
| { | ||
| if (_hasModifiedReactSubviewsInCurrentTransaction) { | ||
| [_controller setNeedsUpdateOfChildViewControllers]; | ||
| dumpStackHostSubviewsState(_reactSubviews); | ||
| } | ||
| [_controller reactMountingTransactionDidMount]; | ||
| [_stackOperationCoordinator executePendingOperationsIfNeeded:_stackNavigationController | ||
| withRenderedScreens:_renderedScreens]; | ||
| } | ||
|
|
||
| @end | ||
|
|
@@ -167,15 +151,3 @@ - (void)mountingTransactionDidMount:(const facebook::react::MountingTransaction | |
| { | ||
| return RNSStackHostComponentView.class; | ||
| } | ||
|
|
||
| static void dumpStackHostSubviewsState(NSArray<RNSStackScreenComponentView *> *reactSubviews) | ||
| { | ||
| NSMutableArray<NSString *> *descs = [[NSMutableArray alloc] initWithCapacity:reactSubviews.count]; | ||
| for (RNSStackScreenComponentView *screen in reactSubviews) { | ||
| [descs addObject:[NSString stringWithFormat:@"StackScreen [%ld] %@ activityMode=%d", | ||
| screen.tag, | ||
| screen.screenKey, | ||
| screen.activityMode]]; | ||
| } | ||
| RNSLog(@"%@", descs); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| #pragma once | ||
|
|
||
| #include "RNSStackScreenComponentView.h" | ||
|
|
||
| @interface RNSStackNavigationController : UINavigationController | ||
|
|
||
| - (void)enqueuePushOperation:(nonnull RNSStackScreenComponentView *)stackScreen; | ||
|
|
||
| - (void)enqueuePopOperation:(nonnull RNSStackScreenComponentView *)stackScreen; | ||
|
|
||
| - (void)performContainerUpdateIfNeeded; | ||
|
|
||
| @end |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,80 @@ | ||
| #import "RNSStackNavigationController.h" | ||
| #import "RNSLog.h" | ||
| #import "RNSStackOperation.h" | ||
| #import "React/RCTAssert.h" | ||
|
|
||
| @implementation RNSStackNavigationController { | ||
| NSMutableArray<RNSPushOperation *> *_Nonnull _pendingPushOperations; | ||
| NSMutableArray<RNSPopOperation *> *_Nonnull _pendingPopOperations; | ||
| } | ||
|
|
||
| - (instancetype)init | ||
| { | ||
| if (self = [super init]) { | ||
| [self initState]; | ||
| } | ||
| return self; | ||
| } | ||
|
|
||
| - (void)initState | ||
| { | ||
| _pendingPushOperations = [NSMutableArray array]; | ||
| _pendingPopOperations = [NSMutableArray array]; | ||
| } | ||
|
|
||
| - (BOOL)hasPendingOperations | ||
| { | ||
| return _pendingPushOperations.count > 0 || _pendingPopOperations.count > 0; | ||
| } | ||
|
|
||
| - (void)enqueuePushOperation:(nonnull RNSStackScreenComponentView *)stackScreen | ||
| { | ||
| RNSPushOperation *operation = [[RNSPushOperation alloc] initWithScreen:stackScreen]; | ||
| [_pendingPushOperations addObject:operation]; | ||
| } | ||
|
|
||
| - (void)enqueuePopOperation:(nonnull RNSStackScreenComponentView *)stackScreen | ||
| { | ||
| RNSPopOperation *operation = [[RNSPopOperation alloc] initWithScreen:stackScreen]; | ||
| [_pendingPopOperations addObject:operation]; | ||
| } | ||
|
|
||
| - (void)performContainerUpdateIfNeeded | ||
| { | ||
| // NOTE: We consider UINavigationController.viewControllers to be part of | ||
| // the internal state of our stack implementation and expect it to be | ||
| // *synchronously* updated by UIKit while we perform our pop and push operations | ||
| // | ||
| // The assertions below work under this assumption | ||
|
|
||
| if ([self hasPendingOperations]) { | ||
| for (RNSPopOperation *op in _pendingPopOperations) { | ||
| UIViewController *controller = static_cast<UIViewController *>(op.stackScreen.controller); | ||
| RCTAssert([self.viewControllers count] > 1, @"[RNScreens] Attempt to pop last screen from the stack"); | ||
| RCTAssert(self.topViewController == controller, @"[RNScreens] Attempt to pop non-top screen"); | ||
| [self popViewControllerAnimated:true]; | ||
| } | ||
|
|
||
| for (RNSPushOperation *op in _pendingPushOperations) { | ||
| UIViewController *controller = static_cast<UIViewController *>(op.stackScreen.controller); | ||
| [self pushViewController:controller animated:true]; | ||
| } | ||
|
|
||
| RCTAssert([self.viewControllers count] > 0, @"[RNScreens] Stack should never be empty after updates"); | ||
|
|
||
| [self dumpStackModel]; | ||
|
|
||
| [_pendingPopOperations removeAllObjects]; | ||
| [_pendingPushOperations removeAllObjects]; | ||
| } | ||
| } | ||
|
|
||
| - (void)dumpStackModel | ||
| { | ||
| RNSLog(@"[RNScreens] StackContainer [%ld] MODEL BEGIN", self.view.tag); | ||
| for (UIViewController *viewController in self.viewControllers) { | ||
| RNSLog(@"[RNScreens] %@", static_cast<RNSStackScreenComponentView *>(viewController.view).screenKey); | ||
| } | ||
| } | ||
|
|
||
| @end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These weren't used outside the implementation or anywhere