UNPKG

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