first commit

This commit is contained in:
2026-03-10 16:18:05 +00:00
commit 11f9c069b5
31635 changed files with 3187747 additions and 0 deletions

View File

@@ -0,0 +1,65 @@
import Foundation
import UIKit
@objc
public class RNSStackController: UINavigationController, ReactMountingTransactionObserving {
private var needsChildViewControllersUpdate = false
private let stackHostComponentView: RNSStackHostComponentView
@objc public required init(stackHostComponentView: RNSStackHostComponentView) {
self.stackHostComponentView = stackHostComponentView
super.init(nibName: nil, bundle: nil)
}
required init?(coder aDecoder: NSCoder) {
return nil
}
// MARK: Signals
@objc
public func setNeedsUpdateOfChildViewControllers() {
needsChildViewControllersUpdate = true
}
// MARK: Updating
@objc
public func updateChildViewControllersIfNeeded() {
if needsChildViewControllersUpdate {
updateChildViewControllers()
}
}
@objc
public func updateChildViewControllers() {
precondition(
needsChildViewControllersUpdate,
"[RNScreens] Child view controller must be invalidated when update is forced!")
let activeControllers = sourceAllViewControllers()
.filter { screenCtrl in screenCtrl.screen.activityMode == .attached }
setViewControllers(activeControllers, animated: true)
needsChildViewControllersUpdate = false
}
private func sourceAllViewControllers() -> [RNSStackScreenController] {
let screenStackComponents =
stackHostComponentView.reactSubviews() as! [RNSStackScreenComponentView]
return screenStackComponents.lazy.map(\.controller)
}
// MARK: ReactMountingTransactionObserving
@objc
public func reactMountingTransactionWillMount() {
// noop
}
@objc
public func reactMountingTransactionDidMount() {
updateChildViewControllersIfNeeded()
}
}

View File

@@ -0,0 +1,26 @@
#pragma once
#import "RNSReactBaseView.h"
@class RNSStackController;
@class RNSStackScreenComponentView;
NS_ASSUME_NONNULL_BEGIN
@interface RNSStackHostComponentView : RNSReactBaseView
@property (nonatomic, nonnull, strong, readonly) RNSStackController *stackController;
- (nonnull NSMutableArray<RNSStackScreenComponentView *> *)reactSubviews;
@end
#pragma mark - Communication with StackScreen
@interface RNSStackHostComponentView ()
- (void)stackScreenChangedActivityMode:(nonnull RNSStackScreenComponentView *)stackScreen;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,180 @@
#import "RNSStackHostComponentView.h"
#import <React/RCTConversions.h>
#import <React/RCTMountingTransactionObserving.h>
#import <React/UIView+React.h>
#import <react/renderer/components/rnscreens/ComponentDescriptors.h>
#import <react/renderer/components/rnscreens/EventEmitters.h>
#import <react/renderer/components/rnscreens/Props.h>
#import <react/renderer/components/rnscreens/RCTComponentViewHelpers.h>
#import "RNSDefines.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;
}
- (instancetype)initWithFrame:(CGRect)frame
{
if (self = [super initWithFrame:frame]) {
[self initState];
}
return self;
}
- (void)initState
{
_controller = [[RNSStackController alloc] initWithStackHostComponentView:self];
_hasModifiedReactSubviewsInCurrentTransaction = false;
_reactSubviews = [NSMutableArray new];
}
- (void)didMoveToWindow
{
RCTAssert(_controller != nil, @"[RNScreens] Controller must not be nil while attaching to window");
[self reactAddControllerToClosestParent:_controller];
}
- (void)reactAddControllerToClosestParent:(UIViewController *)controller
{
if (!controller.parentViewController) {
UIView *parentView = (UIView *)self.reactSuperview;
while (parentView) {
if (parentView.reactViewController) {
[parentView.reactViewController addChildViewController:controller];
[self addSubview:controller.view];
[controller didMoveToParentViewController:parentView.reactViewController];
break;
}
parentView = (UIView *)parentView.reactSuperview;
}
return;
}
}
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];
}
#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);
auto *childScreen = static_cast<RNSStackScreenComponentView *>(childComponentView);
childScreen.stackHost = self;
[_reactSubviews insertObject:childScreen atIndex:index];
_hasModifiedReactSubviewsInCurrentTransaction = true;
NSLog(
@"StackHost [%ld] mount: StackScreen [%ld] (%@) at %ld",
self.tag,
childComponentView.tag,
childScreen.screenKey,
index);
}
- (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);
auto *childScreen = static_cast<RNSStackScreenComponentView *>(childComponentView);
[_reactSubviews removeObject:childScreen];
childScreen.stackHost = nil;
_hasModifiedReactSubviewsInCurrentTransaction = true;
NSLog(
@"StackHost [%ld] unmount: StackScreen [%ld] (%@) at %ld",
self.tag,
childComponentView.tag,
childScreen.screenKey,
index);
}
+ (react::ComponentDescriptorProvider)componentDescriptorProvider
{
return react::concreteComponentDescriptorProvider<react::RNSStackHostComponentDescriptor>();
}
+ (BOOL)shouldBeRecycled
{
// There won't be tens of instances of this component usually & it's easier for now.
// We could consider enabling it someday though.
return NO;
}
#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];
}
@end
Class<RCTComponentViewProtocol> RNSStackHostCls(void)
{
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]];
}
NSLog(@"%@", descs);
}

