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
198 changes: 198 additions & 0 deletions apps/src/tests/TestSubtitle.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import React from 'react';
import { ScrollView, StyleSheet, Text, View } from 'react-native';

const Stack = createNativeStackNavigator();

const MainScreen = (): JSX.Element => (
<ScrollView
contentContainerStyle={styles.scrollViewContent}
contentInsetAdjustmentBehavior="automatic">
<View style={styles.container}>
<Text style={styles.paragraph}>
Mobile development has evolved tremendously over the past decade. From
simple applications that barely connected to the internet, we now have
sophisticated apps that can handle complex data processing, real-time
communication, and immersive user experiences.
</Text>

<Text style={styles.subtitle}>React Native: A Game Changer</Text>

<Text style={styles.paragraph}>
React Native has revolutionized the way we build mobile applications. By
allowing developers to write code once and deploy it to both iOS and
Android platforms, it has significantly reduced development time and
costs. The framework leverages the power of React and JavaScript, making
it accessible to web developers who want to venture into mobile
development.
</Text>

<Text style={styles.paragraph}>
One of the key advantages of React Native is its ability to provide
near-native performance while maintaining code reusability. The
framework uses native components under the hood, ensuring that the user
experience remains smooth and responsive. This approach has made it a
popular choice among startups and large enterprises alike.
</Text>

<Text style={styles.subtitle}>The Importance of Navigation</Text>

<Text style={styles.paragraph}>
Navigation is a crucial aspect of any mobile application. Users need to
be able to move seamlessly between different screens and sections of an
app. React Navigation has become the de facto standard for navigation in
React Native applications, providing a comprehensive set of navigators
and customization options.
</Text>

<Text style={styles.paragraph}>
The react-native-screens library plays a vital role in optimizing
navigation performance. By providing native screen components, it
ensures that navigation animations are smooth and memory usage is
optimized. This is particularly important for complex applications with
multiple screens and deep navigation stacks.
</Text>

<Text style={styles.subtitle}>Performance Optimization</Text>

<Text style={styles.paragraph}>
Performance is always a critical consideration in mobile development.
Users expect applications to be fast, responsive, and efficient. React
Native provides several tools and techniques for optimizing performance,
including the use of native modules, efficient state management, and
proper component lifecycle management.
</Text>

<Text style={styles.paragraph}>
Memory management is another important aspect of mobile app performance.
Proper handling of component mounting and unmounting, avoiding memory
leaks, and optimizing image loading are all essential practices for
maintaining good performance across different devices and operating
system versions.
</Text>

<Text style={styles.subtitle}>Testing and Quality Assurance</Text>

<Text style={styles.paragraph}>
Testing is an integral part of the development process. React Native
applications can be tested using various approaches, including unit
testing with Jest, integration testing, and end-to-end testing with
tools like Detox. Proper testing ensures that applications work
correctly across different devices and scenarios.
</Text>

<Text style={styles.paragraph}>
Continuous integration and deployment pipelines are essential for
maintaining code quality and delivering updates efficiently. Tools like
GitHub Actions, CircleCI, and others can automate the testing and
deployment process, reducing the risk of introducing bugs into
production.
</Text>

<Text style={styles.subtitle}>The Developer Experience</Text>

<Text style={styles.paragraph}>
Developer experience has become increasingly important in framework
adoption. React Native provides excellent developer tools, including hot
reloading, debugging capabilities, and comprehensive documentation. The
active community and ecosystem of third-party libraries make it easier
for developers to build feature-rich applications.
</Text>

<Text style={styles.paragraph}>
The learning curve for React Native is relatively gentle, especially for
developers with React or JavaScript experience. The framework's
component-based architecture and declarative programming model make it
intuitive to understand and work with.
</Text>

<Text style={styles.subtitle}>Future Trends</Text>

<Text style={styles.paragraph}>
Looking ahead, mobile development will continue to evolve with new
technologies and user expectations. Artificial intelligence, augmented
reality, and Internet of Things integration are becoming more common in
mobile applications. React Native is well-positioned to support these
trends with its flexible architecture and strong community support.
</Text>

<Text style={styles.paragraph}>
The introduction of React Native's New Architecture, including the new
renderer (Fabric) and the new JavaScript interface (TurboModules),
promises to bring even better performance and developer experience.
These improvements will make React Native an even more compelling choice
for mobile development.
</Text>

<Text style={styles.paragraph}>
Cross-platform development will continue to gain popularity as
businesses seek to maximize their development efficiency while
maintaining high-quality user experiences. React Native's position as a
mature, well-supported framework makes it an excellent choice for
organizations looking to build scalable mobile applications.
</Text>

<Text style={styles.subtitle}>Conclusion</Text>

<Text style={styles.paragraph}>
Mobile development is an exciting and rapidly evolving field. React
Native has proven itself as a reliable and efficient framework for
building high-quality mobile applications. With continuous improvements
and a strong ecosystem, it will continue to play a significant role in
shaping the future of mobile development.
</Text>

<Text style={styles.paragraph}>
Whether you're a seasoned developer or just starting your mobile
development journey, React Native offers the tools and flexibility
needed to create amazing user experiences. The combination of
performance, developer experience, and community support makes it an
excellent choice for modern mobile application development.
</Text>
</View>
</ScrollView>
);

