Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 0 additions & 65 deletions ios/gamma/stack/host/RNSStackController.swift

This file was deleted.

8 changes: 1 addition & 7 deletions ios/gamma/stack/host/RNSStackHostComponentView.h
Original file line number Diff line number Diff line change
@@ -1,18 +1,12 @@
#pragma once

#import "RNSReactBaseView.h"

@class RNSStackController;
@class RNSStackScreenComponentView;
#import "RNSStackScreenComponentView.h"

NS_ASSUME_NONNULL_BEGIN

@interface RNSStackHostComponentView : RNSReactBaseView

@property (nonatomic, nonnull, strong, readonly) RNSStackController *stackController;

- (nonnull NSMutableArray<RNSStackScreenComponentView *> *)reactSubviews;

Comment on lines -12 to -15
Copy link
Copy Markdown
Contributor Author

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

@end

#pragma mark - Communication with StackScreen
Expand Down
130 changes: 51 additions & 79 deletions ios/gamma/stack/host/RNSStackHostComponentView.mm
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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;
Expand All @@ -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) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we add error handling?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The 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.
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The 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
Expand All @@ -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
Expand All @@ -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);
}
13 changes: 13 additions & 0 deletions ios/gamma/stack/host/RNSStackNavigationController.h
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
80 changes: 80 additions & 0 deletions ios/gamma/stack/host/RNSStackNavigationController.mm
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
Loading
Loading