1#import <UIKit/UIKit.h> 2 3#import "RNSSearchBar.h" 4 5#import <React/RCTBridge.h> 6#import <React/RCTComponent.h> 7#import <React/RCTUIManager.h> 8 9#ifdef RCT_NEW_ARCH_ENABLED 10#import <React/RCTConversions.h> 11#import <React/RCTFabricComponentsPlugins.h> 12#import <react/renderer/components/rnscreens/ComponentDescriptors.h> 13#import <react/renderer/components/rnscreens/EventEmitters.h> 14#import <react/renderer/components/rnscreens/Props.h> 15#import "RNSConvert.h" 16#endif 17 18@implementation RNSSearchBar { 19 __weak RCTBridge *_bridge; 20 UISearchController *_controller; 21 UIColor *_textColor; 22} 23 24@synthesize controller = _controller; 25 26- (instancetype)initWithBridge:(RCTBridge *)bridge 27{ 28 if (self = [super init]) { 29 _bridge = bridge; 30 [self initCommonProps]; 31 } 32 return self; 33} 34 35#ifdef RCT_NEW_ARCH_ENABLED 36- (instancetype)init 37{ 38 if (self = [super init]) { 39 static const auto defaultProps = std::make_shared<const facebook::react::RNSSearchBarProps>(); 40 _props = defaultProps; 41 [self initCommonProps]; 42 } 43 return self; 44} 45#endif 46 47- (void)initCommonProps 48{ 49 _controller = [[UISearchController alloc] initWithSearchResultsController:nil]; 50 _controller.searchBar.delegate = self; 51 _hideWhenScrolling = YES; 52} 53 54- (void)emitOnFocusEvent 55{ 56#ifdef RCT_NEW_ARCH_ENABLED 57 if (_eventEmitter != nullptr) { 58 std::dynamic_pointer_cast<const facebook::react::RNSSearchBarEventEmitter>(_eventEmitter) 59 ->onFocus(facebook::react::RNSSearchBarEventEmitter::OnFocus{}); 60 } 61#else 62 if (self.onFocus) { 63 self.onFocus(@{}); 64 } 65#endif 66} 67 68- (void)emitOnBlurEvent 69{ 70#ifdef RCT_NEW_ARCH_ENABLED 71 if (_eventEmitter != nullptr) { 72 std::dynamic_pointer_cast<const facebook::react::RNSSearchBarEventEmitter>(_eventEmitter) 73 ->onBlur(facebook::react::RNSSearchBarEventEmitter::OnBlur{}); 74 } 75#else 76 if (self.onBlur) { 77 self.onBlur(@{}); 78 } 79#endif 80} 81 82- (void)emitOnSearchButtonPressEventWithText:(NSString *)text 83{ 84#ifdef RCT_NEW_ARCH_ENABLED 85 if (_eventEmitter != nullptr) { 86 std::dynamic_pointer_cast<const facebook::react::RNSSearchBarEventEmitter>(_eventEmitter) 87 ->onSearchButtonPress( 88 facebook::react::RNSSearchBarEventEmitter::OnSearchButtonPress{.text = RCTStringFromNSString(text)}); 89 } 90#else 91 if (self.onSearchButtonPress) { 92 self.onSearchButtonPress(@{ 93 @"text" : text, 94 }); 95 } 96#endif 97} 98 99- (void)emitOnCancelButtonPressEvent 100{ 101#ifdef RCT_NEW_ARCH_ENABLED 102 if (_eventEmitter != nullptr) { 103 std::dynamic_pointer_cast<const facebook::react::RNSSearchBarEventEmitter>(_eventEmitter) 104 ->onCancelButtonPress(facebook::react::RNSSearchBarEventEmitter::OnCancelButtonPress{}); 105 } 106#else 107 if (self.onCancelButtonPress) { 108 self.onCancelButtonPress(@{}); 109 } 110#endif 111} 112 113- (void)emitOnChangeTextEventWithText:(NSString *)text 114{ 115#ifdef RCT_NEW_ARCH_ENABLED 116 if (_eventEmitter != nullptr) { 117 std::dynamic_pointer_cast<const facebook::react::RNSSearchBarEventEmitter>(_eventEmitter) 118 ->onChangeText(facebook::react::RNSSearchBarEventEmitter::OnChangeText{.text = RCTStringFromNSString(text)}); 119 } 120#else 121 if (self.onChangeText) { 122 self.onChangeText(@{ 123 @"text" : text, 124 }); 125 } 126#endif 127} 128 129- (void)setObscureBackground:(BOOL)obscureBackground 130{ 131 if (@available(iOS 9.1, *)) { 132 [_controller setObscuresBackgroundDuringPresentation:obscureBackground]; 133 } 134} 135 136- (void)setHideNavigationBar:(BOOL)hideNavigationBar 137{ 138 [_controller setHidesNavigationBarDuringPresentation:hideNavigationBar]; 139} 140 141- (void)setHideWhenScrolling:(BOOL)hideWhenScrolling 142{ 143 _hideWhenScrolling = hideWhenScrolling; 144} 145 146- (void)setAutoCapitalize:(UITextAutocapitalizationType)autoCapitalize 147{ 148 [_controller.searchBar setAutocapitalizationType:autoCapitalize]; 149} 150 151- (void)setPlaceholder:(NSString *)placeholder 152{ 153 [_controller.searchBar setPlaceholder:placeholder]; 154} 155 156- (void)setBarTintColor:(UIColor *)barTintColor 157{ 158#if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && defined(__IPHONE_13_0) && \ 159 __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0 && !TARGET_OS_TV 160 if (@available(iOS 13.0, *)) { 161 [_controller.searchBar.searchTextField setBackgroundColor:barTintColor]; 162 } 163#endif 164} 165 166- (void)setTintColor:(UIColor *)tintColor 167{ 168 [_controller.searchBar setTintColor:tintColor]; 169} 170 171- (void)setTextColor:(UIColor *)textColor 172{ 173#if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && defined(__IPHONE_13_0) && \ 174 __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0 && !TARGET_OS_TV 175 _textColor = textColor; 176 if (@available(iOS 13.0, *)) { 177 [_controller.searchBar.searchTextField setTextColor:_textColor]; 178 } 179#endif 180} 181 182- (void)setCancelButtonText:(NSString *)text 183{ 184 [_controller.searchBar setValue:text forKey:@"cancelButtonText"]; 185} 186 187- (void)hideCancelButton 188{ 189#if !TARGET_OS_TV 190 if (@available(iOS 13, *)) { 191 // On iOS 13+ UISearchController automatically shows/hides cancel button 192 // https://developer.apple.com/documentation/uikit/uisearchcontroller/3152926-automaticallyshowscancelbutton?language=objc 193 } else { 194 [_controller.searchBar setShowsCancelButton:NO animated:YES]; 195 } 196#endif 197} 198 199- (void)showCancelButton 200{ 201#if !TARGET_OS_TV 202 if (@available(iOS 13, *)) { 203 // On iOS 13+ UISearchController automatically shows/hides cancel button 204 // https://developer.apple.com/documentation/uikit/uisearchcontroller/3152926-automaticallyshowscancelbutton?language=objc 205 } else { 206 [_controller.searchBar setShowsCancelButton:YES animated:YES]; 207 } 208#endif 209} 210 211#pragma mark delegate methods 212 213- (void)searchBarTextDidBeginEditing:(UISearchBar *)searchBar 214{ 215#if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && defined(__IPHONE_13_0) && \ 216 __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0 && !TARGET_OS_TV 217 if (@available(iOS 13.0, *)) { 218 // for some reason, the color does not change when set at the beginning, 219 // so we apply it again here 220 if (_textColor != nil) { 221 [_controller.searchBar.searchTextField setTextColor:_textColor]; 222 } 223 } 224#endif 225 226 [self showCancelButton]; 227 [self becomeFirstResponder]; 228 [self emitOnFocusEvent]; 229} 230 231- (void)searchBarTextDidEndEditing:(UISearchBar *)searchBar 232{ 233 [self emitOnBlurEvent]; 234 [self hideCancelButton]; 235} 236 237- (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText 238{ 239 [self emitOnChangeTextEventWithText:_controller.searchBar.text]; 240} 241 242- (void)searchBarSearchButtonClicked:(UISearchBar *)searchBar 243{ 244 [self emitOnSearchButtonPressEventWithText:_controller.searchBar.text]; 245} 246 247#if !TARGET_OS_TV 248- (void)searchBarCancelButtonClicked:(UISearchBar *)searchBar 249{ 250 _controller.searchBar.text = @""; 251 [self resignFirstResponder]; 252 [self hideCancelButton]; 253 254 [self emitOnCancelButtonPressEvent]; 255 [self emitOnChangeTextEventWithText:_controller.searchBar.text]; 256} 257#endif // !TARGET_OS_TV 258 259- (void)blur 260{ 261 [_controller.searchBar resignFirstResponder]; 262} 263 264- (void)focus 265{ 266 [_controller.searchBar becomeFirstResponder]; 267} 268 269- (void)clearText 270{ 271 [_controller.searchBar setText:@""]; 272} 273 274- (void)toggleCancelButton:(BOOL)flag 275{ 276#if !TARGET_OS_TV 277 [_controller.searchBar setShowsCancelButton:flag animated:YES]; 278#endif 279} 280 281- (void)setText:(NSString *)text 282{ 283 [_controller.searchBar setText:text]; 284} 285 286#pragma mark-- Fabric specific 287 288#ifdef RCT_NEW_ARCH_ENABLED 289- (void)updateProps:(facebook::react::Props::Shared const &)props 290 oldProps:(facebook::react::Props::Shared const &)oldProps 291{ 292 const auto &oldScreenProps = *std::static_pointer_cast<const facebook::react::RNSSearchBarProps>(_props); 293 const auto &newScreenProps = *std::static_pointer_cast<const facebook::react::RNSSearchBarProps>(props); 294 295 [self setHideWhenScrolling:newScreenProps.hideWhenScrolling]; 296 297 if (oldScreenProps.cancelButtonText != newScreenProps.cancelButtonText) { 298 [self setCancelButtonText:RCTNSStringFromStringNilIfEmpty(newScreenProps.cancelButtonText)]; 299 } 300 301 if (oldScreenProps.obscureBackground != newScreenProps.obscureBackground) { 302 [self setObscureBackground:newScreenProps.obscureBackground]; 303 } 304 305 if (oldScreenProps.hideNavigationBar != newScreenProps.hideNavigationBar) { 306 [self setHideNavigationBar:newScreenProps.hideNavigationBar]; 307 } 308 309 if (oldScreenProps.placeholder != newScreenProps.placeholder) { 310 [self setPlaceholder:RCTNSStringFromStringNilIfEmpty(newScreenProps.placeholder)]; 311 } 312 313 if (oldScreenProps.autoCapitalize != newScreenProps.autoCapitalize) { 314 [self setAutoCapitalize:[RNSConvert UITextAutocapitalizationTypeFromCppEquivalent:newScreenProps.autoCapitalize]]; 315 } 316 317 if (oldScreenProps.tintColor != newScreenProps.tintColor) { 318 [self setTintColor:RCTUIColorFromSharedColor(newScreenProps.tintColor)]; 319 } 320 321 if (oldScreenProps.barTintColor != newScreenProps.barTintColor) { 322 [self setBarTintColor:RCTUIColorFromSharedColor(newScreenProps.barTintColor)]; 323 } 324 325 if (oldScreenProps.textColor != newScreenProps.textColor) { 326 [self setTextColor:RCTUIColorFromSharedColor(newScreenProps.textColor)]; 327 } 328 329 [super updateProps:props oldProps:oldProps]; 330} 331 332+ (facebook::react::ComponentDescriptorProvider)componentDescriptorProvider 333{ 334 return facebook::react::concreteComponentDescriptorProvider<facebook::react::RNSSearchBarComponentDescriptor>(); 335} 336 337- (void)handleCommand:(const NSString *)commandName args:(const NSArray *)args 338{ 339 RCTRNSSearchBarHandleCommand(self, commandName, args); 340} 341 342#else 343#endif // RCT_NEW_ARCH_ENABLED 344 345@end 346 347#ifdef RCT_NEW_ARCH_ENABLED 348Class<RCTComponentViewProtocol> RNSSearchBarCls(void) 349{ 350 return RNSSearchBar.class; 351} 352#endif 353 354@implementation RNSSearchBarManager 355 356RCT_EXPORT_MODULE() 357 358#ifdef RCT_NEW_ARCH_ENABLED 359#else 360- (UIView *)view 361{ 362 return [[RNSSearchBar alloc] initWithBridge:self.bridge]; 363} 364#endif 365 366RCT_EXPORT_VIEW_PROPERTY(obscureBackground, BOOL) 367RCT_EXPORT_VIEW_PROPERTY(hideNavigationBar, BOOL) 368RCT_EXPORT_VIEW_PROPERTY(hideWhenScrolling, BOOL) 369RCT_EXPORT_VIEW_PROPERTY(autoCapitalize, UITextAutocapitalizationType) 370RCT_EXPORT_VIEW_PROPERTY(placeholder, NSString) 371RCT_EXPORT_VIEW_PROPERTY(barTintColor, UIColor) 372RCT_EXPORT_VIEW_PROPERTY(tintColor, UIColor) 373RCT_EXPORT_VIEW_PROPERTY(textColor, UIColor) 374RCT_EXPORT_VIEW_PROPERTY(cancelButtonText, NSString) 375 376RCT_EXPORT_VIEW_PROPERTY(onChangeText, RCTBubblingEventBlock) 377RCT_EXPORT_VIEW_PROPERTY(onCancelButtonPress, RCTBubblingEventBlock) 378RCT_EXPORT_VIEW_PROPERTY(onSearchButtonPress, RCTBubblingEventBlock) 379RCT_EXPORT_VIEW_PROPERTY(onFocus, RCTBubblingEventBlock) 380RCT_EXPORT_VIEW_PROPERTY(onBlur, RCTBubblingEventBlock) 381 382#ifndef RCT_NEW_ARCH_ENABLED 383 384RCT_EXPORT_METHOD(focus : (NSNumber *_Nonnull)reactTag) 385{ 386 [self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary *viewRegistry) { 387 RNSSearchBar *searchBar = viewRegistry[reactTag]; 388 [searchBar focus]; 389 }]; 390} 391 392RCT_EXPORT_METHOD(blur : (NSNumber *_Nonnull)reactTag) 393{ 394 [self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary *viewRegistry) { 395 RNSSearchBar *searchBar = viewRegistry[reactTag]; 396 [searchBar blur]; 397 }]; 398} 399 400RCT_EXPORT_METHOD(clearText : (NSNumber *_Nonnull)reactTag) 401{ 402 [self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary *viewRegistry) { 403 RNSSearchBar *searchBar = viewRegistry[reactTag]; 404 [searchBar clearText]; 405 }]; 406} 407 408RCT_EXPORT_METHOD(toggleCancelButton : (NSNumber *_Nonnull)reactTag flag : (BOOL *)flag) 409{ 410 [self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary *viewRegistry) { 411 RNSSearchBar *searchBar = viewRegistry[reactTag]; 412 [searchBar toggleCancelButton:flag]; 413 }]; 414} 415 416RCT_EXPORT_METHOD(setText : (NSNumber *_Nonnull)reactTag text : (NSString *)text) 417{ 418 [self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary *viewRegistry) { 419 RNSSearchBar *searchBar = viewRegistry[reactTag]; 420 [searchBar setText:text]; 421 }]; 422} 423 424#endif /* !RCT_NEW_ARCH_ENABLED */ 425 426@end 427