const App = (): JSX.Element => {
return (
<Stack.Navigator>
<Stack.Screen
name="root"
component={MainScreen}
options={{
headerLargeTitle: true,
headerTransparent: true,
title: 'Future of Mobile Dev',
// @ts-expect-error
subtitle: '09/05/2025 • 5 min read',
}}
/>
</Stack.Navigator>
);
};

const styles = StyleSheet.create({
scrollViewContent: {
flexGrow: 1,
padding: 16,
},
container: {
flex: 1,
},
subtitle: {
fontSize: 20,
fontWeight: '600',
marginTop: 24,
marginBottom: 12,
color: '#444',
},
paragraph: {
fontSize: 16,
lineHeight: 24,
marginBottom: 16,
color: '#666',
textAlign: 'justify',
},
});

export default App;
1 change: 1 addition & 0 deletions apps/src/tests/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -161,3 +161,4 @@ export { default as TestAnimation } from './TestAnimation';
export { default as TestBottomTabs } from './TestBottomTabs';
export { default as TestScreenStack } from './TestScreenStack';
export { default as TestSplitView } from './TestSplitView';
export { default as TestSubtitle } from './TestSubtitle';
11 changes: 11 additions & 0 deletions ios/RNSScreenStackHeaderConfig.h
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,17 @@ NS_ASSUME_NONNULL_BEGIN
@property (nonatomic, retain) NSNumber *titleFontSize;
@property (nonatomic, retain) NSString *titleFontWeight;
@property (nonatomic, retain) UIColor *titleColor;
@property (nonatomic, retain) NSString *subtitle;
@property (nonatomic, retain) NSString *subtitleFontFamily;
@property (nonatomic, retain) NSNumber *subtitleFontSize;
@property (nonatomic, retain) NSString *subtitleFontWeight;
@property (nonatomic, retain) UIColor *subtitleColor;
@property (nonatomic) BOOL largeSubtitlePresent;
@property (nonatomic, retain) NSString *largeSubtitle;
@property (nonatomic, retain) NSString *largeSubtitleFontFamily;
@property (nonatomic, retain) NSNumber *largeSubtitleFontSize;
@property (nonatomic, retain) NSString *largeSubtitleFontWeight;
@property (nonatomic, retain) UIColor *largeSubtitleColor;
@property (nonatomic, retain) NSString *backTitle;
@property (nonatomic, retain) NSString *backTitleFontFamily;
@property (nonatomic, retain) NSNumber *backTitleFontSize;
Expand Down
83 changes: 83 additions & 0 deletions ios/RNSScreenStackHeaderConfig.mm
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
#endif // RCT_NEW_ARCH_ENABLED

static constexpr auto DEFAULT_TITLE_FONT_SIZE = @17;
static constexpr auto DEFAULT_SUBTITLE_FONT_SIZE = @12;
static constexpr auto DEFAULT_TITLE_LARGE_FONT_SIZE = @34;

#if !defined(RCT_NEW_ARCH_ENABLED)
Expand Down Expand Up @@ -536,6 +537,64 @@

appearance.largeTitleTextAttributes = largeAttrs;
}