View File

@@ -0,0 +1,11 @@
#pragma once
#import <React/RCTViewManager.h>
NS_ASSUME_NONNULL_BEGIN
@interface RNSStackHostComponentViewManager : RCTViewManager
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,7 @@
#import "RNSStackHostComponentViewManager.h"
@implementation RNSStackHostComponentViewManager
RCT_EXPORT_MODULE(RNSStackHostViewManager)
@end

View File

@@ -0,0 +1,41 @@
#pragma once
#import <Foundation/Foundation.h>
// Hide C++ symbols from C compiler used when building Swift module
#if defined(__cplusplus)
#import <react/renderer/components/rnscreens/EventEmitters.h>
namespace react = facebook::react;
#endif // __cplusplus
NS_ASSUME_NONNULL_BEGIN
/**
* These methods can be called to send an appropriate event to ElementTree.
* Returned value denotes whether the event has been successfully dispatched to React event pipeline.
* The returned value of `true` does not mean, that the event has been successfully delivered.
*/
@interface RNSStackScreenComponentEventEmitter : NSObject
- (BOOL)emitOnWillAppear;
- (BOOL)emitOnDidAppear;
- (BOOL)emitOnWillDisappear;
- (BOOL)emitOnDidDisappear;
- (BOOL)emitOnDismiss;
- (BOOL)emitOnNativeDismiss;
@end
#pragma mark - Hidden from Swift
#if defined(__cplusplus)
@interface RNSStackScreenComponentEventEmitter ()
- (void)updateEventEmitter:(const std::shared_ptr<const react::RNSStackScreenEventEmitter> &)emitter;
@end
#endif // __cplusplus
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,82 @@
#import "RNSStackScreenComponentEventEmitter.h"
#import <React/RCTConversions.h>
#import <React/RCTLog.h>
#import <react/renderer/components/rnscreens/EventEmitters.h>
@implementation RNSStackScreenComponentEventEmitter {
std::shared_ptr<const react::RNSStackScreenEventEmitter> _reactEventEmitter;
}
- (BOOL)emitOnWillAppear
{
if (_reactEventEmitter != nullptr) {
_reactEventEmitter->onWillAppear({});
return YES;
} else {
RCTLogWarn(@"[RNScreens] Skipped OnWillAppear event emission due to nullish emitter");
return NO;
}
}
- (BOOL)emitOnDidAppear
{
if (_reactEventEmitter != nullptr) {
_reactEventEmitter->onDidAppear({});
return YES;
} else {
RCTLogWarn(@"[RNScreens] Skipped OnDidAppear event emission due to nullish emitter");
return NO;
}
}
- (BOOL)emitOnWillDisappear
{
if (_reactEventEmitter != nullptr) {
_reactEventEmitter->onWillDisappear({});
return YES;
} else {
RCTLogWarn(@"[RNScreens] Skipped OnWillDisappear event emission due to nullish emitter");
return NO;
}
}
- (BOOL)emitOnDidDisappear
{
if (_reactEventEmitter != nullptr) {
_reactEventEmitter->onDidDisappear({});
return YES;
} else {
RCTLogWarn(@"[RNScreens] Skipped OnDidDisappear event emission due to nullish emitter");
return NO;
}
}
- (BOOL)emitOnDismiss
{
if (_reactEventEmitter != nullptr) {
_reactEventEmitter->onDismiss({.isNativeDismiss = false});
return YES;
} else {
RCTLogWarn(@"[RNScreens] Skipped OnDismiss event emission due to nullish emitter");
return NO;
}
}
- (BOOL)emitOnNativeDismiss
{
if (_reactEventEmitter != nullptr) {
_reactEventEmitter->onDismiss({.isNativeDismiss = true});
return YES;
} else {
RCTLogWarn(@"[RNScreens] Skipped OnDismiss (native) event emission due to nullish emitter");
return NO;
}
}
- (void)updateEventEmitter:(const std::shared_ptr<const react::RNSStackScreenEventEmitter> &)emitter
{
_reactEventEmitter = emitter;
}
@end

