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
|
19 | namespace 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 |
|
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 |
|
203 |
|
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 |
|
215 |
|
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 |
|
246 |
|
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
|
388 | Class<RCTComponentViewProtocol> RNSSearchBarCls(void)
|
389 | {
|
390 | return RNSSearchBar.class;
|
391 | }
|
392 | #endif
|
393 |
|
394 | @implementation RNSSearchBarManager
|
395 |
|
396 | RCT_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 |
|
406 | RCT_EXPORT_VIEW_PROPERTY(obscureBackground, BOOL)
|
407 | RCT_EXPORT_VIEW_PROPERTY(hideNavigationBar, BOOL)
|
408 | RCT_EXPORT_VIEW_PROPERTY(hideWhenScrolling, BOOL)
|
409 | RCT_EXPORT_VIEW_PROPERTY(autoCapitalize, UITextAutocapitalizationType)
|
410 | RCT_EXPORT_VIEW_PROPERTY(placeholder, NSString)
|
411 | RCT_EXPORT_VIEW_PROPERTY(barTintColor, UIColor)
|
412 | RCT_EXPORT_VIEW_PROPERTY(tintColor, UIColor)
|
413 | RCT_EXPORT_VIEW_PROPERTY(textColor, UIColor)
|
414 | RCT_EXPORT_VIEW_PROPERTY(cancelButtonText, NSString)
|
415 | RCT_EXPORT_VIEW_PROPERTY(placement, RNSSearchBarPlacement)
|
416 |
|
417 | RCT_EXPORT_VIEW_PROPERTY(onChangeText, RCTDirectEventBlock)
|
418 | RCT_EXPORT_VIEW_PROPERTY(onCancelButtonPress, RCTDirectEventBlock)
|
419 | RCT_EXPORT_VIEW_PROPERTY(onSearchButtonPress, RCTDirectEventBlock)
|
420 | RCT_EXPORT_VIEW_PROPERTY(onSearchFocus, RCTDirectEventBlock)
|
421 | RCT_EXPORT_VIEW_PROPERTY(onSearchBlur, RCTDirectEventBlock)
|
422 |
|
423 | #ifndef RCT_NEW_ARCH_ENABLED
|
424 |
|
425 | RCT_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 |
|
433 | RCT_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 |
|
441 | RCT_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 |
|
449 | RCT_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 |
|
457 | RCT_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 |
|
465 | RCT_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 |
|
479 | RCT_ENUM_CONVERTER(
|
480 | RNSSearchBarPlacement,
|
481 | (@{
|
482 | @"automatic" : @(RNSSearchBarPlacementAutomatic),
|
483 | @"inline" : @(RNSSearchBarPlacementInline),
|
484 | @"stacked" : @(RNSSearchBarPlacementStacked),
|
485 | }),
|
486 | RNSSearchBarPlacementStacked,
|
487 | integerValue)
|
488 |
|
489 | @end
|