1 | #ifdef RCT_NEW_ARCH_ENABLED
|
2 | #import <React/RCTConversions.h>
|
3 | #import <React/RCTFabricComponentsPlugins.h>
|
4 | #import <React/RCTImageComponentView.h>
|
5 | #import <React/UIView+React.h>
|
6 | #import <react/renderer/components/image/ImageProps.h>
|
7 | #import <react/renderer/components/rnscreens/ComponentDescriptors.h>
|
8 | #import <react/renderer/components/rnscreens/EventEmitters.h>
|
9 | #import <react/renderer/components/rnscreens/Props.h>
|
10 | #import <react/renderer/components/rnscreens/RCTComponentViewHelpers.h>
|
11 | #import "RCTImageComponentView+RNSScreenStackHeaderConfig.h"
|
12 | #else
|
13 | #import <React/RCTImageView.h>
|
14 | #import <React/RCTShadowView.h>
|
15 | #import <React/RCTUIManager.h>
|
16 | #import <React/RCTUIManagerUtils.h>
|
17 | #endif
|
18 | #import <React/RCTBridge.h>
|
19 | #import <React/RCTFont.h>
|
20 | #import <React/RCTImageLoader.h>
|
21 | #import <React/RCTImageSource.h>
|
22 | #import "RNSConvert.h"
|
23 | #import "RNSScreen.h"
|
24 | #import "RNSScreenStackHeaderConfig.h"
|
25 | #import "RNSSearchBar.h"
|
26 | #import "RNSUIBarButtonItem.h"
|
27 |
|
28 | #ifdef RCT_NEW_ARCH_ENABLED
|
29 | namespace react = facebook::react;
|
30 | #endif // RCT_NEW_ARCH_ENABLED
|
31 |
|
32 | #ifndef RCT_NEW_ARCH_ENABLED
|
33 |
|
34 |
|
35 | @interface RCTImageView (Private)
|
36 | - (UIImage *)image;
|
37 | @end
|
38 | #endif // !RCT_NEW_ARCH_ENABLED
|
39 |
|
40 | @interface RCTImageLoader (Private)
|
41 | - (id<RCTImageCache>)imageCache;
|
42 | @end
|
43 |
|
44 | @implementation NSString (RNSStringUtil)
|
45 |
|
46 | + (BOOL)RNSisBlank:(NSString *)string
|
47 | {
|
48 | if (string == nil) {
|
49 | return YES;
|
50 | }
|
51 | return [[string stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] length] == 0;
|
52 | }
|
53 |
|
54 | @end
|
55 |
|
56 | @implementation RNSScreenStackHeaderConfig {
|
57 | NSMutableArray<RNSScreenStackHeaderSubview *> *_reactSubviews;
|
58 | #ifdef RCT_NEW_ARCH_ENABLED
|
59 | BOOL _initialPropsSet;
|
60 | #else
|
61 | #endif
|
62 | }
|
63 |
|
64 | #ifdef RCT_NEW_ARCH_ENABLED
|
65 | - (instancetype)initWithFrame:(CGRect)frame
|
66 | {
|
67 | if (self = [super initWithFrame:frame]) {
|
68 | static const auto defaultProps = std::make_shared<const react::RNSScreenStackHeaderConfigProps>();
|
69 | _props = defaultProps;
|
70 | _show = YES;
|
71 | _translucent = NO;
|
72 | [self initProps];
|
73 | }
|
74 | return self;
|
75 | }
|
76 | #else
|
77 | - (instancetype)init
|
78 | {
|
79 | if (self = [super init]) {
|
80 | _translucent = YES;
|
81 | [self initProps];
|
82 | }
|
83 | return self;
|
84 | }
|
85 | #endif
|
86 |
|
87 | - (void)initProps
|
88 | {
|
89 | self.hidden = YES;
|
90 | _reactSubviews = [NSMutableArray new];
|
91 | _backTitleVisible = YES;
|
92 | }
|
93 |
|
94 | - (UIView *)reactSuperview
|
95 | {
|
96 | return _screenView;
|
97 | }
|
98 |
|
99 | - (NSArray<UIView *> *)reactSubviews
|
100 | {
|
101 | return _reactSubviews;
|
102 | }
|
103 |
|
104 | - (void)removeFromSuperview
|
105 | {
|
106 | [super removeFromSuperview];
|
107 | _screenView = nil;
|
108 | }
|
109 |
|
110 |
|
111 |
|
112 | - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
|
113 | {
|
114 | for (RNSScreenStackHeaderSubview *subview in _reactSubviews) {
|
115 | if (subview.type == RNSScreenStackHeaderSubviewTypeLeft || subview.type == RNSScreenStackHeaderSubviewTypeRight) {
|
116 |
|
117 |
|
118 | UIView *headerComponent = subview.subviews.firstObject;
|
119 |
|
120 | CGPoint convertedPoint = [_screenView.reactSuperview convertPoint:point toView:headerComponent];
|
121 |
|
122 | UIView *hitTestResult = [headerComponent hitTest:convertedPoint withEvent:event];
|
123 | if (hitTestResult != nil) {
|
124 | return hitTestResult;
|
125 | }
|
126 | }
|
127 | }
|
128 | return nil;
|
129 | }
|
130 |
|
131 | - (void)updateViewControllerIfNeeded
|
132 | {
|
133 | UIViewController *vc = _screenView.controller;
|
134 | UINavigationController *nav = (UINavigationController *)vc.parentViewController;
|
135 | UIViewController *nextVC = nav.visibleViewController;
|
136 | if (nav.transitionCoordinator != nil) {
|
137 |
|
138 |
|
139 |
|
140 |
|
141 | nextVC = nav.topViewController;
|
142 | }
|
143 |
|
144 |
|
145 | BOOL isPresentingVC = nextVC != nil && vc.presentedViewController == nextVC;
|
146 |
|
147 | BOOL isInFullScreenModal = nav == nil && _screenView.stackPresentation == RNSScreenStackPresentationFullScreenModal;
|
148 |
|
149 | if (vc != nil && (nextVC == vc || isInFullScreenModal || isPresentingVC)) {
|
150 | [RNSScreenStackHeaderConfig updateViewController:self.screenView.controller withConfig:self animated:YES];
|
151 |
|
152 |
|
153 | [self.screenView.controller calculateAndNotifyHeaderHeightChangeIsModal:NO];
|
154 | }
|
155 | }
|
156 |
|
157 | - (void)layoutNavigationControllerView
|
158 | {
|
159 |
|
160 |
|
161 |
|
162 | UIViewController *vc = _screenView.controller;
|
163 | UINavigationController *navctr = vc.navigationController;
|
164 | [navctr.view setNeedsLayout];
|
165 | }
|
166 |
|
167 | + (void)setAnimatedConfig:(UIViewController *)vc withConfig:(RNSScreenStackHeaderConfig *)config
|
168 | {
|
169 | UINavigationBar *navbar = ((UINavigationController *)vc.parentViewController).navigationBar;
|
170 |
|
171 |
|
172 |
|
173 |
|
174 |
|
175 | #if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && defined(__IPHONE_14_0) && \
|
176 | __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_14_0
|
177 |
|
178 |
|
179 | if (@available(iOS 14.0, *)) {
|
180 | } else
|
181 | #endif
|
182 | {
|
183 | [navbar setTintColor:[config.color colorWithAlphaComponent:CGColorGetAlpha(config.color.CGColor) - 0.01]];
|
184 | }
|
185 | [navbar setTintColor:config.color];
|
186 |
|
187 | #if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && defined(__IPHONE_13_0) && \
|
188 | __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0
|
189 | if (@available(iOS 13.0, *)) {
|
190 |
|
191 | } else
|
192 | #endif
|
193 | {
|
194 | BOOL hideShadow = config.hideShadow;
|
195 |
|
196 | if (config.backgroundColor && CGColorGetAlpha(config.backgroundColor.CGColor) == 0.) {
|
197 | [navbar setBackgroundImage:[UIImage new] forBarMetrics:UIBarMetricsDefault];
|
198 | [navbar setBarTintColor:[UIColor clearColor]];
|
199 | hideShadow = YES;
|
200 | } else {
|
201 | [navbar setBackgroundImage:nil forBarMetrics:UIBarMetricsDefault];
|
202 | [navbar setBarTintColor:config.backgroundColor];
|
203 | }
|
204 | [navbar setTranslucent:config.translucent];
|
205 | [navbar setValue:@(hideShadow ? YES : NO) forKey:@"hidesShadow"];
|
206 |
|
207 | if (config.titleFontFamily || config.titleFontSize || config.titleFontWeight || config.titleColor) {
|
208 | NSMutableDictionary *attrs = [NSMutableDictionary new];
|
209 |
|
210 | if (config.titleColor) {
|
211 | attrs[NSForegroundColorAttributeName] = config.titleColor;
|
212 | }
|
213 |
|
214 | NSString *family = config.titleFontFamily ?: nil;
|
215 | NSNumber *size = config.titleFontSize ?: @17;
|
216 | NSString *weight = config.titleFontWeight ?: nil;
|
217 | if (family || weight) {
|
218 | attrs[NSFontAttributeName] = [RCTFont updateFont:nil
|
219 | withFamily:family
|
220 | size:size
|
221 | weight:weight
|
222 | style:nil
|
223 | variant:nil
|
224 | scaleMultiplier:1.0];
|
225 | } else {
|
226 | attrs[NSFontAttributeName] = [UIFont boldSystemFontOfSize:[size floatValue]];
|
227 | }
|
228 | [navbar setTitleTextAttributes:attrs];
|
229 | }
|
230 |
|
231 | #if !TARGET_OS_TV && !TARGET_OS_VISION
|
232 | if (@available(iOS 11.0, *)) {
|
233 | if (config.largeTitle &&
|
234 | (config.largeTitleFontFamily || config.largeTitleFontSize || config.largeTitleFontWeight ||
|
235 | config.largeTitleColor || config.titleColor)) {
|
236 | NSMutableDictionary *largeAttrs = [NSMutableDictionary new];
|
237 | if (config.largeTitleColor || config.titleColor) {
|
238 | largeAttrs[NSForegroundColorAttributeName] =
|
239 | config.largeTitleColor ? config.largeTitleColor : config.titleColor;
|
240 | }
|
241 | NSString *largeFamily = config.largeTitleFontFamily ?: nil;
|
242 | NSNumber *largeSize = config.largeTitleFontSize ?: @34;
|
243 | NSString *largeWeight = config.largeTitleFontWeight ?: nil;
|
244 | if (largeFamily || largeWeight) {
|
245 | largeAttrs[NSFontAttributeName] = [RCTFont updateFont:nil
|
246 | withFamily:largeFamily
|
247 | size:largeSize
|
248 | weight:largeWeight
|
249 | style:nil
|
250 | variant:nil
|
251 | scaleMultiplier:1.0];
|
252 | } else {
|
253 | largeAttrs[NSFontAttributeName] = [UIFont systemFontOfSize:[largeSize floatValue] weight:UIFontWeightBold];
|
254 | }
|
255 | [navbar setLargeTitleTextAttributes:largeAttrs];
|
256 | }
|
257 | }
|
258 | #endif
|
259 | }
|
260 | }
|
261 |
|
262 | + (void)setTitleAttibutes:(NSDictionary *)attrs forButton:(UIBarButtonItem *)button
|
263 | {
|
264 | [button setTitleTextAttributes:attrs forState:UIControlStateNormal];
|
265 | [button setTitleTextAttributes:attrs forState:UIControlStateHighlighted];
|
266 | [button setTitleTextAttributes:attrs forState:UIControlStateDisabled];
|
267 | [button setTitleTextAttributes:attrs forState:UIControlStateSelected];
|
268 | [button setTitleTextAttributes:attrs forState:UIControlStateFocused];
|
269 | }
|
270 |
|
271 | + (UIImage *)loadBackButtonImageInViewController:(UIViewController *)vc withConfig:(RNSScreenStackHeaderConfig *)config
|
272 | {
|
273 | BOOL hasBackButtonImage = NO;
|
274 | for (RNSScreenStackHeaderSubview *subview in config.reactSubviews) {
|
275 | if (subview.type == RNSScreenStackHeaderSubviewTypeBackButton && subview.subviews.count > 0) {
|
276 | hasBackButtonImage = YES;
|
277 | #ifdef RCT_NEW_ARCH_ENABLED
|
278 | RCTImageComponentView *imageView = subview.subviews[0];
|
279 | #else
|
280 | RCTImageView *imageView = subview.subviews[0];
|
281 | #endif // RCT_NEW_ARCH_ENABLED
|
282 | if (imageView.image == nil) {
|
283 |
|
284 |
|
285 |
|
286 |
|
287 |
|
288 |
|
289 |
|
290 | RCTImageSource *imageSource = [RNSScreenStackHeaderConfig imageSourceFromImageView:imageView];
|
291 | [imageView reactSetFrame:CGRectMake(
|
292 | imageView.frame.origin.x,
|
293 | imageView.frame.origin.y,
|
294 | imageSource.size.width,
|
295 | imageSource.size.height)];
|
296 | }
|
297 |
|
298 | UIImage *image = imageView.image;
|
299 |
|
300 |
|
301 |
|
302 |
|
303 |
|
304 |
|
305 |
|
306 |
|
307 |
|
308 |
|
309 |
|
310 | if (image == nil) {
|
311 |
|
312 |
|
313 | RCTImageSource *imageSource = [RNSScreenStackHeaderConfig imageSourceFromImageView:imageView];
|
314 | RCTImageLoader *imageLoader = [subview.bridge moduleForClass:[RCTImageLoader class]];
|
315 |
|
316 | image = [imageLoader.imageCache
|
317 | imageForUrl:imageSource.request.URL.absoluteString
|
318 | size:imageSource.size
|
319 | scale:imageSource.scale
|
320 | #ifdef RCT_NEW_ARCH_ENABLED
|
321 | resizeMode:resizeModeFromCppEquiv(
|
322 | std::static_pointer_cast<const react::ImageProps>(imageView.props)->resizeMode)];
|
323 | #else
|
324 | resizeMode:imageView.resizeMode];
|
325 | #endif // RCT_NEW_ARCH_ENABLED
|
326 | }
|
327 | if (image == nil) {
|
328 |
|
329 |
|
330 |
|
331 | if (vc.transitionCoordinator) {
|
332 | [vc.transitionCoordinator
|
333 | animateAlongsideTransition:^(id<UIViewControllerTransitionCoordinatorContext> _Nonnull context) {
|
334 |
|
335 | }
|
336 | completion:^(id<UIViewControllerTransitionCoordinatorContext> _Nonnull context) {
|
337 |
|
338 |
|
339 |
|
340 | #if !TARGET_OS_TV
|
341 | vc.navigationItem.hidesBackButton = YES;
|
342 | #endif
|
343 | [config updateViewControllerIfNeeded];
|
344 | }];
|
345 | }
|
346 | return [UIImage new];
|
347 | } else {
|
348 | return image;
|
349 | }
|
350 | }
|
351 | }
|
352 | return nil;
|
353 | }
|
354 |
|
355 | + (void)willShowViewController:(UIViewController *)vc
|
356 | animated:(BOOL)animated
|
357 | withConfig:(RNSScreenStackHeaderConfig *)config
|
358 | {
|
359 | [self updateViewController:vc withConfig:config animated:animated];
|
360 |
|
361 |
|
362 | if ([vc isKindOfClass:[RNSScreen class]]) {
|
363 | [(RNSScreen *)vc calculateAndNotifyHeaderHeightChangeIsModal:NO];
|
364 | }
|
365 | }
|
366 |
|
367 | #if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && defined(__IPHONE_13_0) && \
|
368 | __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0
|
369 | + (UINavigationBarAppearance *)buildAppearance:(UIViewController *)vc
|
370 | withConfig:(RNSScreenStackHeaderConfig *)config API_AVAILABLE(ios(13.0))
|
371 | {
|
372 | UINavigationBarAppearance *appearance = [UINavigationBarAppearance new];
|
373 |
|
374 | if (config.backgroundColor && CGColorGetAlpha(config.backgroundColor.CGColor) == 0.) {
|
375 |
|
376 | [appearance configureWithTransparentBackground];
|
377 | } else {
|
378 | [appearance configureWithOpaqueBackground];
|
379 | }
|
380 |
|
381 |
|
382 | if (config.backgroundColor) {
|
383 | appearance.backgroundColor = config.backgroundColor;
|
384 | }
|
385 |
|
386 |
|
387 | #ifdef RCT_NEW_ARCH_ENABLED
|
388 | #else
|
389 | if (config.blurEffect) {
|
390 | appearance.backgroundEffect = [UIBlurEffect effectWithStyle:config.blurEffect];
|
391 | }
|
392 | #endif
|
393 |
|
394 | if (config.hideShadow) {
|
395 | appearance.shadowColor = nil;
|
396 | }
|
397 |
|
398 | if (config.titleFontFamily || config.titleFontSize || config.titleFontWeight || config.titleColor) {
|
399 | NSMutableDictionary *attrs = [NSMutableDictionary new];
|
400 |
|
401 |
|
402 | #if !TARGET_OS_VISION
|
403 | if (config.titleColor) {
|
404 | attrs[NSForegroundColorAttributeName] = config.titleColor;
|
405 | }
|
406 | #endif
|
407 |
|
408 | NSString *family = config.titleFontFamily ?: nil;
|
409 | NSNumber *size = config.titleFontSize ?: @17;
|
410 | NSString *weight = config.titleFontWeight ?: nil;
|
411 | if (family || weight) {
|
412 | attrs[NSFontAttributeName] = [RCTFont updateFont:nil
|
413 | withFamily:config.titleFontFamily
|
414 | size:size
|
415 | weight:weight
|
416 | style:nil
|
417 | variant:nil
|
418 | scaleMultiplier:1.0];
|
419 | } else {
|
420 | attrs[NSFontAttributeName] = [UIFont boldSystemFontOfSize:[size floatValue]];
|
421 | }
|
422 | appearance.titleTextAttributes = attrs;
|
423 | }
|
424 |
|
425 | if (config.largeTitleFontFamily || config.largeTitleFontSize || config.largeTitleFontWeight ||
|
426 | config.largeTitleColor || config.titleColor) {
|
427 | NSMutableDictionary *largeAttrs = [NSMutableDictionary new];
|
428 |
|
429 |
|
430 | #if !TARGET_OS_VISION
|
431 | if (config.largeTitleColor || config.titleColor) {
|
432 | largeAttrs[NSForegroundColorAttributeName] = config.largeTitleColor ? config.largeTitleColor : config.titleColor;
|
433 | }
|
434 | #endif
|
435 |
|
436 | NSString *largeFamily = config.largeTitleFontFamily ?: nil;
|
437 | NSNumber *largeSize = config.largeTitleFontSize ?: @34;
|
438 | NSString *largeWeight = config.largeTitleFontWeight ?: nil;
|
439 | if (largeFamily || largeWeight) {
|
440 | largeAttrs[NSFontAttributeName] = [RCTFont updateFont:nil
|
441 | withFamily:largeFamily
|
442 | size:largeSize
|
443 | weight:largeWeight
|
444 | style:nil
|
445 | variant:nil
|
446 | scaleMultiplier:1.0];
|
447 | } else {
|
448 | largeAttrs[NSFontAttributeName] = [UIFont systemFontOfSize:[largeSize floatValue] weight:UIFontWeightBold];
|
449 | }
|
450 |
|
451 | appearance.largeTitleTextAttributes = largeAttrs;
|
452 | }
|
453 |
|
454 | UIImage *backButtonImage = [self loadBackButtonImageInViewController:vc withConfig:config];
|
455 | if (backButtonImage) {
|
456 | [appearance setBackIndicatorImage:backButtonImage transitionMaskImage:backButtonImage];
|
457 | } else if (appearance.backIndicatorImage) {
|
458 | [appearance setBackIndicatorImage:nil transitionMaskImage:nil];
|
459 | }
|
460 | return appearance;
|
461 | }
|
462 | #endif // Check for >= iOS 13.0
|
463 |
|
464 | + (void)updateViewController:(UIViewController *)vc
|
465 | withConfig:(RNSScreenStackHeaderConfig *)config
|
466 | animated:(BOOL)animated
|
467 | {
|
468 | UINavigationItem *navitem = vc.navigationItem;
|
469 | UINavigationController *navctr = (UINavigationController *)vc.parentViewController;
|
470 |
|
471 | NSUInteger currentIndex = [navctr.viewControllers indexOfObject:vc];
|
472 | UINavigationItem *prevItem =
|
473 | currentIndex > 0 ? [navctr.viewControllers objectAtIndex:currentIndex - 1].navigationItem : nil;
|
474 |
|
475 | BOOL wasHidden = navctr.navigationBarHidden;
|
476 | #ifdef RCT_NEW_ARCH_ENABLED
|
477 | BOOL shouldHide = config == nil || !config.show;
|
478 | #else
|
479 | BOOL shouldHide = config == nil || config.hide;
|
480 | #endif
|
481 |
|
482 | if (!shouldHide && !config.translucent) {
|
483 |
|
484 |
|
485 | vc.edgesForExtendedLayout = UIRectEdgeNone;
|
486 | } else {
|
487 |
|
488 | vc.edgesForExtendedLayout = UIRectEdgeAll;
|
489 | }
|
490 |
|
491 | [navctr setNavigationBarHidden:shouldHide animated:animated];
|
492 |
|
493 | if ((config.direction == UISemanticContentAttributeForceLeftToRight ||
|
494 | config.direction == UISemanticContentAttributeForceRightToLeft) &&
|
495 |
|
496 | navctr.view.semanticContentAttribute != config.direction) {
|
497 | navctr.view.semanticContentAttribute = config.direction;
|
498 | navctr.navigationBar.semanticContentAttribute = config.direction;
|
499 | }
|
500 |
|
501 | if (shouldHide) {
|
502 | navitem.title = config.title;
|
503 | return;
|
504 | }
|
505 |
|
506 | #if !TARGET_OS_TV
|
507 | const auto isBackTitleBlank = [NSString RNSisBlank:config.backTitle] == YES;
|
508 | NSString *resolvedBackTitle = isBackTitleBlank ? prevItem.title : config.backTitle;
|
509 | RNSUIBarButtonItem *backBarButtonItem = [[RNSUIBarButtonItem alloc] initWithTitle:resolvedBackTitle
|
510 | style:UIBarButtonItemStylePlain
|
511 | target:nil
|
512 | action:nil];
|
513 | [backBarButtonItem setMenuHidden:config.disableBackButtonMenu];
|
514 |
|
515 | auto isBackButtonCustomized = !isBackTitleBlank || config.disableBackButtonMenu;
|
516 |
|
517 | #if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && defined(__IPHONE_14_0) && \
|
518 | __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_14_0
|
519 | if (@available(iOS 14.0, *)) {
|
520 | prevItem.backButtonDisplayMode = config.backButtonDisplayMode;
|
521 | }
|
522 | #endif
|
523 |
|
524 | if (config.isBackTitleVisible) {
|
525 | if ((config.backTitleFontFamily &&
|
526 |
|
527 |
|
528 |
|
529 |
|
530 | ![config.backTitleFontFamily isEqual:@"System"]) ||
|
531 | config.backTitleFontSize) {
|
532 | isBackButtonCustomized = YES;
|
533 | NSMutableDictionary *attrs = [NSMutableDictionary new];
|
534 | NSNumber *size = config.backTitleFontSize ?: @17;
|
535 | if (config.backTitleFontFamily) {
|
536 | attrs[NSFontAttributeName] = [RCTFont updateFont:nil
|
537 | withFamily:config.backTitleFontFamily
|
538 | size:size
|
539 | weight:nil
|
540 | style:nil
|
541 | variant:nil
|
542 | scaleMultiplier:1.0];
|
543 | } else {
|
544 | attrs[NSFontAttributeName] = [UIFont boldSystemFontOfSize:[size floatValue]];
|
545 | }
|
546 | [self setTitleAttibutes:attrs forButton:backBarButtonItem];
|
547 | }
|
548 | } else {
|
549 |
|
550 |
|
551 |
|
552 |
|
553 |
|
554 | [backBarButtonItem setTitle:nil];
|
555 | isBackButtonCustomized = YES;
|
556 | prevItem.backButtonTitle = resolvedBackTitle;
|
557 | }
|
558 |
|
559 |
|
560 |
|
561 |
|
562 |
|
563 | if (isBackButtonCustomized) {
|
564 | prevItem.backBarButtonItem = backBarButtonItem;
|
565 | }
|
566 |
|
567 | if (@available(iOS 11.0, *)) {
|
568 | if (config.largeTitle) {
|
569 | navctr.navigationBar.prefersLargeTitles = YES;
|
570 | }
|
571 | navitem.largeTitleDisplayMode =
|
572 | config.largeTitle ? UINavigationItemLargeTitleDisplayModeAlways : UINavigationItemLargeTitleDisplayModeNever;
|
573 | }
|
574 | #endif
|
575 |
|
576 | #if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && defined(__IPHONE_13_0) && \
|
577 | __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0
|
578 | if (@available(iOS 13.0, tvOS 13.0, *)) {
|
579 | UINavigationBarAppearance *appearance = [self buildAppearance:vc withConfig:config];
|
580 | navitem.standardAppearance = appearance;
|
581 | navitem.compactAppearance = appearance;
|
582 |
|
583 | UINavigationBarAppearance *scrollEdgeAppearance =
|
584 | [[UINavigationBarAppearance alloc] initWithBarAppearance:appearance];
|
585 | if (config.largeTitleBackgroundColor != nil) {
|
586 | scrollEdgeAppearance.backgroundColor = config.largeTitleBackgroundColor;
|
587 | }
|
588 | if (config.largeTitleHideShadow) {
|
589 | scrollEdgeAppearance.shadowColor = nil;
|
590 | }
|
591 | navitem.scrollEdgeAppearance = scrollEdgeAppearance;
|
592 | } else
|
593 | #endif
|
594 | {
|
595 | #if !TARGET_OS_TV
|
596 |
|
597 |
|
598 | UIImage *backButtonImage = [self loadBackButtonImageInViewController:vc withConfig:config];
|
599 | if (backButtonImage) {
|
600 | navctr.navigationBar.backIndicatorImage = backButtonImage;
|
601 | navctr.navigationBar.backIndicatorTransitionMaskImage = backButtonImage;
|
602 | } else if (navctr.navigationBar.backIndicatorImage) {
|
603 | navctr.navigationBar.backIndicatorImage = nil;
|
604 | navctr.navigationBar.backIndicatorTransitionMaskImage = nil;
|
605 | }
|
606 | #endif
|
607 | }
|
608 | #if !TARGET_OS_TV
|
609 |
|
610 | navitem.hidesBackButton = true;
|
611 | navitem.hidesBackButton = config.hideBackButton;
|
612 | #endif
|
613 | navitem.leftBarButtonItem = nil;
|
614 | navitem.rightBarButtonItem = nil;
|
615 | navitem.titleView = nil;
|
616 |
|
617 | for (RNSScreenStackHeaderSubview *subview in config.reactSubviews) {
|
618 | switch (subview.type) {
|
619 | case RNSScreenStackHeaderSubviewTypeLeft: {
|
620 | #if !TARGET_OS_TV
|
621 | navitem.leftItemsSupplementBackButton = config.backButtonInCustomView;
|
622 | #endif
|
623 | UIBarButtonItem *buttonItem = [[UIBarButtonItem alloc] initWithCustomView:subview];
|
624 | navitem.leftBarButtonItem = buttonItem;
|
625 | break;
|
626 | }
|
627 | case RNSScreenStackHeaderSubviewTypeRight: {
|
628 | UIBarButtonItem *buttonItem = [[UIBarButtonItem alloc] initWithCustomView:subview];
|
629 | navitem.rightBarButtonItem = buttonItem;
|
630 | break;
|
631 | }
|
632 | case RNSScreenStackHeaderSubviewTypeCenter:
|
633 | case RNSScreenStackHeaderSubviewTypeTitle: {
|
634 | navitem.titleView = subview;
|
635 | break;
|
636 | }
|
637 | case RNSScreenStackHeaderSubviewTypeSearchBar: {
|
638 | if (subview.subviews == nil || [subview.subviews count] == 0) {
|
639 | RCTLogWarn(
|
640 | @"Failed to attach search bar to the header. We recommend using `useLayoutEffect` when managing "
|
641 | "searchBar properties dynamically. \n\nSee: github.com/software-mansion/react-native-screens/issues/1188");
|
642 | break;
|
643 | }
|
644 |
|
645 | if ([subview.subviews[0] isKindOfClass:[RNSSearchBar class]]) {
|
646 | #if !TARGET_OS_TV
|
647 | if (@available(iOS 11.0, *)) {
|
648 | RNSSearchBar *searchBar = subview.subviews[0];
|
649 | navitem.searchController = searchBar.controller;
|
650 | navitem.hidesSearchBarWhenScrolling = searchBar.hideWhenScrolling;
|
651 | #if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && defined(__IPHONE_16_0) && \
|
652 | __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_16_0
|
653 | if (@available(iOS 16.0, *)) {
|
654 | navitem.preferredSearchBarPlacement = [searchBar placementAsUINavigationItemSearchBarPlacement];
|
655 | }
|
656 | #endif /* Check for iOS 16.0 */
|
657 | }
|
658 | #endif /* !TARGET_OS_TV */
|
659 | }
|
660 | break;
|
661 | }
|
662 | case RNSScreenStackHeaderSubviewTypeBackButton: {
|
663 | break;
|
664 | }
|
665 | }
|
666 | }
|
667 |
|
668 | dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0), dispatch_get_main_queue(), ^{
|
669 |
|
670 | for (UIView *view in navctr.navigationBar.subviews) {
|
671 | view.semanticContentAttribute = config.direction;
|
672 | }
|
673 | });
|
674 |
|
675 |
|
676 |
|
677 | navitem.title = config.title;
|
678 |
|
679 | if (animated && vc.transitionCoordinator != nil &&
|
680 | vc.transitionCoordinator.presentationStyle == UIModalPresentationNone && !wasHidden) {
|
681 |
|
682 |
|
683 |
|
684 |
|
685 |
|
686 |
|
687 |
|
688 | [vc.transitionCoordinator
|
689 | animateAlongsideTransition:^(id<UIViewControllerTransitionCoordinatorContext> _Nonnull context) {
|
690 | [self setAnimatedConfig:vc withConfig:config];
|
691 | }
|
692 | completion:^(id<UIViewControllerTransitionCoordinatorContext> _Nonnull context) {
|
693 | if ([context isCancelled]) {
|
694 | UIViewController *fromVC = [context viewControllerForKey:UITransitionContextFromViewControllerKey];
|
695 | RNSScreenStackHeaderConfig *config = nil;
|
696 | for (UIView *subview in fromVC.view.reactSubviews) {
|
697 | if ([subview isKindOfClass:[RNSScreenStackHeaderConfig class]]) {
|
698 | config = (RNSScreenStackHeaderConfig *)subview;
|
699 | break;
|
700 | }
|
701 | }
|
702 | [self setAnimatedConfig:fromVC withConfig:config];
|
703 | }
|
704 | }];
|
705 | } else {
|
706 | [self setAnimatedConfig:vc withConfig:config];
|
707 | }
|
708 | }
|
709 |
|
710 | - (void)insertReactSubview:(RNSScreenStackHeaderSubview *)subview atIndex:(NSInteger)atIndex
|
711 | {
|
712 | [_reactSubviews insertObject:subview atIndex:atIndex];
|
713 | subview.reactSuperview = self;
|
714 | }
|
715 |
|
716 | - (void)removeReactSubview:(RNSScreenStackHeaderSubview *)subview
|
717 | {
|
718 | [_reactSubviews removeObject:subview];
|
719 | }
|
720 |
|
721 | - (void)didUpdateReactSubviews
|
722 | {
|
723 | [super didUpdateReactSubviews];
|
724 | [self updateViewControllerIfNeeded];
|
725 | }
|
726 |
|
727 | #ifdef RCT_NEW_ARCH_ENABLED
|
728 | #pragma mark - Fabric specific
|
729 |
|
730 | - (void)mountChildComponentView:(UIView<RCTComponentViewProtocol> *)childComponentView index:(NSInteger)index
|
731 | {
|
732 | if (![childComponentView isKindOfClass:[RNSScreenStackHeaderSubview class]]) {
|
733 | RCTLogError(@"ScreenStackHeader only accepts children of type ScreenStackHeaderSubview");
|
734 | return;
|
735 | }
|
736 |
|
737 | RCTAssert(
|
738 | childComponentView.superview == nil,
|
739 | @"Attempt to mount already mounted component view. (parent: %@, child: %@, index: %@, existing parent: %@)",
|
740 | self,
|
741 | childComponentView,
|
742 | @(index),
|
743 | @([childComponentView.superview tag]));
|
744 |
|
745 |
|
746 | [self insertReactSubview:(RNSScreenStackHeaderSubview *)childComponentView atIndex:index];
|
747 | [self updateViewControllerIfNeeded];
|
748 | }
|
749 |
|
750 | - (void)unmountChildComponentView:(UIView<RCTComponentViewProtocol> *)childComponentView index:(NSInteger)index
|
751 | {
|
752 | [_reactSubviews removeObject:(RNSScreenStackHeaderSubview *)childComponentView];
|
753 | [childComponentView removeFromSuperview];
|
754 | }
|
755 |
|
756 | static RCTResizeMode resizeModeFromCppEquiv(react::ImageResizeMode resizeMode)
|
757 | {
|
758 | switch (resizeMode) {
|
759 | case react::ImageResizeMode::Cover:
|
760 | return RCTResizeModeCover;
|
761 | case react::ImageResizeMode::Contain:
|
762 | return RCTResizeModeContain;
|
763 | case react::ImageResizeMode::Stretch:
|
764 | return RCTResizeModeStretch;
|
765 | case react::ImageResizeMode::Center:
|
766 | return RCTResizeModeCenter;
|
767 | case react::ImageResizeMode::Repeat:
|
768 | return RCTResizeModeRepeat;
|
769 | }
|
770 | }
|
771 |
|
772 |
|
773 |
|
774 |
|
775 |
|
776 | + (RCTImageSource *)imageSourceFromImageView:(RCTImageComponentView *)view
|
777 | {
|
778 | const auto &imageProps = *std::static_pointer_cast<const react::ImageProps>(view.props);
|
779 | react::ImageSource cppImageSource = imageProps.sources.at(0);
|
780 | auto imageSize = CGSize{cppImageSource.size.width, cppImageSource.size.height};
|
781 | NSURLRequest *request =
|
782 | [NSURLRequest requestWithURL:[NSURL URLWithString:RCTNSStringFromStringNilIfEmpty(cppImageSource.uri)]];
|
783 | RCTImageSource *imageSource = [[RCTImageSource alloc] initWithURLRequest:request
|
784 | size:imageSize
|
785 | scale:cppImageSource.scale];
|
786 | return imageSource;
|
787 | }
|
788 |
|
789 | #pragma mark - RCTComponentViewProtocol
|
790 |
|
791 | - (void)prepareForRecycle
|
792 | {
|
793 | [super prepareForRecycle];
|
794 | _initialPropsSet = NO;
|
795 | }
|
796 |
|
797 | - (NSNumber *)getFontSizePropValue:(int)value
|
798 | {
|
799 | if (value > 0)
|
800 | return [NSNumber numberWithInt:value];
|
801 | return nil;
|
802 | }
|
803 |
|
804 | + (react::ComponentDescriptorProvider)componentDescriptorProvider
|
805 | {
|
806 | return react::concreteComponentDescriptorProvider<react::RNSScreenStackHeaderConfigComponentDescriptor>();
|
807 | }
|
808 |
|
809 | - (void)updateProps:(react::Props::Shared const &)props oldProps:(react::Props::Shared const &)oldProps
|
810 | {
|
811 | const auto &oldScreenProps = *std::static_pointer_cast<const react::RNSScreenStackHeaderConfigProps>(_props);
|
812 | const auto &newScreenProps = *std::static_pointer_cast<const react::RNSScreenStackHeaderConfigProps>(props);
|
813 |
|
814 | BOOL needsNavigationControllerLayout = !_initialPropsSet;
|
815 |
|
816 | if (newScreenProps.hidden != !_show) {
|
817 | _show = !newScreenProps.hidden;
|
818 | needsNavigationControllerLayout = YES;
|
819 | }
|
820 |
|
821 | if (newScreenProps.translucent != _translucent) {
|
822 | _translucent = newScreenProps.translucent;
|
823 | needsNavigationControllerLayout = YES;
|
824 | }
|
825 |
|
826 | if (newScreenProps.backButtonInCustomView != _backButtonInCustomView) {
|
827 | [self setBackButtonInCustomView:newScreenProps.backButtonInCustomView];
|
828 | }
|
829 |
|
830 | _title = RCTNSStringFromStringNilIfEmpty(newScreenProps.title);
|
831 | if (newScreenProps.titleFontFamily != oldScreenProps.titleFontFamily) {
|
832 | _titleFontFamily = RCTNSStringFromStringNilIfEmpty(newScreenProps.titleFontFamily);
|
833 | }
|
834 | _titleFontWeight = RCTNSStringFromStringNilIfEmpty(newScreenProps.titleFontWeight);
|
835 | _titleFontSize = [self getFontSizePropValue:newScreenProps.titleFontSize];
|
836 | _hideShadow = newScreenProps.hideShadow;
|
837 |
|
838 | _largeTitle = newScreenProps.largeTitle;
|
839 | if (newScreenProps.largeTitleFontFamily != oldScreenProps.largeTitleFontFamily) {
|
840 | _largeTitleFontFamily = RCTNSStringFromStringNilIfEmpty(newScreenProps.largeTitleFontFamily);
|
841 | }
|
842 | _largeTitleFontWeight = RCTNSStringFromStringNilIfEmpty(newScreenProps.largeTitleFontWeight);
|
843 | _largeTitleFontSize = [self getFontSizePropValue:newScreenProps.largeTitleFontSize];
|
844 | _largeTitleHideShadow = newScreenProps.largeTitleHideShadow;
|
845 |
|
846 | _backTitle = RCTNSStringFromStringNilIfEmpty(newScreenProps.backTitle);
|
847 | if (newScreenProps.backTitleFontFamily != oldScreenProps.backTitleFontFamily) {
|
848 | _backTitleFontFamily = RCTNSStringFromStringNilIfEmpty(newScreenProps.backTitleFontFamily);
|
849 | }
|
850 | _backTitleFontSize = [self getFontSizePropValue:newScreenProps.backTitleFontSize];
|
851 | _hideBackButton = newScreenProps.hideBackButton;
|
852 | _disableBackButtonMenu = newScreenProps.disableBackButtonMenu;
|
853 | _backButtonDisplayMode =
|
854 | [RNSConvert UINavigationItemBackButtonDisplayModeFromCppEquivalent:newScreenProps.backButtonDisplayMode];
|
855 |
|
856 | if (newScreenProps.direction != oldScreenProps.direction) {
|
857 | _direction = [RNSConvert UISemanticContentAttributeFromCppEquivalent:newScreenProps.direction];
|
858 | }
|
859 |
|
860 | _backTitleVisible = newScreenProps.backTitleVisible;
|
861 |
|
862 |
|
863 |
|
864 | _titleColor = RCTUIColorFromSharedColor(newScreenProps.titleColor);
|
865 | _largeTitleColor = RCTUIColorFromSharedColor(newScreenProps.largeTitleColor);
|
866 | _color = RCTUIColorFromSharedColor(newScreenProps.color);
|
867 | _backgroundColor = RCTUIColorFromSharedColor(newScreenProps.backgroundColor);
|
868 |
|
869 | [self updateViewControllerIfNeeded];
|
870 |
|
871 | if (needsNavigationControllerLayout) {
|
872 | [self layoutNavigationControllerView];
|
873 | }
|
874 |
|
875 | _initialPropsSet = YES;
|
876 | _props = std::static_pointer_cast<react::RNSScreenStackHeaderConfigProps const>(props);
|
877 |
|
878 | [super updateProps:props oldProps:oldProps];
|
879 | }
|
880 |
|
881 | #else
|
882 | #pragma mark - Paper specific
|
883 |
|
884 | - (void)didSetProps:(NSArray<NSString *> *)changedProps
|
885 | {
|
886 | [super didSetProps:changedProps];
|
887 | [self updateViewControllerIfNeeded];
|
888 |
|
889 |
|
890 |
|
891 | if ([changedProps containsObject:@"translucent"]) {
|
892 | [self layoutNavigationControllerView];
|
893 | }
|
894 | }
|
895 |
|
896 |
|
897 |
|
898 |
|
899 |
|
900 | + (RCTImageSource *)imageSourceFromImageView:(RCTImageView *)view
|
901 | {
|
902 | return view.imageSources[0];
|
903 | }
|
904 |
|
905 | #endif
|
906 | @end
|
907 |
|
908 | #ifdef RCT_NEW_ARCH_ENABLED
|
909 | Class<RCTComponentViewProtocol> RNSScreenStackHeaderConfigCls(void)
|
910 | {
|
911 | return RNSScreenStackHeaderConfig.class;
|
912 | }
|
913 | #endif
|
914 |
|
915 | @implementation RNSScreenStackHeaderConfigManager
|
916 |
|
917 | RCT_EXPORT_MODULE()
|
918 |
|
919 | - (UIView *)view
|
920 | {
|
921 | return [RNSScreenStackHeaderConfig new];
|
922 | }
|
923 |
|
924 | RCT_EXPORT_VIEW_PROPERTY(title, NSString)
|
925 | RCT_EXPORT_VIEW_PROPERTY(titleFontFamily, NSString)
|
926 | RCT_EXPORT_VIEW_PROPERTY(titleFontSize, NSNumber)
|
927 | RCT_EXPORT_VIEW_PROPERTY(titleFontWeight, NSString)
|
928 | RCT_EXPORT_VIEW_PROPERTY(titleColor, UIColor)
|
929 | RCT_EXPORT_VIEW_PROPERTY(backTitle, NSString)
|
930 | RCT_EXPORT_VIEW_PROPERTY(backTitleFontFamily, NSString)
|
931 | RCT_EXPORT_VIEW_PROPERTY(backTitleFontSize, NSNumber)
|
932 | RCT_EXPORT_VIEW_PROPERTY(backgroundColor, UIColor)
|
933 | RCT_EXPORT_VIEW_PROPERTY(backTitleVisible, BOOL)
|
934 | RCT_EXPORT_VIEW_PROPERTY(blurEffect, UIBlurEffectStyle)
|
935 | RCT_EXPORT_VIEW_PROPERTY(color, UIColor)
|
936 | RCT_EXPORT_VIEW_PROPERTY(direction, UISemanticContentAttribute)
|
937 | RCT_EXPORT_VIEW_PROPERTY(largeTitle, BOOL)
|
938 | RCT_EXPORT_VIEW_PROPERTY(largeTitleFontFamily, NSString)
|
939 | RCT_EXPORT_VIEW_PROPERTY(largeTitleFontSize, NSNumber)
|
940 | RCT_EXPORT_VIEW_PROPERTY(largeTitleFontWeight, NSString)
|
941 | RCT_EXPORT_VIEW_PROPERTY(largeTitleColor, UIColor)
|
942 | RCT_EXPORT_VIEW_PROPERTY(largeTitleBackgroundColor, UIColor)
|
943 | RCT_EXPORT_VIEW_PROPERTY(largeTitleHideShadow, BOOL)
|
944 | RCT_EXPORT_VIEW_PROPERTY(hideBackButton, BOOL)
|
945 | RCT_EXPORT_VIEW_PROPERTY(hideShadow, BOOL)
|
946 | RCT_EXPORT_VIEW_PROPERTY(backButtonInCustomView, BOOL)
|
947 | RCT_EXPORT_VIEW_PROPERTY(disableBackButtonMenu, BOOL)
|
948 | RCT_EXPORT_VIEW_PROPERTY(backButtonDisplayMode, UINavigationItemBackButtonDisplayMode)
|
949 | RCT_REMAP_VIEW_PROPERTY(hidden, hide, BOOL)
|
950 | RCT_EXPORT_VIEW_PROPERTY(translucent, BOOL)
|
951 |
|
952 | @end
|
953 |
|
954 | @implementation RCTConvert (RNSScreenStackHeader)
|
955 |
|
956 | + (NSMutableDictionary *)blurEffectsForIOSVersion
|
957 | {
|
958 | NSMutableDictionary *blurEffects = [NSMutableDictionary new];
|
959 | [blurEffects addEntriesFromDictionary:@{
|
960 | @"extraLight" : @(UIBlurEffectStyleExtraLight),
|
961 | @"light" : @(UIBlurEffectStyleLight),
|
962 | @"dark" : @(UIBlurEffectStyleDark),
|
963 | }];
|
964 |
|
965 | if (@available(iOS 10.0, *)) {
|
966 | [blurEffects addEntriesFromDictionary:@{
|
967 | @"regular" : @(UIBlurEffectStyleRegular),
|
968 | @"prominent" : @(UIBlurEffectStyleProminent),
|
969 | }];
|
970 | }
|
971 | #if !TARGET_OS_TV && defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && defined(__IPHONE_13_0) && \
|
972 | __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0
|
973 | if (@available(iOS 13.0, *)) {
|
974 | [blurEffects addEntriesFromDictionary:@{
|
975 | @"systemUltraThinMaterial" : @(UIBlurEffectStyleSystemUltraThinMaterial),
|
976 | @"systemThinMaterial" : @(UIBlurEffectStyleSystemThinMaterial),
|
977 | @"systemMaterial" : @(UIBlurEffectStyleSystemMaterial),
|
978 | @"systemThickMaterial" : @(UIBlurEffectStyleSystemThickMaterial),
|
979 | @"systemChromeMaterial" : @(UIBlurEffectStyleSystemChromeMaterial),
|
980 | @"systemUltraThinMaterialLight" : @(UIBlurEffectStyleSystemUltraThinMaterialLight),
|
981 | @"systemThinMaterialLight" : @(UIBlurEffectStyleSystemThinMaterialLight),
|
982 | @"systemMaterialLight" : @(UIBlurEffectStyleSystemMaterialLight),
|
983 | @"systemThickMaterialLight" : @(UIBlurEffectStyleSystemThickMaterialLight),
|
984 | @"systemChromeMaterialLight" : @(UIBlurEffectStyleSystemChromeMaterialLight),
|
985 | @"systemUltraThinMaterialDark" : @(UIBlurEffectStyleSystemUltraThinMaterialDark),
|
986 | @"systemThinMaterialDark" : @(UIBlurEffectStyleSystemThinMaterialDark),
|
987 | @"systemMaterialDark" : @(UIBlurEffectStyleSystemMaterialDark),
|
988 | @"systemThickMaterialDark" : @(UIBlurEffectStyleSystemThickMaterialDark),
|
989 | @"systemChromeMaterialDark" : @(UIBlurEffectStyleSystemChromeMaterialDark),
|
990 | }];
|
991 | }
|
992 | #endif
|
993 | return blurEffects;
|
994 | }
|
995 |
|
996 | RCT_ENUM_CONVERTER(
|
997 | UISemanticContentAttribute,
|
998 | (@{
|
999 | @"ltr" : @(UISemanticContentAttributeForceLeftToRight),
|
1000 | @"rtl" : @(UISemanticContentAttributeForceRightToLeft),
|
1001 | }),
|
1002 | UISemanticContentAttributeUnspecified,
|
1003 | integerValue)
|
1004 |
|
1005 | RCT_ENUM_CONVERTER(
|
1006 | UINavigationItemBackButtonDisplayMode,
|
1007 | (@{
|
1008 | @"default" : @(UINavigationItemBackButtonDisplayModeDefault),
|
1009 | @"generic" : @(UINavigationItemBackButtonDisplayModeGeneric),
|
1010 | @"minimal" : @(UINavigationItemBackButtonDisplayModeMinimal),
|
1011 | }),
|
1012 | UINavigationItemBackButtonDisplayModeDefault,
|
1013 | integerValue)
|
1014 |
|
1015 | RCT_ENUM_CONVERTER(UIBlurEffectStyle, ([self blurEffectsForIOSVersion]), UIBlurEffectStyleExtraLight, integerValue)
|
1016 |
|
1017 | @end
|