View File

@@ -0,0 +1,43 @@
#pragma once
#import "RNSReactBaseView.h"
#import "RNSStackScreenComponentEventEmitter.h"
NS_ASSUME_NONNULL_BEGIN
@class RNSStackScreenController;
@class RNSStackHostComponentView;
typedef NS_ENUM(int, RNSStackScreenActivityMode) {
RNSStackScreenActivityModeDetached = 0,
RNSStackScreenActivityModeAttached = 1,
};
@interface RNSStackScreenComponentView : RNSReactBaseView
@property (nonatomic, weak, readwrite, nullable) RNSStackHostComponentView *stackHost;
@property (nonatomic, strong, readonly, nonnull) RNSStackScreenController *controller;
@end
#pragma mark - Props
@interface RNSStackScreenComponentView ()
@property (nonatomic, strong, readonly, nullable) NSString *screenKey;
@property (nonatomic, readonly) RNSStackScreenActivityMode activityMode;
@end
#pragma mark - Events
@interface RNSStackScreenComponentView ()
/**
* Use returned object to emit appropriate React Events to Element Tree.
*/
- (nonnull RNSStackScreenComponentEventEmitter *)reactEventEmitter;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,144 @@
#import "RNSStackScreenComponentView.h"
#import <React/RCTConversions.h>
#import <React/RCTMountingTransactionObserving.h>
#import <react/renderer/components/rnscreens/ComponentDescriptors.h>
#import <react/renderer/components/rnscreens/EventEmitters.h>
#import <react/renderer/components/rnscreens/Props.h>
#import <react/renderer/components/rnscreens/RCTComponentViewHelpers.h>
#import "RNSConversions-Stack.h"
#import "RNSStackHostComponentView.h"
#import "Swift-Bridging.h"
namespace react = facebook::react;
@interface RNSStackScreenComponentView () <RCTMountingTransactionObserving>
@end
#pragma mark - View implementation
@implementation RNSStackScreenComponentView {
RNSStackScreenController *_Nonnull _controller;
RNSStackScreenComponentEventEmitter *_Nonnull _reactEventEmitter;
// Flags
BOOL _hasUpdatedActivityMode;
}
- (instancetype)initWithFrame:(CGRect)frame
{
if (self = [super initWithFrame:frame]) {
[self initState];
}
return self;
}
- (void)initState
{
[self resetProps];
[self setupController];
_reactEventEmitter = [RNSStackScreenComponentEventEmitter new];
_hasUpdatedActivityMode = NO;
}
- (void)resetProps
{
static const auto defaultProps = std::make_shared<const react::RNSScreenStackProps>();
_props = defaultProps;
// container state
_screenKey = nil;
_activityMode = RNSStackScreenActivityModeDetached;
}
- (void)setupController
{
_controller = [[RNSStackScreenController alloc] initWithComponentView:self];
_controller.view = self;
}
- (void)invalidateImpl
{
// We want to run after container updates are performed (transitions etc.)
__weak auto weakSelf = self;
dispatch_async(dispatch_get_main_queue(), ^{
auto strongSelf = weakSelf;
if (strongSelf) {
strongSelf->_controller = nil;
}
});
}
#pragma mark - Events
- (nonnull RNSStackScreenComponentEventEmitter *)reactEventEmitter
{
RCTAssert(_reactEventEmitter != nil, @"[RNScreens] Attempt to access uninitialized _reactEventEmitter");
return _reactEventEmitter;
}
#pragma mark - RCTComponentViewProtocol
- (void)updateProps:(const facebook::react::Props::Shared &)props
oldProps:(const facebook::react::Props::Shared &)oldProps
{
const auto &oldComponentProps = *std::static_pointer_cast<const react::RNSStackScreenProps>(_props);
const auto &newComponentProps = *std::static_pointer_cast<const react::RNSStackScreenProps>(props);
if (oldComponentProps.activityMode != newComponentProps.activityMode) {
_activityMode = rnscreens::conversion::convert<RNSStackScreenActivityMode>(newComponentProps.activityMode);
_hasUpdatedActivityMode = YES;
}
if (oldComponentProps.screenKey != newComponentProps.screenKey) {
RCTAssert(_screenKey == nil, @"[RNScreens] ScreenController cannot change its screenKey");
_screenKey = RCTNSStringFromStringNilIfEmpty(newComponentProps.screenKey);
}
[super updateProps:props oldProps:oldProps];
}
- (void)finalizeUpdates:(RNComponentViewUpdateMask)updateMask
{
if (_hasUpdatedActivityMode) {
_hasUpdatedActivityMode = NO;
[self.stackHost stackScreenChangedActivityMode:self];
}
[super finalizeUpdates:updateMask];
}
- (void)updateEventEmitter:(const facebook::react::EventEmitter::Shared &)eventEmitter
{
[super updateEventEmitter:eventEmitter];
[_reactEventEmitter
updateEventEmitter:std::static_pointer_cast<const react::RNSStackScreenEventEmitter>(eventEmitter)];
}
+ (react::ComponentDescriptorProvider)componentDescriptorProvider
{
return react::concreteComponentDescriptorProvider<react::RNSStackScreenComponentDescriptor>();
}
+ (BOOL)shouldBeRecycled
{
// There won't be tens of instances of this component usually & it's easier for now.
// We could consider enabling it someday though.
return NO;
}
- (void)invalidate
{
[self invalidateImpl];
}
@end
Class<RCTComponentViewProtocol> RNSStackScreenCls(void)
{
return RNSStackScreenComponentView.class;
}