if (@available(iOS 26.0, *)) {
if (config.subtitleFontFamily || config.subtitleFontSize || config.subtitleFontWeight || config.subtitleColor) {
NSMutableDictionary *attrs = [NSMutableDictionary new];

// Ignore changing header title color on visionOS
#if !TARGET_OS_VISION
if (config.subtitleColor) {
attrs[NSForegroundColorAttributeName] = config.subtitleColor;
}
#endif

NSString *family = config.subtitleFontFamily ?: nil;
NSNumber *size = config.subtitleFontSize ?: DEFAULT_SUBTITLE_FONT_SIZE;
NSString *weight = config.subtitleFontWeight ?: nil;
if (family || weight) {
attrs[NSFontAttributeName] = [RCTFont updateFont:nil
withFamily:config.subtitleFontFamily
size:size
weight:weight
style:nil
variant:nil
scaleMultiplier:1.0];
} else {
attrs[NSFontAttributeName] = [UIFont boldSystemFontOfSize:[size floatValue]];
}

appearance.subtitleTextAttributes = attrs;

Check failure on line 567 in ios/RNSScreenStackHeaderConfig.mm

View workflow job for this annotation

GitHub Actions / build

property 'subtitleTextAttributes' not found on object of type 'UINavigationBarAppearance *'; did you mean 'titleTextAttributes'?

Check failure on line 567 in ios/RNSScreenStackHeaderConfig.mm

View workflow job for this annotation

GitHub Actions / build

property 'subtitleTextAttributes' not found on object of type 'UINavigationBarAppearance *'; did you mean 'titleTextAttributes'?

Check failure on line 567 in ios/RNSScreenStackHeaderConfig.mm

View workflow job for this annotation

GitHub Actions / build

property 'subtitleTextAttributes' not found on object of type 'UINavigationBarAppearance *'; did you mean 'titleTextAttributes'?
}

if (config.largeSubtitleFontFamily || config.largeSubtitleFontSize || config.largeSubtitleFontWeight || config.largeSubtitleColor) {
NSMutableDictionary *attrs = [NSMutableDictionary new];

// Ignore changing header title color on visionOS
#if !TARGET_OS_VISION
if (config.largeSubtitleColor) {
attrs[NSForegroundColorAttributeName] = config.largeSubtitleColor;
}
#endif

NSString *family = config.largeSubtitleFontFamily ?: nil;
NSNumber *size = config.largeSubtitleFontSize ?: DEFAULT_SUBTITLE_FONT_SIZE;
NSString *weight = config.largeSubtitleFontWeight ?: nil;
if (family || weight) {
attrs[NSFontAttributeName] = [RCTFont updateFont:nil
withFamily:config.largeSubtitleFontFamily
size:size
weight:weight
style:nil
variant:nil
scaleMultiplier:1.0];
} else {
attrs[NSFontAttributeName] = [UIFont boldSystemFontOfSize:[size floatValue]];
}

appearance.largeSubtitleTextAttributes = attrs;

Check failure on line 595 in ios/RNSScreenStackHeaderConfig.mm

View workflow job for this annotation

GitHub Actions / build

property 'largeSubtitleTextAttributes' not found on object of type 'UINavigationBarAppearance *'; did you mean 'largeTitleTextAttributes'?

Check failure on line 595 in ios/RNSScreenStackHeaderConfig.mm

View workflow job for this annotation

GitHub Actions / build

property 'largeSubtitleTextAttributes' not found on object of type 'UINavigationBarAppearance *'; did you mean 'largeTitleTextAttributes'?

Check failure on line 595 in ios/RNSScreenStackHeaderConfig.mm

View workflow job for this annotation

GitHub Actions / build

property 'largeSubtitleTextAttributes' not found on object of type 'UINavigationBarAppearance *'; did you mean 'largeTitleTextAttributes'?
}
}

