From 42fde594cf2b213e33fa44f8a0d38f93ba6a58ab Mon Sep 17 00:00:00 2001 From: gaetano <64538010+skrtdev@users.noreply.github.com> Date: Sat, 23 May 2026 21:41:53 +0200 Subject: [PATCH 1/6] fix: support hintTextColor on iOS search bar --- ios/RNSSearchBar.mm | 24 +++++++++++++++++++++++- src/fabric/SearchBarNativeComponent.ts | 2 +- src/types.tsx | 2 -- 3 files changed, 24 insertions(+), 4 deletions(-) diff --git a/ios/RNSSearchBar.mm b/ios/RNSSearchBar.mm index ac9dbf0720..67af793c7f 100644 --- a/ios/RNSSearchBar.mm +++ b/ios/RNSSearchBar.mm @@ -24,6 +24,8 @@ @implementation RNSSearchBar { __weak RCTBridge *_bridge; UISearchController *_controller; UIColor *_textColor; + UIColor *_hintTextColor; + NSString *_placeholder; // We use those booleans to log a warning if user attempts to restore // default behavior after setting explicit value for the prop. @@ -204,7 +206,14 @@ - (void)setAutoCapitalize:(UITextAutocapitalizationType)autoCapitalize - (void)setPlaceholder:(NSString *)placeholder { - [_controller.searchBar setPlaceholder:placeholder]; + _placeholder = placeholder; + + if (_hintTextColor != nil && _placeholder != nil) { + _controller.searchBar.searchTextField.attributedPlaceholder = + [[NSAttributedString alloc] initWithString:_placeholder attributes:@{NSForegroundColorAttributeName : _hintTextColor}]; + } else { + [_controller.searchBar setPlaceholder:_placeholder]; + } } - (void)setBarTintColor:(UIColor *)barTintColor @@ -227,6 +236,14 @@ - (void)setTextColor:(UIColor *)textColor #endif } +- (void)setHintTextColor:(UIColor *)hintTextColor +{ +#if !TARGET_OS_TV + _hintTextColor = hintTextColor; + [self setPlaceholder:_placeholder]; +#endif +} + - (void)setCancelButtonText:(NSString *)text { [_controller.searchBar setValue:text forKey:@"cancelButtonText"]; @@ -429,6 +446,10 @@ - (void)updateProps:(react::Props::Shared const &)props oldProps:(react::Props:: [self setTextColor:RCTUIColorFromSharedColor(newScreenProps.textColor)]; } + if (oldScreenProps.hintTextColor != newScreenProps.hintTextColor) { + [self setHintTextColor:RCTUIColorFromSharedColor(newScreenProps.hintTextColor)]; + } + if (oldScreenProps.placement != newScreenProps.placement) { self.placement = [RNSConvert RNSScreenSearchBarPlacementFromCppEquivalent:newScreenProps.placement]; } @@ -512,6 +533,7 @@ - (UIView *)view RCT_EXPORT_VIEW_PROPERTY(barTintColor, UIColor) RCT_EXPORT_VIEW_PROPERTY(tintColor, UIColor) RCT_EXPORT_VIEW_PROPERTY(textColor, UIColor) +RCT_EXPORT_VIEW_PROPERTY(hintTextColor, UIColor) RCT_EXPORT_VIEW_PROPERTY(cancelButtonText, NSString) RCT_EXPORT_VIEW_PROPERTY(placement, RNSSearchBarPlacement) RCT_EXPORT_VIEW_PROPERTY(allowToolbarIntegration, BOOL) diff --git a/src/fabric/SearchBarNativeComponent.ts b/src/fabric/SearchBarNativeComponent.ts index cb876835d0..d746e7bc17 100644 --- a/src/fabric/SearchBarNativeComponent.ts +++ b/src/fabric/SearchBarNativeComponent.ts @@ -60,6 +60,7 @@ export interface NativeProps extends ViewProps { barTintColor?: ColorValue | undefined; tintColor?: ColorValue | undefined; textColor?: ColorValue | undefined; + hintTextColor?: ColorValue | undefined; // Android only autoFocus?: CT.WithDefault; @@ -68,7 +69,6 @@ export interface NativeProps extends ViewProps { inputType?: string | undefined; onClose?: CT.DirectEventHandler | null | undefined; onOpen?: CT.DirectEventHandler | null | undefined; - hintTextColor?: ColorValue | undefined; headerIconColor?: ColorValue | undefined; shouldShowHintSearchIcon?: CT.WithDefault; } diff --git a/src/types.tsx b/src/types.tsx index 76a83f3acb..9a1d5d1ed5 100644 --- a/src/types.tsx +++ b/src/types.tsx @@ -1064,8 +1064,6 @@ export interface SearchBarProps { textColor?: ColorValue | undefined; /** * The search hint text color - * - * @plaform android */ hintTextColor?: ColorValue | undefined; /** From 015d02d222f83418ca8bbb1d0740ca2ebd5b4fc7 Mon Sep 17 00:00:00 2001 From: gaetano <64538010+skrtdev@users.noreply.github.com> Date: Mon, 25 May 2026 19:33:15 +0200 Subject: [PATCH 2/6] Move hintTextColor above the comment --- src/fabric/SearchBarNativeComponent.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/fabric/SearchBarNativeComponent.ts b/src/fabric/SearchBarNativeComponent.ts index d746e7bc17..1dcabc1298 100644 --- a/src/fabric/SearchBarNativeComponent.ts +++ b/src/fabric/SearchBarNativeComponent.ts @@ -56,11 +56,11 @@ export interface NativeProps extends ViewProps { obscureBackground?: CT.WithDefault; hideNavigationBar?: CT.WithDefault; cancelButtonText?: string | undefined; + hintTextColor?: ColorValue | undefined; // TODO: implement these on iOS barTintColor?: ColorValue | undefined; tintColor?: ColorValue | undefined; textColor?: ColorValue | undefined; - hintTextColor?: ColorValue | undefined; // Android only autoFocus?: CT.WithDefault; From 6278fec2b3790637dfa0bb0ac155ba4aeaa38703 Mon Sep 17 00:00:00 2001 From: gaetano <64538010+skrtdev@users.noreply.github.com> Date: Mon, 25 May 2026 19:36:45 +0200 Subject: [PATCH 3/6] Remove (Android only) from GUIDE_FOR_LIBRARY_AUTHORS.md --- guides/GUIDE_FOR_LIBRARY_AUTHORS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/guides/GUIDE_FOR_LIBRARY_AUTHORS.md b/guides/GUIDE_FOR_LIBRARY_AUTHORS.md index 77791dab6c..9d65929f7a 100644 --- a/guides/GUIDE_FOR_LIBRARY_AUTHORS.md +++ b/guides/GUIDE_FOR_LIBRARY_AUTHORS.md @@ -533,7 +533,7 @@ To render a search bar use `ScreenStackHeaderSearchBarView` with `` c - `placement` - Placement of the search bar in the navigation bar. (iOS only) - `allowToolbarIntegration` - Indicates whether the system can place the search bar among other toolbar items on iPhone. (iOS only) - `textColor` - The search field text color. -- `hintTextColor` - The search hint text color. (Android only) +- `hintTextColor` - The search hint text color. - `headerIconColor` - The search and close icon color shown in the header. (Android only) - `shouldShowHintSearchIcon` - Show the search hint icon when search bar is focused. (Android only) - `ref` - A React ref to imperatively modify search bar. From a94c5d830a0db05dddb48158eeb09099edb825e0 Mon Sep 17 00:00:00 2001 From: gaetano <64538010+skrtdev@users.noreply.github.com> Date: Mon, 25 May 2026 19:40:00 +0200 Subject: [PATCH 4/6] fix: update iOS search bar placeholder handling --- apps/src/tests/issue-tests/index.ts | 1 + ios/RNSScreenStackHeaderConfig.mm | 1 + ios/RNSSearchBar.h | 2 + ios/RNSSearchBar.mm | 74 ++++++++++++++++++++++++----- 4 files changed, 65 insertions(+), 13 deletions(-) diff --git a/apps/src/tests/issue-tests/index.ts b/apps/src/tests/issue-tests/index.ts index 1d9a125d5a..a104479f14 100644 --- a/apps/src/tests/issue-tests/index.ts +++ b/apps/src/tests/issue-tests/index.ts @@ -194,6 +194,7 @@ export { default as Test3885 } from './Test3885'; export { default as Test3910 } from './Test3910'; export { default as Test4027 } from './Test4027'; export { default as Test4064 } from './Test4064'; +export { default as Test4089 } from './Test4089'; export { default as TestScreenAnimation } from './TestScreenAnimation'; // The following test was meant to demo the "go back" gesture using Reanimated // but the associated PR in react-navigation is currently put on hold diff --git a/ios/RNSScreenStackHeaderConfig.mm b/ios/RNSScreenStackHeaderConfig.mm index bda3a4f3bd..af0331ca48 100644 --- a/ios/RNSScreenStackHeaderConfig.mm +++ b/ios/RNSScreenStackHeaderConfig.mm @@ -614,6 +614,7 @@ + (void)updateViewController:(UIViewController *)vc } } #endif /* Check for iOS 26.0 */ + [searchBar updatePlaceholder]; #endif /* !TARGET_OS_TV */ } break; diff --git a/ios/RNSSearchBar.h b/ios/RNSSearchBar.h index ec72bcfde0..67ea13bd0b 100644 --- a/ios/RNSSearchBar.h +++ b/ios/RNSSearchBar.h @@ -26,6 +26,8 @@ @property (nonatomic, retain) UISearchController *controller; +- (void)updatePlaceholder; + #if RNS_IPHONE_OS_VERSION_AVAILABLE(16_0) && !TARGET_OS_TV - (UINavigationItemSearchBarPlacement)placementAsUINavigationItemSearchBarPlacement API_AVAILABLE(ios(16.0)) API_UNAVAILABLE(tvos, watchos); diff --git a/ios/RNSSearchBar.mm b/ios/RNSSearchBar.mm index 67af793c7f..d287faa21f 100644 --- a/ios/RNSSearchBar.mm +++ b/ios/RNSSearchBar.mm @@ -20,12 +20,19 @@ namespace react = facebook::react; #endif // RCT_NEW_ARCH_ENABLED +@interface RNSSearchBar () +- (void)setPlaceholder:(NSString *)placeholder; +- (void)setHintTextColor:(UIColor *)hintTextColor; +@end + @implementation RNSSearchBar { __weak RCTBridge *_bridge; UISearchController *_controller; UIColor *_textColor; UIColor *_hintTextColor; NSString *_placeholder; + NSString *_defaultPlaceholder; + BOOL _didSetPlaceholder; // We use those booleans to log a warning if user attempts to restore // default behavior after setting explicit value for the prop. @@ -68,6 +75,17 @@ - (void)initCommonProps _controller.searchBar.delegate = self; + _didSetPlaceholder = NO; + +#if !TARGET_OS_TV + _defaultPlaceholder = _controller.searchBar.placeholder; + if (_defaultPlaceholder == nil) { + _defaultPlaceholder = _controller.searchBar.searchTextField.placeholder; + } +#else + _defaultPlaceholder = _controller.searchBar.placeholder; +#endif + _isObscureBackgroundSet = NO; _isHideNavigationBarSet = NO; @@ -207,13 +225,33 @@ - (void)setAutoCapitalize:(UITextAutocapitalizationType)autoCapitalize - (void)setPlaceholder:(NSString *)placeholder { _placeholder = placeholder; + _didSetPlaceholder = YES; +} - if (_hintTextColor != nil && _placeholder != nil) { +- (void)setHintTextColor:(UIColor *)hintTextColor +{ + _hintTextColor = hintTextColor; +} + +- (void)updatePlaceholder +{ + NSString *placeholder = _placeholder != nil ? _placeholder : _defaultPlaceholder; + +#if !TARGET_OS_TV + if (_hintTextColor != nil && placeholder != nil) { _controller.searchBar.searchTextField.attributedPlaceholder = - [[NSAttributedString alloc] initWithString:_placeholder attributes:@{NSForegroundColorAttributeName : _hintTextColor}]; + [[NSAttributedString alloc] initWithString:placeholder attributes:@{NSForegroundColorAttributeName : _hintTextColor}]; } else { - [_controller.searchBar setPlaceholder:_placeholder]; + _controller.searchBar.searchTextField.attributedPlaceholder = nil; + if (placeholder != nil || _didSetPlaceholder) { + [_controller.searchBar setPlaceholder:placeholder]; + } + } +#else + if (placeholder != nil || _didSetPlaceholder) { + [_controller.searchBar setPlaceholder:placeholder]; } +#endif } - (void)setBarTintColor:(UIColor *)barTintColor @@ -236,14 +274,6 @@ - (void)setTextColor:(UIColor *)textColor #endif } -- (void)setHintTextColor:(UIColor *)hintTextColor -{ -#if !TARGET_OS_TV - _hintTextColor = hintTextColor; - [self setPlaceholder:_placeholder]; -#endif -} - - (void)setCancelButtonText:(NSString *)text { [_controller.searchBar setValue:text forKey:@"cancelButtonText"]; @@ -424,8 +454,11 @@ - (void)updateProps:(react::Props::Shared const &)props oldProps:(react::Props:: RNSOptionalBooleanFromRNSSearchBarHideNavigationBar:newScreenProps.hideNavigationBar]]; } + BOOL shouldUpdatePlaceholder = NO; + if (oldScreenProps.placeholder != newScreenProps.placeholder) { [self setPlaceholder:RCTNSStringFromStringNilIfEmpty(newScreenProps.placeholder)]; + shouldUpdatePlaceholder = YES; } #if !TARGET_OS_VISION @@ -448,6 +481,11 @@ - (void)updateProps:(react::Props::Shared const &)props oldProps:(react::Props:: if (oldScreenProps.hintTextColor != newScreenProps.hintTextColor) { [self setHintTextColor:RCTUIColorFromSharedColor(newScreenProps.hintTextColor)]; + shouldUpdatePlaceholder = YES; + } + + if (shouldUpdatePlaceholder) { + [self updatePlaceholder]; } if (oldScreenProps.placement != newScreenProps.placement) { @@ -529,11 +567,21 @@ - (UIView *)view } } -RCT_EXPORT_VIEW_PROPERTY(placeholder, NSString) +RCT_CUSTOM_VIEW_PROPERTY(placeholder, NSString, RNSSearchBar) +{ + RNSSearchBar *searchBarView = static_cast(view); + [searchBarView setPlaceholder:[RCTConvert NSString:json]]; + [searchBarView updatePlaceholder]; +} RCT_EXPORT_VIEW_PROPERTY(barTintColor, UIColor) RCT_EXPORT_VIEW_PROPERTY(tintColor, UIColor) RCT_EXPORT_VIEW_PROPERTY(textColor, UIColor) -RCT_EXPORT_VIEW_PROPERTY(hintTextColor, UIColor) +RCT_CUSTOM_VIEW_PROPERTY(hintTextColor, UIColor, RNSSearchBar) +{ + RNSSearchBar *searchBarView = static_cast(view); + [searchBarView setHintTextColor:[RCTConvert UIColor:json]]; + [searchBarView updatePlaceholder]; +} RCT_EXPORT_VIEW_PROPERTY(cancelButtonText, NSString) RCT_EXPORT_VIEW_PROPERTY(placement, RNSSearchBarPlacement) RCT_EXPORT_VIEW_PROPERTY(allowToolbarIntegration, BOOL) From dc6aced8df526438631cfc3a617b642e25c6763d Mon Sep 17 00:00:00 2001 From: gaetano <64538010+skrtdev@users.noreply.github.com> Date: Mon, 25 May 2026 19:40:23 +0200 Subject: [PATCH 5/6] Add tests --- apps/src/tests/issue-tests/Test4089.tsx | 198 ++++++++++++++++++++++++ 1 file changed, 198 insertions(+) create mode 100644 apps/src/tests/issue-tests/Test4089.tsx diff --git a/apps/src/tests/issue-tests/Test4089.tsx b/apps/src/tests/issue-tests/Test4089.tsx new file mode 100644 index 0000000000..726d9d9ff3 --- /dev/null +++ b/apps/src/tests/issue-tests/Test4089.tsx @@ -0,0 +1,198 @@ +import { NavigationContainer } from '@react-navigation/native'; +import { createNativeStackNavigator } from '@react-navigation/native-stack'; +import type { NativeStackScreenProps } from '@react-navigation/native-stack'; +import React, { useLayoutEffect, useState } from 'react'; +import { Button, ScrollView, StyleSheet, Text, View } from 'react-native'; +import type { SearchBarPlacement, SearchBarProps } from 'react-native-screens'; + +type StackParamList = { + Home: undefined; + SearchBar: undefined; +}; + +type PlaceholderMode = 'default' | 'custom' | 'empty'; +type HintColorMode = 'default' | 'red' | 'blue'; + +type SearchBarConfig = { + placement: SearchBarPlacement; + placeholderMode: PlaceholderMode; + hintColorMode: HintColorMode; + allowToolbarIntegration: boolean; +}; + +const Stack = createNativeStackNavigator(); + +const placements: SearchBarPlacement[] = [ + 'automatic', + 'inline', + 'stacked', + 'integrated', + 'integratedButton', + 'integratedCentered', +]; + +const placeholderModes: PlaceholderMode[] = ['default', 'custom', 'empty']; +const hintColorModes: HintColorMode[] = ['default', 'red', 'blue']; + +const defaultConfig: SearchBarConfig = { + placement: 'automatic', + placeholderMode: 'default', + hintColorMode: 'default', + allowToolbarIntegration: true, +}; + +function getNextValue(values: readonly T[], currentValue: T): T { + const currentIndex = values.indexOf(currentValue); + return values[(currentIndex + 1) % values.length]; +} + +function getPlaceholder(mode: PlaceholderMode): SearchBarProps['placeholder'] { + switch (mode) { + case 'custom': + return 'Custom placeholder'; + case 'empty': + return ''; + default: + return undefined; + } +} + +function getHintTextColor( + mode: HintColorMode, +): SearchBarProps['hintTextColor'] { + switch (mode) { + case 'red': + return 'red'; + case 'blue': + return 'blue'; + default: + return undefined; + } +} + +function Home({ navigation }: NativeStackScreenProps) { + return ( + + Test4089 +