View File

@@ -0,0 +1,11 @@
#pragma once
#import <React/RCTViewManager.h>
NS_ASSUME_NONNULL_BEGIN
@interface RNSStackScreenComponentViewManager : RCTViewManager
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,7 @@
#import "RNSStackScreenComponentViewManager.h"
@implementation RNSStackScreenComponentViewManager
RCT_EXPORT_MODULE(RNSStackScreenViewManager)
@end

View File

@@ -0,0 +1,64 @@
import Foundation
import UIKit
@objc
public class RNSStackScreenController: UIViewController {
let screen: RNSStackScreenComponentView
private var reactEventEmitter: RNSStackScreenComponentEventEmitter {
return screen.reactEventEmitter()
}
@objc public required init(componentView: RNSStackScreenComponentView) {
self.screen = componentView
super.init(nibName: nil, bundle: nil)
}
required init?(coder aDecoder: NSCoder) {
return nil
}
func findStackController() -> RNSStackController? {
if let navCtrl = self.navigationController {
return navCtrl as? RNSStackController
}
if let stackHost = self.screen.stackHost {
return stackHost.stackController
}
return nil
}
// MARK: Signals
// MARK: Events
public override func viewWillAppear(_ animated: Bool) {
reactEventEmitter.emitOnWillAppear()
}
public override func viewDidAppear(_ animated: Bool) {
reactEventEmitter.emitOnDidAppear()
}
public override func viewWillDisappear(_ animated: Bool) {
reactEventEmitter.emitOnWillDisappear()
}
public override func viewDidDisappear(_ animated: Bool) {
reactEventEmitter.emitOnDidDisappear()
}
public override func didMove(toParent parent: UIViewController?) {
print("ScreenCtrl [\(self.screen.tag)] didMoveToParent \(String(describing: parent))")
super.didMove(toParent: parent)
if parent == nil {
if self.screen.activityMode == .detached {
reactEventEmitter.emitOnDismiss()
} else {
reactEventEmitter.emitOnNativeDismiss()
}
}
}
}