UIImage *backButtonImage = [config loadBackButtonImageInViewController:vc];
if (backButtonImage) {
Expand Down Expand Up @@ -748,6 +807,13 @@
// This assignment should be done after `navitem.titleView = ...` assignment (iOS 16.0 bug).
// See: https://github.com/software-mansion/react-native-screens/issues/1570 (comments)
navitem.title = config.title;
if (@available(iOS 26.0, *)) {
navitem.subtitle = config.subtitle;

Check failure on line 811 in ios/RNSScreenStackHeaderConfig.mm

View workflow job for this annotation

GitHub Actions / build

property 'subtitle' not found on object of type 'UINavigationItem *'

Check failure on line 811 in ios/RNSScreenStackHeaderConfig.mm

View workflow job for this annotation

GitHub Actions / build

property 'subtitle' not found on object of type 'UINavigationItem *'

Check failure on line 811 in ios/RNSScreenStackHeaderConfig.mm

View workflow job for this annotation

GitHub Actions / build

property 'subtitle' not found on object of type 'UINavigationItem *'

if (config.largeSubtitlePresent) {
navitem.largeSubtitle = config.largeSubtitle;

Check failure on line 814 in ios/RNSScreenStackHeaderConfig.mm

View workflow job for this annotation

GitHub Actions / build

property 'largeSubtitle' not found on object of type 'UINavigationItem *'

Check failure on line 814 in ios/RNSScreenStackHeaderConfig.mm

View workflow job for this annotation

GitHub Actions / build

property 'largeSubtitle' not found on object of type 'UINavigationItem *'

Check failure on line 814 in ios/RNSScreenStackHeaderConfig.mm

View workflow job for this annotation

GitHub Actions / build

property 'largeSubtitle' not found on object of type 'UINavigationItem *'
}
}

if (animated && vc.transitionCoordinator != nil &&
vc.transitionCoordinator.presentationStyle == UIModalPresentationNone && !wasHidden) {
Expand Down Expand Up @@ -1086,6 +1152,21 @@
_largeTitleFontSize = [self getFontSizePropValue:newScreenProps.largeTitleFontSize];
_largeTitleHideShadow = newScreenProps.largeTitleHideShadow;
_largeTitleBackgroundColor = RCTUIColorFromSharedColor(newScreenProps.largeTitleBackgroundColor);

_subtitle = RCTNSStringFromStringNilIfEmpty(newScreenProps.subtitle);
if (newScreenProps.subtitleFontFamily != oldScreenProps.subtitleFontFamily) {
_subtitleFontFamily = RCTNSStringFromStringNilIfEmpty(newScreenProps.subtitleFontFamily);
}
_subtitleFontWeight = RCTNSStringFromStringNilIfEmpty(newScreenProps.subtitleFontWeight);
_subtitleFontSize = [self getFontSizePropValue:newScreenProps.subtitleFontSize];

_largeSubtitlePresent = newScreenProps.largeSubtitlePresent;
_largeSubtitle = RCTNSStringFromString(newScreenProps.largeSubtitle);
if (newScreenProps.largeSubtitleFontFamily != oldScreenProps.largeSubtitleFontFamily) {
_largeSubtitleFontFamily = RCTNSStringFromStringNilIfEmpty(newScreenProps.largeSubtitleFontFamily);
}
_largeSubtitleFontWeight = RCTNSStringFromStringNilIfEmpty(newScreenProps.largeSubtitleFontWeight);
_largeSubtitleFontSize = [self getFontSizePropValue:newScreenProps.largeSubtitleFontSize];

_backTitle = RCTNSStringFromStringNilIfEmpty(newScreenProps.backTitle);
if (newScreenProps.backTitleFontFamily != oldScreenProps.backTitleFontFamily) {
Expand All @@ -1107,6 +1188,8 @@
// We could compare color value, but it is more performant to just assign new value
_titleColor = RCTUIColorFromSharedColor(newScreenProps.titleColor);
_largeTitleColor = RCTUIColorFromSharedColor(newScreenProps.largeTitleColor);
_subtitleColor = RCTUIColorFromSharedColor(newScreenProps.subtitleColor);
_largeSubtitleColor = RCTUIColorFromSharedColor(newScreenProps.largeSubtitleColor);
_color = RCTUIColorFromSharedColor(newScreenProps.color);
_backgroundColor = RCTUIColorFromSharedColor(newScreenProps.backgroundColor);

Expand Down
2 changes: 1 addition & 1 deletion react-navigation
3 changes: 3 additions & 0 deletions src/components/ScreenStackHeaderConfig.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ export const ScreenStackHeaderConfig = React.forwardRef<
>((props, ref) => (
<ScreenStackHeaderConfigNativeComponent
{...props}
largeSubtitlePresent={
props.largeSubtitle !== undefined && props.largeSubtitle !== null
}
ref={ref}
topInsetEnabled={EDGE_TO_EDGE ? true : props.topInsetEnabled}
style={styles.headerConfig}
Expand Down
11 changes: 11 additions & 0 deletions src/fabric/ScreenStackHeaderConfigNativeComponent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,17 @@ export interface NativeProps extends ViewProps {
titleFontSize?: Int32;
titleFontWeight?: string;
titleColor?: ColorValue;
subtitle?: string;
subtitleFontFamily?: string;
subtitleFontSize?: Int32;
subtitleFontWeight?: string;
subtitleColor?: ColorValue;
largeSubtitlePresent?: boolean; // Since right now Fabric doesn't generate std::optional in the C++ code we need to have a separate prop to indicate if large subtitle is null or ""
largeSubtitle?: string;
largeSubtitleFontFamily?: string;
largeSubtitleFontSize?: Int32;
largeSubtitleFontWeight?: string;
largeSubtitleColor?: ColorValue;
disableBackButtonMenu?: boolean;
backButtonDisplayMode?: WithDefault<BackButtonDisplayMode, 'default'>;
hideBackButton?: boolean;
Expand Down
Loading
Loading