diff --git a/apps/src/shared/gamma/containers/stack/StackContainer.tsx b/apps/src/shared/gamma/containers/stack/StackContainer.tsx
index a5cb87f84e..35f4b229e2 100644
--- a/apps/src/shared/gamma/containers/stack/StackContainer.tsx
+++ b/apps/src/shared/gamma/containers/stack/StackContainer.tsx
@@ -3,8 +3,10 @@ import {
ScreenStackHost,
StackScreen,
StackScreenLifecycleState,
+ StackScreenHeaderConfig,
} from 'react-native-screens';
import type { StackScreenNativeProps } from 'react-native-screens/components/gamma/StackScreen';
+import type { StackScreenHeaderConfigNativeProps } from 'react-native-screens/components/gamma/StackScreenHeaderConfig';
let id = 0;
@@ -17,7 +19,7 @@ interface StackProps {
}
interface ScreenProps {
- // TBA
+ navigationBar: StackScreenHeaderConfigNativeProps;
}
type Path = {
@@ -156,11 +158,13 @@ export function StackContainer({ pathConfigs }: StackContainerProps) {
onPop={handlePop}>
+ ...screenProps,
+ pop,
+ push,
+ }}
+ >
+
))}
diff --git a/apps/src/tests/TestScreenStack/helper.tsx b/apps/src/tests/TestScreenStack/helper.tsx
index 54d1c9cb2d..d3a850ddf5 100644
--- a/apps/src/tests/TestScreenStack/helper.tsx
+++ b/apps/src/tests/TestScreenStack/helper.tsx
@@ -2,26 +2,28 @@ import React from 'react';
import { Button } from 'react-native';
import { ScreenLayout } from './ScreenLayout';
import { useStackNavigation } from '../../shared/gamma/containers/stack/StackContainer';
+import { ScreenProps } from 'react-native-screens';
-export function generateStackWithNames(screenNames: string[]) {
+export function generateStackWithNames(screens: {name: string; options?: ScreenProps;}[]) {
const TestComponent = () => {
const navigation = useStackNavigation();
return (
- {screenNames.map(screenName => (
+ {screens.map(screen => (
);
};
- return screenNames.map(screenName => ({
- name: screenName,
+ return screens.map(screen => ({
+ name: screen.name,
component: TestComponent,
+ options: screen.options,
}));
}
diff --git a/apps/src/tests/TestScreenStack/index.tsx b/apps/src/tests/TestScreenStack/index.tsx
index d446401eaf..d914100406 100644
--- a/apps/src/tests/TestScreenStack/index.tsx
+++ b/apps/src/tests/TestScreenStack/index.tsx
@@ -1,10 +1,46 @@
import React from 'react';
-
-import { generateStackWithNames } from './helper';
import { StackContainer } from '../../shared/gamma/containers/stack/StackContainer';
+import { generateStackWithNames } from './helper';
+
+
+const config = generateStackWithNames([
+ {
+ name: 'A',
+ options: {
+ navigationBar: {
+ title: 'Screen A',
+ },
+ },
+ },
+ {
+ name: 'B',
+ options: {
+ navigationBar: {
+ title: 'Screen B',
+ },
+ },
+ },
+ {
+ name: 'C',
+ options: {
+ navigationBar: {
+ title: 'Screen C',
+ },
+ },
+ },
+]);
export default function App() {
return (
-
+
);
}
+
+// import { generateStackWithNames } from './helper';
+// import { StackContainer } from './StackContainer';
+
+// export default function App() {
+// return (
+//
+// );
+// }
diff --git a/ios/bottom-tabs/RNSTabBarController.h b/ios/bottom-tabs/RNSTabBarController.h
index 8c79a71de2..3a60b7fa35 100644
--- a/ios/bottom-tabs/RNSTabBarController.h
+++ b/ios/bottom-tabs/RNSTabBarController.h
@@ -122,7 +122,7 @@ NS_ASSUME_NONNULL_BEGIN
@property (nonatomic, readwrite) bool needsUpdateOfSelectedTab;
/**
- * Tell the controller that some configuration regarding the tab bar apperance has changed & the appearance requires
+ * Tell the controller that some configuration regarding the tab bar appearance has changed & the appearance requires
* update.
*/
@property (nonatomic, readwrite) bool needsUpdateOfTabBarAppearance;
diff --git a/ios/gamma/stack/RNSScreenStackHostComponentView.h b/ios/gamma/stack/RNSScreenStackHostComponentView.h
index 5184fceb41..21a8cd4177 100644
--- a/ios/gamma/stack/RNSScreenStackHostComponentView.h
+++ b/ios/gamma/stack/RNSScreenStackHostComponentView.h
@@ -5,10 +5,11 @@
NS_ASSUME_NONNULL_BEGIN
+@class RNSStackController;
+
@interface RNSScreenStackHostComponentView : RNSReactBaseView
@property (nonatomic, nonnull, strong, readonly) RNSStackController *stackController;
-
- (nonnull NSMutableArray *)reactSubviews;
@end
diff --git a/ios/gamma/stack/RNSStackController.swift b/ios/gamma/stack/RNSStackController.swift
index 8de0afef28..72cd51ed16 100644
--- a/ios/gamma/stack/RNSStackController.swift
+++ b/ios/gamma/stack/RNSStackController.swift
@@ -4,10 +4,13 @@ import UIKit
@objc
public class RNSStackController: UINavigationController, ReactMountingTransactionObserving {
private var needsChildViewControllersUpdate = false
+ private var needsNavigationBarAppearanceUpdate = false
private let screenStackHostComponentView: RNSScreenStackHostComponentView
-
+ private let navigationAppearanceCoordinator: RNSStackNavigationAppearanceCoordinator
+
@objc public required init(stackHostComponentView: RNSScreenStackHostComponentView) {
- self.screenStackHostComponentView = stackHostComponentView
+ self.screenStackHostComponentView = stackHostComponentView;
+ self.navigationAppearanceCoordinator = RNSStackNavigationAppearanceCoordinator()
super.init(nibName: nil, bundle: nil)
}
@@ -21,6 +24,11 @@ public class RNSStackController: UINavigationController, ReactMountingTransactio
public func setNeedsUpdateOfChildViewControllers() {
needsChildViewControllersUpdate = true
}
+
+ @objc
+ public func setNeedsNavigationBarAppearanceUpdate() {
+ needsNavigationBarAppearanceUpdate = true
+ }
// MARK: Updating
@@ -34,23 +42,41 @@ public class RNSStackController: UINavigationController, ReactMountingTransactio
@objc
public func updateChildViewControllers() {
precondition(
- needsChildViewControllersUpdate,
- "[RNScreens] Child view controller must be invalidated when update is forced!")
+ needsChildViewControllersUpdate,
+ "[RNScreens] Child view controller must be invalidated when update is forced!")
- let activeControllers = sourceAllViewControllers()
- .filter { screenCtrl in screenCtrl.screenStackComponentView.maxLifecycleState == .attached }
+ let activeControllers = sourceAllViewControllers()
+ .filter { screenCtrl in screenCtrl.screenStackComponentView.maxLifecycleState == .attached }
- setViewControllers(activeControllers, animated: true)
+ setViewControllers(activeControllers, animated: true)
- needsChildViewControllersUpdate = false
+ needsChildViewControllersUpdate = false
}
-
+
private func sourceAllViewControllers() -> [RNSStackScreenController] {
- let screenStackComponents =
- screenStackHostComponentView.reactSubviews() as! [RNSStackScreenComponentView]
- return screenStackComponents.lazy.map(\.controller)
+ let screenStackComponents =
+ screenStackHostComponentView.reactSubviews() as! [RNSStackScreenComponentView]
+ return screenStackComponents.lazy.map(\.controller)
+ }
+
+ @objc
+ public func updateNavigationBarAppearanceIfNeeded() {
+ if needsNavigationBarAppearanceUpdate {
+ updateNavigationBarAppearance()
+ }
}
-
+
+ @objc
+ public func updateNavigationBarAppearance() {
+ precondition(needsNavigationBarAppearanceUpdate, "[RNScreens] Header appearance must be invalidated when update is forced!")
+
+ let currentSubviews = screenStackHostComponentView.reactSubviews() as! [RNSStackScreenComponentView]
+ let viewControllers = currentSubviews.map { $0.controller }
+
+ navigationAppearanceCoordinator.updateNavigationBarAppearance(navigationBar: self.navigationBar, viewControllers: viewControllers);
+ needsNavigationBarAppearanceUpdate = false
+ }
+
// MARK: ReactMountingTransactionObserving
@objc
@@ -61,5 +87,6 @@ public class RNSStackController: UINavigationController, ReactMountingTransactio
@objc
public func reactMountingTransactionDidMount() {
updateChildViewControllersIfNeeded()
+ updateNavigationBarAppearanceIfNeeded()
}
}
diff --git a/ios/gamma/stack/RNSStackNavigationAppearance.swift b/ios/gamma/stack/RNSStackNavigationAppearance.swift
new file mode 100644
index 0000000000..80df666502
--- /dev/null
+++ b/ios/gamma/stack/RNSStackNavigationAppearance.swift
@@ -0,0 +1,7 @@
+import Foundation
+
+@objc
+public class RNSStackNavigationAppearance: NSObject {
+ @objc
+ public var title: String?
+}
diff --git a/ios/gamma/stack/RNSStackNavigationAppearanceCoordinator.swift b/ios/gamma/stack/RNSStackNavigationAppearanceCoordinator.swift
new file mode 100644
index 0000000000..7eb1af9add
--- /dev/null
+++ b/ios/gamma/stack/RNSStackNavigationAppearanceCoordinator.swift
@@ -0,0 +1,21 @@
+import Foundation
+import UIKit
+
+@objc
+public class RNSStackNavigationAppearanceCoordinator: NSObject {
+ @objc
+ func updateNavigationBarAppearance(navigationBar: UINavigationBar, viewControllers: [RNSStackScreenController]) {
+ for viewController in viewControllers {
+ if (!viewController.needsNavigationBarAppearanceUpdate) {
+ continue
+ }
+
+ // TODO: Improve once more props is available
+ viewController.navigationItem.title = viewController.navigationAppearance?.title
+
+ viewController.navigationBarAppearanceDidUpdate()
+ }
+ }
+
+
+}
diff --git a/ios/gamma/stack/RNSStackScreenComponentView.mm b/ios/gamma/stack/RNSStackScreenComponentView.mm
index f17c428743..bd34ff1861 100644
--- a/ios/gamma/stack/RNSStackScreenComponentView.mm
+++ b/ios/gamma/stack/RNSStackScreenComponentView.mm
@@ -1,4 +1,6 @@
#import "RNSStackScreenComponentView.h"
+#import "RNSStackScreenHeaderConfigComponentView.h"
+
#import
#import
#import
@@ -58,6 +60,11 @@ - (void)setupController
_controller.view = self;
}
+- (void)didMoveToWindow
+{
+ [_controller didMoveToWindow];
+}
+
#pragma mark - Events
- (nonnull RNSStackScreenComponentEventEmitter *)reactEventEmitter
diff --git a/ios/gamma/stack/RNSStackScreenController.swift b/ios/gamma/stack/RNSStackScreenController.swift
index d5171705df..4c3cc4de47 100644
--- a/ios/gamma/stack/RNSStackScreenController.swift
+++ b/ios/gamma/stack/RNSStackScreenController.swift
@@ -1,13 +1,13 @@
-import Foundation
-import UIKit
-
@objc
public class RNSStackScreenController: UIViewController {
let screenStackComponentView: RNSStackScreenComponentView
+ public var navigationAppearance: RNSStackNavigationAppearance?
+ public var needsNavigationBarAppearanceUpdate: Bool = false
+
private var reactEventEmitter: RNSStackScreenComponentEventEmitter {
return screenStackComponentView.reactEventEmitter()
}
-
+
@objc public required init(componentView: RNSStackScreenComponentView) {
self.screenStackComponentView = componentView
super.init(nibName: nil, bundle: nil)
@@ -19,23 +19,49 @@ public class RNSStackScreenController: UIViewController {
func findStackController() -> RNSStackController? {
if let navCtrl = self.navigationController {
- return navCtrl as? RNSStackController
- }
+ return navCtrl as? RNSStackController
+ }
- if let stackHost = self.screenStackComponentView.stackHost {
- return stackHost.stackController
- }
+ if let stackHost = self.screenStackComponentView.stackHost {
+ return stackHost.stackController
+ }
- return nil
+ return nil
+ }
+
+
+ @objc func requestNavigationBarAppearanceUpdate() {
+ let stackController = findStackController()
+
+ if (stackController != nil && needsNavigationBarAppearanceUpdate) {
+ // We're not clearing the flag, as it will be cleared after appearance update by host
+ stackController?.setNeedsNavigationBarAppearanceUpdate()
+ }
}
// MARK: Signals
-
+ @objc
+ public func didMoveToWindow() {
+ requestNavigationBarAppearanceUpdate()
+ }
+
@objc
public func setNeedsLifecycleStateUpdate() {
findStackController()?.setNeedsUpdateOfChildViewControllers()
}
-
+
+ @objc
+ public func setNeedsNavigationBarAppearanceUpdate(_ navigationAppearance: RNSStackNavigationAppearance) {
+ self.navigationAppearance = navigationAppearance
+ needsNavigationBarAppearanceUpdate = true
+ requestNavigationBarAppearanceUpdate()
+ }
+
+ @objc
+ public func navigationBarAppearanceDidUpdate() {
+ needsNavigationBarAppearanceUpdate = false
+ }
+
// MARK: Events
public override func viewWillAppear(_ animated: Bool) {
diff --git a/ios/gamma/stack/RNSStackScreenHeaderConfigComponentView.h b/ios/gamma/stack/RNSStackScreenHeaderConfigComponentView.h
new file mode 100644
index 0000000000..69edf1d3e5
--- /dev/null
+++ b/ios/gamma/stack/RNSStackScreenHeaderConfigComponentView.h
@@ -0,0 +1,15 @@
+#import "RNSReactBaseView.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface RNSStackScreenHeaderConfigComponentView : RNSReactBaseView
+
+@property (nonatomic, strong, readonly, nullable) NSString *title;
+
+@end
+
+@interface RNSStackScreenHeaderConfigComponentView()
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/ios/gamma/stack/RNSStackScreenHeaderConfigComponentView.mm b/ios/gamma/stack/RNSStackScreenHeaderConfigComponentView.mm
new file mode 100644
index 0000000000..e99e5e16f1
--- /dev/null
+++ b/ios/gamma/stack/RNSStackScreenHeaderConfigComponentView.mm
@@ -0,0 +1,106 @@
+#import "RNSStackScreenHeaderConfigComponentView.h"
+
+#import
+#import
+#import
+#import
+#import
+#import
+#import
+#import "RNSDefines.h"
+#import "RNSStackScreenComponentView.h"
+
+#import "Swift-Bridging.h"
+
+namespace react = facebook::react;
+
+@interface RNSStackScreenHeaderConfigComponentView ()
+@end
+
+@implementation RNSStackScreenHeaderConfigComponentView {
+ // flags
+ BOOL _needsNavigationBarAppearanceUpdate;
+}
+
+- (instancetype)initWithFrame:(CGRect)frame
+{
+ if (self = [super initWithFrame:frame]) {
+ [self initState];
+ }
+ return self;
+}
+
+- (void)initState
+{
+ [self resetProps];
+}
+
+- (void)resetProps
+{
+ static const auto defaultProps = std::make_shared();
+ _props = defaultProps;
+
+ // flags
+ _needsNavigationBarAppearanceUpdate = NO;
+ // navigation item props
+ _title = nil;
+}
+
+- (nullable RNSStackScreenComponentView *)findParent
+{
+ return static_cast(self.superview);
+}
+
+- (void)requestNavigationBarAppearanceUpdate
+{
+ auto parent = [self findParent];
+
+ if (_needsNavigationBarAppearanceUpdate && parent != nil) {
+ _needsNavigationBarAppearanceUpdate = NO;
+
+ auto stackNavigationProps = [[RNSStackNavigationAppearance alloc] init];
+
+ stackNavigationProps.title = _title;
+
+ RNSStackScreenController *stackScreenController = parent.controller;
+ [stackScreenController setNeedsNavigationBarAppearanceUpdate:stackNavigationProps];
+ }
+}
+
+- (void)didMoveToWindow
+{
+ [self requestNavigationBarAppearanceUpdate];
+}
+
+#pragma mark - RCTViewComponentViewProtocol
+
++ (react::ComponentDescriptorProvider)componentDescriptorProvider
+{
+ return react::concreteComponentDescriptorProvider();
+}
+
+- (void)updateProps:(const facebook::react::Props::Shared &)props
+ oldProps:(const facebook::react::Props::Shared &)oldProps
+{
+ const auto &oldComponentProps = *std::static_pointer_cast(_props);
+ const auto &newComponentProps = *std::static_pointer_cast(props);
+ if (oldComponentProps.title != newComponentProps.title) {
+ _title = RCTNSStringFromStringNilIfEmpty(newComponentProps.title);
+ _needsNavigationBarAppearanceUpdate = YES;
+ }
+
+ [super updateProps:props oldProps:oldProps];
+}
+
+- (void)finalizeUpdates:(RNComponentViewUpdateMask)updateMask
+{
+ [super finalizeUpdates:updateMask];
+ [self requestNavigationBarAppearanceUpdate];
+}
+
+@end
+
+Class RNSStackScreenHeaderConfigCls(void)
+{
+ return RNSStackScreenHeaderConfigComponentView.class;
+}
diff --git a/ios/gamma/stack/RNSStackScreenHeaderConfigComponentViewManager.h b/ios/gamma/stack/RNSStackScreenHeaderConfigComponentViewManager.h
new file mode 100644
index 0000000000..86b41e7123
--- /dev/null
+++ b/ios/gamma/stack/RNSStackScreenHeaderConfigComponentViewManager.h
@@ -0,0 +1,11 @@
+#pragma once
+
+#import
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface RNSStackScreenHeaderConfigComponentViewManager : RCTViewManager
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/ios/gamma/stack/RNSStackScreenHeaderConfigComponentViewManager.mm b/ios/gamma/stack/RNSStackScreenHeaderConfigComponentViewManager.mm
new file mode 100644
index 0000000000..196b179ab0
--- /dev/null
+++ b/ios/gamma/stack/RNSStackScreenHeaderConfigComponentViewManager.mm
@@ -0,0 +1,7 @@
+#import "RNSStackScreenHeaderConfigComponentViewManager.h"
+
+@implementation RNSStackScreenHeaderConfigComponentViewManager
+
+RCT_EXPORT_MODULE(RNSStackScreenHeaderConfigComponentViewManager)
+
+@end
diff --git a/package.json b/package.json
index d38c07af18..9cfacb582a 100644
--- a/package.json
+++ b/package.json
@@ -165,6 +165,7 @@
"componentProvider": {
"RNSStackScreen": "RNSStackScreenComponentView",
"RNSScreenStackHost": "RNSScreenStackHostComponentView",
+ "RNSStackScreenHeaderConfig": "RNSStackScreenHeaderConfigComponentView",
"RNSBottomTabsScreen": "RNSBottomTabsScreenComponentView",
"RNSBottomTabs": "RNSBottomTabsHostComponentView",
"RNSFullWindowOverlay": "RNSFullWindowOverlay",
@@ -221,6 +222,9 @@
"RNSScreenStackHost": {
"className": "RNSScreenStackHostComponentView"
},
+ "RNSStackScreenHeaderConfig": {
+ "className": "RNSStackScreenHeaderConfigComponentView"
+ },
"RNSBottomTabsScreen": {
"className": "RNSBottomTabsScreenComponentView"
},
diff --git a/src/components/gamma/StackScreenHeaderConfig.tsx b/src/components/gamma/StackScreenHeaderConfig.tsx
new file mode 100644
index 0000000000..fda40001a9
--- /dev/null
+++ b/src/components/gamma/StackScreenHeaderConfig.tsx
@@ -0,0 +1,24 @@
+import React from 'react';
+import type { ViewProps } from 'react-native';
+import StackScreenHeaderConfigNativeComponent, {NativeProps} from '../../fabric/gamma/StackScreenHeaderConfigNativeComponent';
+
+export type StackScreenHeaderConfigNativeProps = NativeProps & {
+ // Overrides
+}
+
+type StackScreenHeaderConfigProps = {
+ children?: ViewProps['children'];
+} & StackScreenHeaderConfigNativeProps;
+
+function StackScreenHeaderConfig({
+ children,
+ ...props
+}: StackScreenHeaderConfigProps) {
+ return (
+
+ {children}
+
+ );
+}
+
+export default StackScreenHeaderConfig;
diff --git a/src/fabric/gamma/StackScreenHeaderConfigNativeComponent.ts b/src/fabric/gamma/StackScreenHeaderConfigNativeComponent.ts
new file mode 100644
index 0000000000..24d61a6905
--- /dev/null
+++ b/src/fabric/gamma/StackScreenHeaderConfigNativeComponent.ts
@@ -0,0 +1,11 @@
+'use client';
+
+import type { ViewProps } from 'react-native';
+import { WithDefault } from 'react-native/Libraries/Types/CodegenTypes';
+import codegenNativeComponent from 'react-native/Libraries/Utilities/codegenNativeComponent';
+
+export interface NativeProps extends ViewProps {
+ title?: WithDefault;
+}
+
+export default codegenNativeComponent('RNSStackScreenHeaderConfig', {});
diff --git a/src/index.tsx b/src/index.tsx
index 7c5ff06235..249f7710c6 100644
--- a/src/index.tsx
+++ b/src/index.tsx
@@ -65,6 +65,7 @@ export { default as useTransitionProgress } from './useTransitionProgress';
export { default as BottomTabs } from './components/BottomTabs';
export { default as BottomTabsScreen } from './components/BottomTabsScreen';
export { default as ScreenStackHost } from './components/gamma/ScreenStackHost';
+export { default as StackScreenHeaderConfig} from './components/gamma/StackScreenHeaderConfig';
export {
default as StackScreen,
StackScreenLifecycleState,