react-native-screens 3.13.1 → 3.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (123) hide show
  1. package/README.md +2 -2
  2. package/RNScreens.podspec +5 -4
  3. package/android/build.gradle +18 -1
  4. package/android/src/main/java/com/swmansion/rnscreens/Screen.kt +0 -5
  5. package/android/src/main/java/com/swmansion/rnscreens/ScreenStack.kt +8 -4
  6. package/android/src/main/java/com/swmansion/rnscreens/ScreenViewManager.kt +24 -6
  7. package/android/src/main/java/com/swmansion/rnscreens/ScreenWindowTraits.kt +17 -21
  8. package/android/src/main/jni/Android.mk +1 -2
  9. package/android/src/paper/java/com/facebook/react/viewmanagers/RNSScreenManagerDelegate.java +39 -14
  10. package/android/src/paper/java/com/facebook/react/viewmanagers/RNSScreenManagerInterface.java +15 -6
  11. package/android/src/paper/java/com/facebook/react/viewmanagers/RNSScreenStackHeaderConfigManagerDelegate.java +3 -3
  12. package/android/src/paper/java/com/facebook/react/viewmanagers/RNSScreenStackHeaderConfigManagerInterface.java +1 -1
  13. package/android/src/paper/java/com/swmansion/rnscreens/FabricEnabledViewGroup.kt +4 -10
  14. package/common/cpp/Android.mk +1 -2
  15. package/createNativeStackNavigator/README.md +4 -0
  16. package/ios/RNSConvert.h +30 -0
  17. package/ios/RNSConvert.mm +120 -0
  18. package/ios/RNSEnums.h +59 -0
  19. package/ios/RNSFullWindowOverlay.h +17 -2
  20. package/ios/RNSFullWindowOverlay.mm +199 -0
  21. package/ios/RNSScreen.h +70 -79
  22. package/ios/{RNSScreen.m → RNSScreen.mm} +678 -302
  23. package/ios/RNSScreenContainer.h +15 -1
  24. package/ios/{RNSScreenContainer.m → RNSScreenContainer.mm} +99 -8
  25. package/ios/{RNSScreenNavigationContainer.m → RNSScreenNavigationContainer.mm} +22 -0
  26. package/ios/RNSScreenStack.h +19 -3
  27. package/ios/{RNSScreenStack.m → RNSScreenStack.mm} +431 -137
  28. package/ios/{RNSScreenStackAnimator.m → RNSScreenStackAnimator.mm} +19 -14
  29. package/ios/RNSScreenStackHeaderConfig.h +20 -21
  30. package/ios/{RNSScreenStackHeaderConfig.m → RNSScreenStackHeaderConfig.mm} +232 -117
  31. package/ios/RNSScreenStackHeaderSubview.h +45 -0
  32. package/ios/RNSScreenStackHeaderSubview.mm +137 -0
  33. package/ios/RNSScreenViewEvent.h +12 -0
  34. package/ios/RNSScreenViewEvent.mm +59 -0
  35. package/ios/{RNSScreenWindowTraits.m → RNSScreenWindowTraits.mm} +3 -2
  36. package/ios/RNSSearchBar.h +14 -1
  37. package/ios/RNSSearchBar.mm +351 -0
  38. package/ios/{UIViewController+RNScreens.m → UIViewController+RNScreens.mm} +0 -0
  39. package/ios/{UIWindow+RNScreens.m → UIWindow+RNScreens.mm} +0 -0
  40. package/lib/commonjs/fabric/FullWindowOverlayNativeComponent.js +21 -0
  41. package/lib/commonjs/fabric/FullWindowOverlayNativeComponent.js.map +1 -0
  42. package/lib/commonjs/fabric/ScreenContainerNativeComponent.js +21 -0
  43. package/lib/commonjs/fabric/ScreenContainerNativeComponent.js.map +1 -0
  44. package/lib/commonjs/fabric/ScreenNativeComponent.js.map +1 -1
  45. package/lib/commonjs/fabric/ScreenNavigationContainerNativeComponent.js +21 -0
  46. package/lib/commonjs/fabric/ScreenNavigationContainerNativeComponent.js.map +1 -0
  47. package/lib/commonjs/fabric/ScreenStackHeaderConfigNativeComponent.js.map +1 -1
  48. package/lib/commonjs/fabric/ScreenStackNativeComponent.js.map +1 -1
  49. package/lib/commonjs/fabric/SearchBarNativeComponent.js +25 -0
  50. package/lib/commonjs/fabric/SearchBarNativeComponent.js.map +1 -0
  51. package/lib/commonjs/index.native.js +20 -32
  52. package/lib/commonjs/index.native.js.map +1 -1
  53. package/lib/commonjs/native-stack/views/NativeStackView.js +4 -0
  54. package/lib/commonjs/native-stack/views/NativeStackView.js.map +1 -1
  55. package/lib/commonjs/reanimated/ReanimatedNativeStackScreen.js +10 -2
  56. package/lib/commonjs/reanimated/ReanimatedNativeStackScreen.js.map +1 -1
  57. package/lib/module/fabric/FullWindowOverlayNativeComponent.js +9 -0
  58. package/lib/module/fabric/FullWindowOverlayNativeComponent.js.map +1 -0
  59. package/lib/module/fabric/ScreenContainerNativeComponent.js +9 -0
  60. package/lib/module/fabric/ScreenContainerNativeComponent.js.map +1 -0
  61. package/lib/module/fabric/ScreenNativeComponent.js.map +1 -1
  62. package/lib/module/fabric/ScreenNavigationContainerNativeComponent.js +9 -0
  63. package/lib/module/fabric/ScreenNavigationContainerNativeComponent.js.map +1 -0
  64. package/lib/module/fabric/ScreenStackHeaderConfigNativeComponent.js.map +1 -1
  65. package/lib/module/fabric/ScreenStackNativeComponent.js.map +1 -1
  66. package/lib/module/fabric/SearchBarNativeComponent.js +11 -0
  67. package/lib/module/fabric/SearchBarNativeComponent.js.map +1 -0
  68. package/lib/module/index.native.js +21 -34
  69. package/lib/module/index.native.js.map +1 -1
  70. package/lib/module/native-stack/views/NativeStackView.js +4 -0
  71. package/lib/module/native-stack/views/NativeStackView.js.map +1 -1
  72. package/lib/module/reanimated/ReanimatedNativeStackScreen.js +9 -2
  73. package/lib/module/reanimated/ReanimatedNativeStackScreen.js.map +1 -1
  74. package/lib/typescript/native-stack/types.d.ts +12 -0
  75. package/lib/typescript/reanimated/ReanimatedNativeStackScreen.d.ts +1 -1
  76. package/lib/typescript/reanimated/ReanimatedScreen.d.ts +1 -1
  77. package/lib/typescript/types.d.ts +24 -0
  78. package/native-stack/README.md +21 -0
  79. package/package.json +2 -2
  80. package/src/fabric/FullWindowOverlayNativeComponent.js +19 -0
  81. package/src/fabric/ScreenContainerNativeComponent.js +19 -0
  82. package/src/fabric/ScreenNativeComponent.js +41 -8
  83. package/src/fabric/ScreenNavigationContainerNativeComponent.js +19 -0
  84. package/src/fabric/ScreenStackHeaderConfigNativeComponent.js +1 -1
  85. package/src/fabric/ScreenStackNativeComponent.js +4 -0
  86. package/src/fabric/SearchBarNativeComponent.js +62 -0
  87. package/src/index.native.tsx +17 -36
  88. package/src/native-stack/types.tsx +12 -0
  89. package/src/native-stack/views/NativeStackView.tsx +4 -0
  90. package/src/reanimated/ReanimatedNativeStackScreen.tsx +8 -0
  91. package/src/types.tsx +25 -0
  92. package/ios/RNSFullWindowOverlay.m +0 -105
  93. package/ios/RNSScreenComponentView.h +0 -23
  94. package/ios/RNSScreenComponentView.mm +0 -159
  95. package/ios/RNSScreenController.h +0 -10
  96. package/ios/RNSScreenController.mm +0 -79
  97. package/ios/RNSScreenStackComponentView.h +0 -15
  98. package/ios/RNSScreenStackComponentView.mm +0 -295
  99. package/ios/RNSScreenStackHeaderConfigComponentView.h +0 -42
  100. package/ios/RNSScreenStackHeaderConfigComponentView.mm +0 -662
  101. package/ios/RNSScreenStackHeaderSubviewComponentView.h +0 -14
  102. package/ios/RNSScreenStackHeaderSubviewComponentView.mm +0 -77
  103. package/ios/RNSSearchBar.m +0 -198
  104. package/lib/commonjs/fabric/Screen.js +0 -29
  105. package/lib/commonjs/fabric/Screen.js.map +0 -1
  106. package/lib/commonjs/fabric/ScreenStack.js +0 -26
  107. package/lib/commonjs/fabric/ScreenStack.js.map +0 -1
  108. package/lib/commonjs/fabric/ScreenStackHeaderSubview.js +0 -37
  109. package/lib/commonjs/fabric/ScreenStackHeaderSubview.js.map +0 -1
  110. package/lib/commonjs/fabric/index.js +0 -40
  111. package/lib/commonjs/fabric/index.js.map +0 -1
  112. package/lib/module/fabric/Screen.js +0 -16
  113. package/lib/module/fabric/Screen.js.map +0 -1
  114. package/lib/module/fabric/ScreenStack.js +0 -15
  115. package/lib/module/fabric/ScreenStack.js.map +0 -1
  116. package/lib/module/fabric/ScreenStackHeaderSubview.js +0 -24
  117. package/lib/module/fabric/ScreenStackHeaderSubview.js.map +0 -1
  118. package/lib/module/fabric/index.js +0 -6
  119. package/lib/module/fabric/index.js.map +0 -1
  120. package/src/fabric/Screen.js +0 -15
  121. package/src/fabric/ScreenStack.js +0 -10
  122. package/src/fabric/ScreenStackHeaderSubview.js +0 -22
  123. package/src/fabric/index.js +0 -11
@@ -2,57 +2,86 @@
2
2
 
3
3
  #import "RNSScreen.h"
4
4
  #import "RNSScreenContainer.h"
5
- #import "RNSScreenStack.h"
6
- #import "RNSScreenStackHeaderConfig.h"
7
5
  #import "RNSScreenWindowTraits.h"
8
6
 
9
- #import <React/RCTShadowView.h>
7
+ #ifdef RN_FABRIC_ENABLED
8
+ #import <React/RCTConversions.h>
9
+ #import <React/RCTRootComponentView.h>
10
+ #import <React/RCTSurfaceTouchHandler.h>
11
+ #import <react/renderer/components/rnscreens/EventEmitters.h>
12
+ #import <react/renderer/components/rnscreens/Props.h>
13
+ #import <react/renderer/components/rnscreens/RCTComponentViewHelpers.h>
14
+ #import <rnscreens/RNSScreenComponentDescriptor.h>
15
+ #import "RCTFabricComponentsPlugins.h"
16
+ #import "RNSConvert.h"
17
+ #import "RNSScreenViewEvent.h"
18
+ #else
10
19
  #import <React/RCTTouchHandler.h>
20
+ #endif
21
+
22
+ #import <React/RCTShadowView.h>
11
23
  #import <React/RCTUIManager.h>
24
+ #import "RNSScreenStack.h"
25
+ #import "RNSScreenStackHeaderConfig.h"
12
26
 
13
- @interface RNSScreenView () <UIAdaptivePresentationControllerDelegate, RCTInvalidating>
27
+ @interface RNSScreenView ()
28
+ #ifdef RN_FABRIC_ENABLED
29
+ <RCTRNSScreenViewProtocol, UIAdaptivePresentationControllerDelegate>
30
+ #else
31
+ <UIAdaptivePresentationControllerDelegate, RCTInvalidating>
32
+ #endif
14
33
  @end
15
34
 
16
35
  @implementation RNSScreenView {
17
36
  __weak RCTBridge *_bridge;
18
- RNSScreen *_controller;
37
+ #ifdef RN_FABRIC_ENABLED
38
+ RCTSurfaceTouchHandler *_touchHandler;
39
+ facebook::react::RNSScreenShadowNode::ConcreteState::Shared _state;
40
+ // on fabric, they are not available by default so we need them exposed here too
41
+ NSMutableArray<UIView *> *_reactSubviews;
42
+ #else
19
43
  RCTTouchHandler *_touchHandler;
20
44
  CGRect _reactFrame;
45
+ #endif
46
+ }
47
+
48
+ #ifdef RN_FABRIC_ENABLED
49
+ - (instancetype)initWithFrame:(CGRect)frame
50
+ {
51
+ if (self = [super initWithFrame:frame]) {
52
+ static const auto defaultProps = std::make_shared<const facebook::react::RNSScreenProps>();
53
+ _props = defaultProps;
54
+ _reactSubviews = [NSMutableArray new];
55
+ [self initCommonProps];
56
+ }
57
+
58
+ return self;
21
59
  }
60
+ #endif
22
61
 
23
62
  - (instancetype)initWithBridge:(RCTBridge *)bridge
24
63
  {
25
64
  if (self = [super init]) {
26
65
  _bridge = bridge;
27
- _controller = [[RNSScreen alloc] initWithView:self];
28
- _stackPresentation = RNSScreenStackPresentationPush;
29
- _stackAnimation = RNSScreenStackAnimationDefault;
30
- _gestureEnabled = YES;
31
- _replaceAnimation = RNSScreenReplaceAnimationPop;
32
- _dismissed = NO;
33
- _hasStatusBarStyleSet = NO;
34
- _hasStatusBarAnimationSet = NO;
35
- _hasStatusBarHiddenSet = NO;
36
- _hasOrientationSet = NO;
37
- _hasHomeIndicatorHiddenSet = NO;
66
+ [self initCommonProps];
38
67
  }
39
68
 
40
69
  return self;
41
70
  }
42
71
 
43
- - (void)reactSetFrame:(CGRect)frame
72
+ - (void)initCommonProps
44
73
  {
45
- _reactFrame = frame;
46
- UIViewController *parentVC = self.reactViewController.parentViewController;
47
- if (parentVC != nil && ![parentVC isKindOfClass:[RNScreensNavigationController class]]) {
48
- [super reactSetFrame:frame];
49
- }
50
- // when screen is mounted under RNScreensNavigationController it's size is controller
51
- // by the navigation controller itself. That is, it is set to fill space of
52
- // the controller. In that case we ignore react layout system from managing
53
- // the screen dimensions and we wait for the screen VC to update and then we
54
- // pass the dimensions to ui view manager to take into account when laying out
55
- // subviews
74
+ _controller = [[RNSScreen alloc] initWithView:self];
75
+ _stackPresentation = RNSScreenStackPresentationPush;
76
+ _stackAnimation = RNSScreenStackAnimationDefault;
77
+ _gestureEnabled = YES;
78
+ _replaceAnimation = RNSScreenReplaceAnimationPop;
79
+ _dismissed = NO;
80
+ _hasStatusBarStyleSet = NO;
81
+ _hasStatusBarAnimationSet = NO;
82
+ _hasStatusBarHiddenSet = NO;
83
+ _hasOrientationSet = NO;
84
+ _hasHomeIndicatorHiddenSet = NO;
56
85
  }
57
86
 
58
87
  - (UIViewController *)reactViewController
@@ -60,28 +89,25 @@
60
89
  return _controller;
61
90
  }
62
91
 
63
- - (void)updateBounds
92
+ #ifdef RN_FABRIC_ENABLED
93
+ - (NSArray<UIView *> *)reactSubviews
64
94
  {
65
- [_bridge.uiManager setSize:self.bounds.size forView:self];
95
+ return _reactSubviews;
66
96
  }
97
+ #endif
67
98
 
68
- // Nil will be provided when activityState is set as an animated value and we change
69
- // it from JS to be a plain value (non animated).
70
- // In case when nil is received, we want to ignore such value and not make
71
- // any updates as the actual non-nil value will follow immediately.
72
- - (void)setActivityStateOrNil:(NSNumber *)activityStateOrNil
99
+ - (void)updateBounds
73
100
  {
74
- int activityState = [activityStateOrNil intValue];
75
- if (activityStateOrNil != nil && activityState != _activityState) {
76
- _activityState = activityState;
77
- [_reactSuperview markChildUpdated];
101
+ #ifdef RN_FABRIC_ENABLED
102
+ if (_state != nullptr) {
103
+ auto newState = facebook::react::RNSScreenState{RCTSizeFromCGSize(self.bounds.size)};
104
+ _state->updateState(std::move(newState));
105
+ UINavigationController *navctr = _controller.navigationController;
106
+ [navctr.view setNeedsLayout];
78
107
  }
79
- }
80
-
81
- - (void)setPointerEvents:(RCTPointerEvents)pointerEvents
82
- {
83
- // pointer events settings are managed by the parent screen container, we ignore
84
- // any attempt of setting that via React props
108
+ #else
109
+ [_bridge.uiManager setSize:self.bounds.size forView:self];
110
+ #endif
85
111
  }
86
112
 
87
113
  - (void)setStackPresentation:(RNSScreenStackPresentation)stackPresentation
@@ -131,8 +157,13 @@
131
157
  // https://developer.apple.com/documentation/uikit/uiviewcontroller/1621426-presentationcontroller?language=objc
132
158
  _controller.presentationController.delegate = self;
133
159
  } else if (_stackPresentation != RNSScreenStackPresentationPush) {
160
+ #ifdef RN_FABRIC_ENABLED
161
+ // TODO: on Fabric, same controllers can be used as modals and then recycled and used a push which would result in
162
+ // this error. It would be good to check if it doesn't leak in such case.
163
+ #else
134
164
  RCTLogError(
135
165
  @"Screen presentation updated from modal to push, this may likely result in a screen object leakage. If you need to change presentation style create a new screen object instead");
166
+ #endif
136
167
  }
137
168
  _stackPresentation = stackPresentation;
138
169
  }
@@ -177,6 +208,19 @@
177
208
  _replaceAnimation = replaceAnimation;
178
209
  }
179
210
 
211
+ // Nil will be provided when activityState is set as an animated value and we change
212
+ // it from JS to be a plain value (non animated).
213
+ // In case when nil is received, we want to ignore such value and not make
214
+ // any updates as the actual non-nil value will follow immediately.
215
+ - (void)setActivityStateOrNil:(NSNumber *)activityStateOrNil
216
+ {
217
+ int activityState = [activityStateOrNil intValue];
218
+ if (activityStateOrNil != nil && activityState != -1 && activityState != _activityState) {
219
+ _activityState = activityState;
220
+ [_reactSuperview markChildUpdated];
221
+ }
222
+ }
223
+
180
224
  #if !TARGET_OS_TV
181
225
  - (void)setStatusBarStyle:(RNSStatusBarStyle)statusBarStyle
182
226
  {
@@ -230,13 +274,17 @@
230
274
  }
231
275
  }
232
276
 
233
- - (void)notifyFinishTransitioning
234
- {
235
- [_controller notifyFinishTransitioning];
236
- }
237
-
238
277
  - (void)notifyDismissedWithCount:(int)dismissCount
239
278
  {
279
+ #ifdef RN_FABRIC_ENABLED
280
+ // If screen is already unmounted then there will be no event emitter
281
+ // it will be cleaned in prepareForRecycle
282
+ if (_eventEmitter != nullptr) {
283
+ std::dynamic_pointer_cast<const facebook::react::RNSScreenEventEmitter>(_eventEmitter)
284
+ ->onDismissed(facebook::react::RNSScreenEventEmitter::OnDismissed{.dismissCount = dismissCount});
285
+ }
286
+ #else
287
+ // TODO: hopefully problems connected to dismissed prop are only the case on paper
240
288
  _dismissed = YES;
241
289
  if (self.onDismissed) {
242
290
  dispatch_async(dispatch_get_main_queue(), ^{
@@ -245,27 +293,75 @@
245
293
  }
246
294
  });
247
295
  }
296
+ #endif
297
+ }
298
+
299
+ - (void)notifyDismissCancelledWithDismissCount:(int)dismissCount
300
+ {
301
+ #ifdef RN_FABRIC_ENABLED
302
+ // If screen is already unmounted then there will be no event emitter
303
+ // it will be cleaned in prepareForRecycle
304
+ if (_eventEmitter != nullptr) {
305
+ std::dynamic_pointer_cast<const facebook::react::RNSScreenEventEmitter>(_eventEmitter)
306
+ ->onNativeDismissCancelled(
307
+ facebook::react::RNSScreenEventEmitter::OnNativeDismissCancelled{.dismissCount = dismissCount});
308
+ }
309
+ #else
310
+ if (self.onNativeDismissCancelled) {
311
+ self.onNativeDismissCancelled(@{@"dismissCount" : @(dismissCount)});
312
+ }
313
+ #endif
248
314
  }
249
315
 
250
316
  - (void)notifyWillAppear
251
317
  {
318
+ #ifdef RN_FABRIC_ENABLED
319
+ // If screen is already unmounted then there will be no event emitter
320
+ // it will be cleaned in prepareForRecycle
321
+ if (_eventEmitter != nullptr) {
322
+ std::dynamic_pointer_cast<const facebook::react::RNSScreenEventEmitter>(_eventEmitter)
323
+ ->onWillAppear(facebook::react::RNSScreenEventEmitter::OnWillAppear{});
324
+ }
325
+ [self updateLayoutMetrics:_newLayoutMetrics oldLayoutMetrics:_oldLayoutMetrics];
326
+ #else
252
327
  if (self.onWillAppear) {
253
328
  self.onWillAppear(nil);
254
329
  }
255
330
  // we do it here too because at this moment the `parentViewController` is already not nil,
256
331
  // so if the parent is not UINavCtr, the frame will be updated to the correct one.
257
332
  [self reactSetFrame:_reactFrame];
333
+ #endif
258
334
  }
259
335
 
260
336
  - (void)notifyWillDisappear
261
337
  {
338
+ if (_hideKeyboardOnSwipe) {
339
+ [self endEditing:YES];
340
+ }
341
+ #ifdef RN_FABRIC_ENABLED
342
+ // If screen is already unmounted then there will be no event emitter
343
+ // it will be cleaned in prepareForRecycle
344
+ if (_eventEmitter != nullptr) {
345
+ std::dynamic_pointer_cast<const facebook::react::RNSScreenEventEmitter>(_eventEmitter)
346
+ ->onWillDisappear(facebook::react::RNSScreenEventEmitter::OnWillDisappear{});
347
+ }
348
+ #else
262
349
  if (self.onWillDisappear) {
263
350
  self.onWillDisappear(nil);
264
351
  }
352
+ #endif
265
353
  }
266
354
 
267
355
  - (void)notifyAppear
268
356
  {
357
+ #ifdef RN_FABRIC_ENABLED
358
+ // If screen is already unmounted then there will be no event emitter
359
+ // it will be cleaned in prepareForRecycle
360
+ if (_eventEmitter != nullptr) {
361
+ std::dynamic_pointer_cast<const facebook::react::RNSScreenEventEmitter>(_eventEmitter)
362
+ ->onAppear(facebook::react::RNSScreenEventEmitter::OnAppear{});
363
+ }
364
+ #else
269
365
  if (self.onAppear) {
270
366
  dispatch_async(dispatch_get_main_queue(), ^{
271
367
  if (self.onAppear) {
@@ -273,41 +369,39 @@
273
369
  }
274
370
  });
275
371
  }
372
+ #endif
276
373
  }
277
374
 
278
375
  - (void)notifyDisappear
279
376
  {
377
+ #ifdef RN_FABRIC_ENABLED
378
+ // If screen is already unmounted then there will be no event emitter
379
+ // it will be cleaned in prepareForRecycle
380
+ if (_eventEmitter != nullptr) {
381
+ std::dynamic_pointer_cast<const facebook::react::RNSScreenEventEmitter>(_eventEmitter)
382
+ ->onDisappear(facebook::react::RNSScreenEventEmitter::OnDisappear{});
383
+ }
384
+ #else
280
385
  if (self.onDisappear) {
281
386
  self.onDisappear(nil);
282
387
  }
283
- }
284
-
285
- - (void)notifyDismissCancelledWithDismissCount:(int)dismissCount
286
- {
287
- if (self.onNativeDismissCancelled) {
288
- self.onNativeDismissCancelled(@{@"dismissCount" : @(dismissCount)});
289
- }
290
- }
291
-
292
- - (void)notifyTransitionProgress:(double)progress closing:(BOOL)closing goingForward:(BOOL)goingForward
293
- {
294
- if (self.onTransitionProgress) {
295
- self.onTransitionProgress(@{
296
- @"progress" : @(progress),
297
- @"closing" : @(closing ? 1 : 0),
298
- @"goingForward" : @(goingForward ? 1 : 0),
299
- });
300
- }
388
+ #endif
301
389
  }
302
390
 
303
391
  - (BOOL)isMountedUnderScreenOrReactRoot
304
392
  {
393
+ #ifdef RN_FABRIC_ENABLED
394
+ #define RNS_EXPECTED_VIEW RCTRootComponentView
395
+ #else
396
+ #define RNS_EXPECTED_VIEW RCTRootView
397
+ #endif
305
398
  for (UIView *parent = self.superview; parent != nil; parent = parent.superview) {
306
- if ([parent isKindOfClass:[RCTRootView class]] || [parent isKindOfClass:[RNSScreenView class]]) {
399
+ if ([parent isKindOfClass:[RNS_EXPECTED_VIEW class]] || [parent isKindOfClass:[RNSScreenView class]]) {
307
400
  return YES;
308
401
  }
309
402
  }
310
403
  return NO;
404
+ #undef RNS_EXPECTED_VIEW
311
405
  }
312
406
 
313
407
  - (void)didMoveToWindow
@@ -317,7 +411,11 @@
317
411
  // root application window.
318
412
  if (self.window != nil && ![self isMountedUnderScreenOrReactRoot]) {
319
413
  if (_touchHandler == nil) {
414
+ #ifdef RN_FABRIC_ENABLED
415
+ _touchHandler = [RCTSurfaceTouchHandler new];
416
+ #else
320
417
  _touchHandler = [[RCTTouchHandler alloc] initWithBridge:_bridge];
418
+ #endif
321
419
  }
322
420
  [_touchHandler attachToView:self];
323
421
  } else {
@@ -325,6 +423,54 @@
325
423
  }
326
424
  }
327
425
 
426
+ #ifdef RN_FABRIC_ENABLED
427
+ - (RCTSurfaceTouchHandler *)touchHandler
428
+ #else
429
+ - (RCTTouchHandler *)touchHandler
430
+ #endif
431
+ {
432
+ if (_touchHandler != nil) {
433
+ return _touchHandler;
434
+ }
435
+ UIView *parent = [self superview];
436
+ while (parent != nil && ![parent respondsToSelector:@selector(touchHandler)])
437
+ parent = parent.superview;
438
+ if (parent != nil) {
439
+ return [parent performSelector:@selector(touchHandler)];
440
+ }
441
+ return nil;
442
+ }
443
+
444
+ - (void)notifyFinishTransitioning
445
+ {
446
+ [_controller notifyFinishTransitioning];
447
+ }
448
+
449
+ - (void)notifyTransitionProgress:(double)progress closing:(BOOL)closing goingForward:(BOOL)goingForward
450
+ {
451
+ #ifdef RN_FABRIC_ENABLED
452
+ if (_eventEmitter != nullptr) {
453
+ std::dynamic_pointer_cast<const facebook::react::RNSScreenEventEmitter>(_eventEmitter)
454
+ ->onTransitionProgress(facebook::react::RNSScreenEventEmitter::OnTransitionProgress{
455
+ .progress = progress, .closing = closing ? 1 : 0, .goingForward = goingForward ? 1 : 0});
456
+ }
457
+ RNSScreenViewEvent *event = [[RNSScreenViewEvent alloc] initWithEventName:@"onTransitionProgress"
458
+ reactTag:[NSNumber numberWithInt:self.tag]
459
+ progress:progress
460
+ closing:closing
461
+ goingForward:goingForward];
462
+ [[RCTBridge currentBridge].eventDispatcher sendEvent:event];
463
+ #else
464
+ if (self.onTransitionProgress) {
465
+ self.onTransitionProgress(@{
466
+ @"progress" : @(progress),
467
+ @"closing" : @(closing ? 1 : 0),
468
+ @"goingForward" : @(goingForward ? 1 : 0),
469
+ });
470
+ }
471
+ #endif
472
+ }
473
+
328
474
  - (void)presentationControllerWillDismiss:(UIPresentationController *)presentationController
329
475
  {
330
476
  // We need to call both "cancel" and "reset" here because RN's gesture recognizer
@@ -337,24 +483,15 @@
337
483
  // pulling down starting at some touchable item. Without "reset" the touchable
338
484
  // will never go back from highlighted state even when the modal start sliding
339
485
  // down.
486
+ #ifdef RN_FABRIC_ENABLED
487
+ [_touchHandler setEnabled:NO];
488
+ [_touchHandler setEnabled:YES];
489
+ #else
340
490
  [_touchHandler cancel];
491
+ #endif
341
492
  [_touchHandler reset];
342
493
  }
343
494
 
344
- - (RCTTouchHandler *)touchHandler
345
- {
346
- if (_touchHandler != nil) {
347
- return _touchHandler;
348
- }
349
- UIView *parent = [self superview];
350
- while (parent != nil && ![parent respondsToSelector:@selector(touchHandler)])
351
- parent = parent.superview;
352
- if (parent != nil) {
353
- return [parent performSelector:@selector(touchHandler)];
354
- }
355
- return nil;
356
- }
357
-
358
495
  - (BOOL)presentationControllerShouldDismiss:(UIPresentationController *)presentationController
359
496
  {
360
497
  if (_preventNativeDismiss) {
@@ -371,211 +508,212 @@
371
508
  }
372
509
  }
373
510
 
374
- - (void)invalidate
375
- {
376
- _controller = nil;
377
- }
378
-
379
- @end
511
+ #pragma mark - Fabric specific
512
+ #ifdef RN_FABRIC_ENABLED
380
513
 
381
- @implementation RNSScreen {
382
- __weak id _previousFirstResponder;
383
- CGRect _lastViewFrame;
384
- UIView *_fakeView;
385
- CADisplayLink *_animationTimer;
386
- CGFloat _currentAlpha;
387
- BOOL _closing;
388
- BOOL _goingForward;
389
- int _dismissCount;
390
- BOOL _isSwiping;
391
- BOOL _shouldNotify;
514
+ + (facebook::react::ComponentDescriptorProvider)componentDescriptorProvider
515
+ {
516
+ return facebook::react::concreteComponentDescriptorProvider<facebook::react::RNSScreenComponentDescriptor>();
392
517
  }
393
518
 
394
- - (instancetype)initWithView:(UIView *)view
519
+ - (void)mountChildComponentView:(UIView<RCTComponentViewProtocol> *)childComponentView index:(NSInteger)index
395
520
  {
396
- if (self = [super init]) {
397
- self.view = view;
398
- _shouldNotify = YES;
399
- _fakeView = [UIView new];
521
+ [super mountChildComponentView:childComponentView index:index];
522
+ if ([childComponentView isKindOfClass:[RNSScreenStackHeaderConfig class]]) {
523
+ _config = childComponentView;
524
+ ((RNSScreenStackHeaderConfig *)childComponentView).screenView = self;
400
525
  }
401
- return self;
526
+ [_reactSubviews insertObject:childComponentView atIndex:index];
402
527
  }
403
528
 
404
- #if !TARGET_OS_TV
405
- - (UIViewController *)childViewControllerForStatusBarStyle
529
+ - (void)unmountChildComponentView:(UIView<RCTComponentViewProtocol> *)childComponentView index:(NSInteger)index
406
530
  {
407
- UIViewController *vc = [self findChildVCForConfigAndTrait:RNSWindowTraitStyle includingModals:NO];
408
- return vc == self ? nil : vc;
531
+ if ([childComponentView isKindOfClass:[RNSScreenStackHeaderConfig class]]) {
532
+ _config = nil;
533
+ }
534
+ [_reactSubviews removeObject:childComponentView];
535
+ [super unmountChildComponentView:childComponentView index:index];
409
536
  }
410
537
 
411
- - (UIStatusBarStyle)preferredStatusBarStyle
412
- {
413
- return [RNSScreenWindowTraits statusBarStyleForRNSStatusBarStyle:((RNSScreenView *)self.view).statusBarStyle];
414
- }
538
+ #pragma mark - RCTComponentViewProtocol
415
539
 
416
- - (UIViewController *)childViewControllerForStatusBarHidden
540
+ - (void)prepareForRecycle
417
541
  {
418
- UIViewController *vc = [self findChildVCForConfigAndTrait:RNSWindowTraitHidden includingModals:NO];
419
- return vc == self ? nil : vc;
542
+ [super prepareForRecycle];
543
+ // TODO: Make sure that there is no edge case when this should be uncommented
544
+ // _controller=nil;
545
+ _dismissed = NO;
546
+ _state.reset();
547
+ _touchHandler = nil;
420
548
  }
421
549
 
422
- - (BOOL)prefersStatusBarHidden
550
+ - (void)updateProps:(facebook::react::Props::Shared const &)props
551
+ oldProps:(facebook::react::Props::Shared const &)oldProps
423
552
  {
424
- return ((RNSScreenView *)self.view).statusBarHidden;
425
- }
553
+ const auto &oldScreenProps = *std::static_pointer_cast<const facebook::react::RNSScreenProps>(_props);
554
+ const auto &newScreenProps = *std::static_pointer_cast<const facebook::react::RNSScreenProps>(props);
426
555
 
427
- - (UIStatusBarAnimation)preferredStatusBarUpdateAnimation
428
- {
429
- UIViewController *vc = [self findChildVCForConfigAndTrait:RNSWindowTraitAnimation includingModals:NO];
556
+ [self setFullScreenSwipeEnabled:newScreenProps.fullScreenSwipeEnabled];
430
557
 
431
- if ([vc isKindOfClass:[RNSScreen class]]) {
432
- return ((RNSScreenView *)vc.view).statusBarAnimation;
558
+ [self setGestureEnabled:newScreenProps.gestureEnabled];
559
+
560
+ [self setTransitionDuration:[NSNumber numberWithInt:newScreenProps.transitionDuration]];
561
+
562
+ [self setHideKeyboardOnSwipe:newScreenProps.hideKeyboardOnSwipe];
563
+
564
+ [self setCustomAnimationOnSwipe:newScreenProps.customAnimationOnSwipe];
565
+
566
+ [self
567
+ setGestureResponseDistance:[RNSConvert
568
+ gestureResponseDistanceDictFromCppStruct:newScreenProps.gestureResponseDistance]];
569
+
570
+ [self setPreventNativeDismiss:newScreenProps.preventNativeDismiss];
571
+
572
+ [self setActivityStateOrNil:[NSNumber numberWithFloat:newScreenProps.activityState]];
573
+
574
+ [self setSwipeDirection:[RNSConvert RNSScreenSwipeDirectionFromCppEquivalent:newScreenProps.swipeDirection]];
575
+
576
+ #if !TARGET_OS_TV
577
+ if (newScreenProps.statusBarHidden != oldScreenProps.statusBarHidden) {
578
+ [self setStatusBarHidden:newScreenProps.statusBarHidden];
433
579
  }
434
- return UIStatusBarAnimationFade;
435
- }
436
580
 
437
- - (UIInterfaceOrientationMask)supportedInterfaceOrientations
438
- {
439
- UIViewController *vc = [self findChildVCForConfigAndTrait:RNSWindowTraitOrientation includingModals:YES];
581
+ if (newScreenProps.statusBarStyle != oldScreenProps.statusBarStyle) {
582
+ [self setStatusBarStyle:[RCTConvert
583
+ RNSStatusBarStyle:RCTNSStringFromStringNilIfEmpty(newScreenProps.statusBarStyle)]];
584
+ }
440
585
 
441
- if ([vc isKindOfClass:[RNSScreen class]]) {
442
- return ((RNSScreenView *)vc.view).screenOrientation;
586
+ if (newScreenProps.statusBarAnimation != oldScreenProps.statusBarAnimation) {
587
+ [self setStatusBarAnimation:[RCTConvert UIStatusBarAnimation:RCTNSStringFromStringNilIfEmpty(
588
+ newScreenProps.statusBarAnimation)]];
443
589
  }
444
- return UIInterfaceOrientationMaskAllButUpsideDown;
590
+
591
+ if (newScreenProps.screenOrientation != oldScreenProps.screenOrientation) {
592
+ [self setScreenOrientation:[RCTConvert UIInterfaceOrientationMask:RCTNSStringFromStringNilIfEmpty(
593
+ newScreenProps.screenOrientation)]];
594
+ }
595
+
596
+ if (newScreenProps.homeIndicatorHidden != oldScreenProps.homeIndicatorHidden) {
597
+ [self setHomeIndicatorHidden:newScreenProps.homeIndicatorHidden];
598
+ }
599
+ #endif
600
+
601
+ if (newScreenProps.stackPresentation != oldScreenProps.stackPresentation) {
602
+ [self
603
+ setStackPresentation:[RNSConvert RNSScreenStackPresentationFromCppEquivalent:newScreenProps.stackPresentation]];
604
+ }
605
+
606
+ if (newScreenProps.stackAnimation != oldScreenProps.stackAnimation) {
607
+ [self setStackAnimation:[RNSConvert RNSScreenStackAnimationFromCppEquivalent:newScreenProps.stackAnimation]];
608
+ }
609
+
610
+ if (newScreenProps.replaceAnimation != oldScreenProps.replaceAnimation) {
611
+ [self setReplaceAnimation:[RNSConvert RNSScreenReplaceAnimationFromCppEquivalent:newScreenProps.replaceAnimation]];
612
+ }
613
+
614
+ [super updateProps:props oldProps:oldProps];
445
615
  }
446
616
 
447
- - (UIViewController *)childViewControllerForHomeIndicatorAutoHidden
617
+ - (void)updateState:(facebook::react::State::Shared const &)state
618
+ oldState:(facebook::react::State::Shared const &)oldState
448
619
  {
449
- UIViewController *vc = [self findChildVCForConfigAndTrait:RNSWindowTraitHomeIndicatorHidden includingModals:YES];
450
- return vc == self ? nil : vc;
620
+ _state = std::static_pointer_cast<const facebook::react::RNSScreenShadowNode::ConcreteState>(state);
451
621
  }
452
622
 
453
- - (BOOL)prefersHomeIndicatorAutoHidden
623
+ - (void)updateLayoutMetrics:(const facebook::react::LayoutMetrics &)layoutMetrics
624
+ oldLayoutMetrics:(const facebook::react::LayoutMetrics &)oldLayoutMetrics
454
625
  {
455
- return ((RNSScreenView *)self.view).homeIndicatorHidden;
626
+ _newLayoutMetrics = layoutMetrics;
627
+ _oldLayoutMetrics = oldLayoutMetrics;
628
+ UIViewController *parentVC = self.reactViewController.parentViewController;
629
+ if (parentVC != nil && ![parentVC isKindOfClass:[RNScreensNavigationController class]]) {
630
+ [super updateLayoutMetrics:layoutMetrics oldLayoutMetrics:oldLayoutMetrics];
631
+ }
632
+ // when screen is mounted under RNScreensNavigationController it's size is controller
633
+ // by the navigation controller itself. That is, it is set to fill space of
634
+ // the controller. In that case we ignore react layout system from managing
635
+ // the screen dimensions and we wait for the screen VC to update and then we
636
+ // pass the dimensions to ui view manager to take into account when laying out
637
+ // subviews
638
+ // Explanation taken from `reactSetFrame`, which is old arch equivalent of this code.
456
639
  }
457
640
 
458
- // if the returned vc is a child, it means that it can provide config;
459
- // if the returned vc is self, it means that there is no child for config and self has config to provide,
460
- // so we return self which results in asking self for preferredStatusBarStyle/Animation etc.;
461
- // if the returned vc is nil, it means none of children could provide config and self does not have config either,
462
- // so if it was asked by parent, it will fallback to parent's option, or use default option if it is the top Screen
463
- - (UIViewController *)findChildVCForConfigAndTrait:(RNSWindowTrait)trait includingModals:(BOOL)includingModals
464
- {
465
- UIViewController *lastViewController = [[self childViewControllers] lastObject];
466
- if ([self.presentedViewController isKindOfClass:[RNSScreen class]]) {
467
- lastViewController = self.presentedViewController;
468
- // we don't want to allow controlling of status bar appearance when we present non-fullScreen modal
469
- // and it is not possible if `modalPresentationCapturesStatusBarAppearance` is not set to YES, so even
470
- // if we went into a modal here and ask it, it wouldn't take any effect. For fullScreen modals, the system
471
- // asks them by itself, so we can stop traversing here.
472
- // for screen orientation, we need to start the search again from that modal
473
- return !includingModals
474
- ? nil
475
- : [(RNSScreen *)lastViewController findChildVCForConfigAndTrait:trait includingModals:includingModals]
476
- ?: lastViewController;
477
- }
641
+ #pragma mark - Paper specific
642
+ #else
478
643
 
479
- UIViewController *selfOrNil = [self hasTraitSet:trait] ? self : nil;
480
- if (lastViewController == nil) {
481
- return selfOrNil;
482
- } else {
483
- if ([lastViewController conformsToProtocol:@protocol(RNScreensViewControllerDelegate)]) {
484
- // If there is a child (should be VC of ScreenContainer or ScreenStack), that has a child that could provide the
485
- // trait, we recursively go into its findChildVCForConfig, and if one of the children has the trait set, we return
486
- // it, otherwise we return self if this VC has config, and nil if it doesn't we use
487
- // `childViewControllerForStatusBarStyle` for all options since the behavior is the same for all of them
488
- UIViewController *childScreen = [lastViewController childViewControllerForStatusBarStyle];
489
- if ([childScreen isKindOfClass:[RNSScreen class]]) {
490
- return [(RNSScreen *)childScreen findChildVCForConfigAndTrait:trait includingModals:includingModals]
491
- ?: selfOrNil;
492
- } else {
493
- return selfOrNil;
494
- }
495
- } else {
496
- // child vc is not from this library, so we don't ask it
497
- return selfOrNil;
498
- }
499
- }
644
+ - (void)setPointerEvents:(RCTPointerEvents)pointerEvents
645
+ {
646
+ // pointer events settings are managed by the parent screen container, we ignore
647
+ // any attempt of setting that via React props
500
648
  }
501
649
 
502
- - (BOOL)hasTraitSet:(RNSWindowTrait)trait
650
+ - (void)reactSetFrame:(CGRect)frame
503
651
  {
504
- switch (trait) {
505
- case RNSWindowTraitStyle: {
506
- return ((RNSScreenView *)self.view).hasStatusBarStyleSet;
507
- }
508
- case RNSWindowTraitAnimation: {
509
- return ((RNSScreenView *)self.view).hasStatusBarAnimationSet;
510
- }
511
- case RNSWindowTraitHidden: {
512
- return ((RNSScreenView *)self.view).hasStatusBarHiddenSet;
513
- }
514
- case RNSWindowTraitOrientation: {
515
- return ((RNSScreenView *)self.view).hasOrientationSet;
516
- }
517
- case RNSWindowTraitHomeIndicatorHidden: {
518
- return ((RNSScreenView *)self.view).hasHomeIndicatorHiddenSet;
519
- }
520
- default: {
521
- RCTLogError(@"Unknown trait passed: %d", (int)trait);
522
- }
652
+ _reactFrame = frame;
653
+ UIViewController *parentVC = self.reactViewController.parentViewController;
654
+ if (parentVC != nil && ![parentVC isKindOfClass:[RNScreensNavigationController class]]) {
655
+ [super reactSetFrame:frame];
523
656
  }
524
- return NO;
657
+ // when screen is mounted under RNScreensNavigationController it's size is controller
658
+ // by the navigation controller itself. That is, it is set to fill space of
659
+ // the controller. In that case we ignore react layout system from managing
660
+ // the screen dimensions and we wait for the screen VC to update and then we
661
+ // pass the dimensions to ui view manager to take into account when laying out
662
+ // subviews
525
663
  }
526
664
 
665
+ - (void)invalidate
666
+ {
667
+ _controller = nil;
668
+ }
527
669
  #endif
528
670
 
529
- - (void)viewDidLayoutSubviews
530
- {
531
- [super viewDidLayoutSubviews];
671
+ @end
532
672
 
533
- // The below code makes the screen view adapt dimensions provided by the system. We take these
534
- // into account only when the view is mounted under RNScreensNavigationController in which case system
535
- // provides additional padding to account for possible header, and in the case when screen is
536
- // shown as a native modal, as the final dimensions of the modal on iOS 12+ are shorter than the
537
- // screen size
538
- BOOL isDisplayedWithinUINavController =
539
- [self.parentViewController isKindOfClass:[RNScreensNavigationController class]];
540
- BOOL isPresentedAsNativeModal = self.parentViewController == nil && self.presentingViewController != nil;
541
- if ((isDisplayedWithinUINavController || isPresentedAsNativeModal) &&
542
- !CGRectEqualToRect(_lastViewFrame, self.view.frame)) {
543
- _lastViewFrame = self.view.frame;
544
- [((RNSScreenView *)self.viewIfLoaded) updateBounds];
545
- }
673
+ #ifdef RN_FABRIC_ENABLED
674
+ Class<RCTComponentViewProtocol> RNSScreenCls(void)
675
+ {
676
+ return RNSScreenView.class;
546
677
  }
678
+ #endif
547
679
 
548
- - (id)findFirstResponder:(UIView *)parent
549
- {
550
- if (parent.isFirstResponder) {
551
- return parent;
552
- }
553
- for (UIView *subView in parent.subviews) {
554
- id responder = [self findFirstResponder:subView];
555
- if (responder != nil) {
556
- return responder;
557
- }
558
- }
559
- return nil;
680
+ #pragma mark - RNSScreen
681
+
682
+ @implementation RNSScreen {
683
+ __weak id _previousFirstResponder;
684
+ CGRect _lastViewFrame;
685
+ RNSScreenView *_initialView;
686
+ UIView *_fakeView;
687
+ CADisplayLink *_animationTimer;
688
+ CGFloat _currentAlpha;
689
+ BOOL _closing;
690
+ BOOL _goingForward;
691
+ int _dismissCount;
692
+ BOOL _isSwiping;
693
+ BOOL _shouldNotify;
560
694
  }
561
695
 
562
- - (void)willMoveToParentViewController:(UIViewController *)parent
696
+ #pragma mark - Common
697
+
698
+ - (instancetype)initWithView:(UIView *)view
563
699
  {
564
- [super willMoveToParentViewController:parent];
565
- if (parent == nil) {
566
- id responder = [self findFirstResponder:self.view];
567
- if (responder != nil) {
568
- _previousFirstResponder = responder;
569
- }
700
+ if (self = [super init]) {
701
+ self.view = view;
702
+ _fakeView = [UIView new];
703
+ _shouldNotify = YES;
704
+ #ifdef RN_FABRIC_ENABLED
705
+ _initialView = (RNSScreenView *)view;
706
+ #endif
570
707
  }
708
+ return self;
571
709
  }
572
710
 
711
+ // TODO: Find out why this is executed when screen is going out
573
712
  - (void)viewWillAppear:(BOOL)animated
574
713
  {
575
714
  [super viewWillAppear:animated];
576
-
577
715
  if (!_isSwiping) {
578
- [((RNSScreenView *)self.view) notifyWillAppear];
716
+ [self.screenView notifyWillAppear];
579
717
  if (self.transitionCoordinator.isInteractive) {
580
718
  // we started dismissing with swipe gesture
581
719
  _isSwiping = YES;
@@ -587,7 +725,6 @@
587
725
  }
588
726
 
589
727
  [self hideHeaderIfNecessary];
590
-
591
728
  // as per documentation of these methods
592
729
  _goingForward = [self isBeingPresented] || [self isMovingToParentViewController];
593
730
 
@@ -599,46 +736,16 @@
599
736
  }
600
737
  }
601
738
 
602
- - (void)hideHeaderIfNecessary
603
- {
604
- #if !TARGET_OS_TV
605
- // On iOS >=13, there is a bug when user transitions from screen with active search bar to screen without header
606
- // In that case default iOS header will be shown. To fix this we hide header when the screens that appears has header
607
- // hidden and search bar was active on previous screen. We need to do it asynchronously, because default header is
608
- // added after viewWillAppear.
609
- if (@available(iOS 13.0, *)) {
610
- NSUInteger currentIndex = [self.navigationController.viewControllers indexOfObject:self];
611
-
612
- if (currentIndex > 0 && [self.view.reactSubviews[0] isKindOfClass:[RNSScreenStackHeaderConfig class]]) {
613
- UINavigationItem *prevNavigationItem =
614
- [self.navigationController.viewControllers objectAtIndex:currentIndex - 1].navigationItem;
615
- RNSScreenStackHeaderConfig *config = ((RNSScreenStackHeaderConfig *)self.view.reactSubviews[0]);
616
-
617
- BOOL wasSearchBarActive = prevNavigationItem.searchController.active;
618
- BOOL shouldHideHeader = config.hide;
619
-
620
- if (wasSearchBarActive && shouldHideHeader) {
621
- dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, 0);
622
- dispatch_after(popTime, dispatch_get_main_queue(), ^(void) {
623
- [self.navigationController setNavigationBarHidden:YES animated:NO];
624
- });
625
- }
626
- }
627
- }
628
- #endif
629
- }
630
-
631
739
  - (void)viewWillDisappear:(BOOL)animated
632
740
  {
633
741
  [super viewWillDisappear:animated];
634
-
635
742
  if (!self.transitionCoordinator.isInteractive) {
636
743
  // user might have long pressed ios 14 back button item,
637
744
  // so he can go back more than one screen and we need to dismiss more screens in JS stack then.
638
745
  // We check it by calculating the difference between the index of currently displayed screen
639
746
  // and the index of the target screen, which is the view of topViewController at this point.
640
747
  // If the value is lower than 1, it means we are dismissing a modal, or navigating forward, or going back with JS.
641
- int selfIndex = [self getIndexOfView:self.view];
748
+ int selfIndex = [self getIndexOfView:self.screenView];
642
749
  int targetIndex = [self getIndexOfView:self.navigationController.topViewController.view];
643
750
  _dismissCount = selfIndex - targetIndex > 0 ? selfIndex - targetIndex : 1;
644
751
  } else {
@@ -647,7 +754,7 @@
647
754
 
648
755
  // same flow as in viewWillAppear
649
756
  if (!_isSwiping) {
650
- [((RNSScreenView *)self.view) notifyWillDisappear];
757
+ [self.screenView notifyWillDisappear];
651
758
  if (self.transitionCoordinator.isInteractive) {
652
759
  _isSwiping = YES;
653
760
  }
@@ -668,11 +775,10 @@
668
775
  - (void)viewDidAppear:(BOOL)animated
669
776
  {
670
777
  [super viewDidAppear:animated];
671
-
672
778
  if (!_isSwiping || _shouldNotify) {
673
779
  // we are going forward or dismissing without swipe
674
780
  // or successfully swiped back
675
- [((RNSScreenView *)self.view) notifyAppear];
781
+ [self.screenView notifyAppear];
676
782
  [self notifyTransitionProgress:1.0 closing:NO goingForward:_goingForward];
677
783
  }
678
784
 
@@ -683,54 +789,57 @@
683
789
  - (void)viewDidDisappear:(BOOL)animated
684
790
  {
685
791
  [super viewDidDisappear:animated];
686
-
792
+ #ifdef RN_FABRIC_ENABLED
793
+ [self resetViewToScreen];
794
+ #endif
687
795
  if (self.parentViewController == nil && self.presentingViewController == nil) {
688
- if (((RNSScreenView *)self.view).preventNativeDismiss) {
796
+ if (self.screenView.preventNativeDismiss) {
689
797
  // if we want to prevent the native dismiss, we do not send dismissal event,
690
798
  // but instead call `updateContainer`, which restores the JS navigation stack
691
- [((RNSScreenView *)self.view).reactSuperview updateContainer];
692
- [((RNSScreenView *)self.view) notifyDismissCancelledWithDismissCount:_dismissCount];
799
+ [self.screenView.reactSuperview updateContainer];
800
+ [self.screenView notifyDismissCancelledWithDismissCount:_dismissCount];
693
801
  } else {
694
802
  // screen dismissed, send event
695
- [((RNSScreenView *)self.view) notifyDismissedWithCount:_dismissCount];
803
+ [self.screenView notifyDismissedWithCount:_dismissCount];
696
804
  }
697
805
  }
698
-
699
806
  // same flow as in viewDidAppear
700
807
  if (!_isSwiping || _shouldNotify) {
701
- [((RNSScreenView *)self.view) notifyDisappear];
808
+ [self.screenView notifyDisappear];
702
809
  [self notifyTransitionProgress:1.0 closing:YES goingForward:_goingForward];
703
810
  }
704
811
 
705
812
  _isSwiping = NO;
706
813
  _shouldNotify = YES;
707
-
708
- [self traverseForScrollView:self.view];
814
+ #ifdef RN_FABRIC_ENABLED
815
+ #else
816
+ [self traverseForScrollView:self.screenView];
817
+ #endif
709
818
  }
710
819
 
711
- - (void)traverseForScrollView:(UIView *)view
820
+ - (void)viewDidLayoutSubviews
712
821
  {
713
- if (![[self.view valueForKey:@"_bridge"] valueForKey:@"_jsThread"]) {
714
- // we don't want to send `scrollViewDidEndDecelerating` event to JS before the JS thread is ready
715
- return;
716
- }
717
- if ([view isKindOfClass:[UIScrollView class]] &&
718
- ([[(UIScrollView *)view delegate] respondsToSelector:@selector(scrollViewDidEndDecelerating:)])) {
719
- [[(UIScrollView *)view delegate] scrollViewDidEndDecelerating:(id)view];
720
- }
721
- [view.subviews enumerateObjectsUsingBlock:^(__kindof UIView *_Nonnull obj, NSUInteger idx, BOOL *_Nonnull stop) {
722
- [self traverseForScrollView:obj];
723
- }];
724
- }
822
+ [super viewDidLayoutSubviews];
725
823
 
726
- - (int)getIndexOfView:(UIView *)view
727
- {
728
- return (int)[[self.view.reactSuperview reactSubviews] indexOfObject:view];
729
- }
824
+ // The below code makes the screen view adapt dimensions provided by the system. We take these
825
+ // into account only when the view is mounted under RNScreensNavigationController in which case system
826
+ // provides additional padding to account for possible header, and in the case when screen is
827
+ // shown as a native modal, as the final dimensions of the modal on iOS 12+ are shorter than the
828
+ // screen size
829
+ BOOL isDisplayedWithinUINavController =
830
+ [self.parentViewController isKindOfClass:[RNScreensNavigationController class]];
831
+ BOOL isPresentedAsNativeModal = self.parentViewController == nil && self.presentingViewController != nil;
730
832
 
731
- - (int)getParentChildrenCount
732
- {
733
- return (int)[[self.view.reactSuperview reactSubviews] count];
833
+ if (isDisplayedWithinUINavController || isPresentedAsNativeModal) {
834
+ #ifdef RN_FABRIC_ENABLED
835
+ [self.screenView updateBounds];
836
+ #else
837
+ if (!CGRectEqualToRect(_lastViewFrame, self.screenView.frame)) {
838
+ _lastViewFrame = self.screenView.frame;
839
+ [((RNSScreenView *)self.viewIfLoaded) updateBounds];
840
+ }
841
+ #endif
842
+ }
734
843
  }
735
844
 
736
845
  - (void)notifyFinishTransitioning
@@ -741,6 +850,31 @@
741
850
  [RNSScreenWindowTraits updateWindowTraits];
742
851
  }
743
852
 
853
+ - (void)willMoveToParentViewController:(UIViewController *)parent
854
+ {
855
+ [super willMoveToParentViewController:parent];
856
+ if (parent == nil) {
857
+ id responder = [self findFirstResponder:self.screenView];
858
+ if (responder != nil) {
859
+ _previousFirstResponder = responder;
860
+ }
861
+ }
862
+ }
863
+
864
+ - (id)findFirstResponder:(UIView *)parent
865
+ {
866
+ if (parent.isFirstResponder) {
867
+ return parent;
868
+ }
869
+ for (UIView *subView in parent.subviews) {
870
+ id responder = [self findFirstResponder:subView];
871
+ if (responder != nil) {
872
+ return responder;
873
+ }
874
+ }
875
+ return nil;
876
+ }
877
+
744
878
  #pragma mark - transition progress related methods
745
879
 
746
880
  - (void)setupProgressNotification
@@ -775,9 +909,239 @@
775
909
 
776
910
  - (void)notifyTransitionProgress:(double)progress closing:(BOOL)closing goingForward:(BOOL)goingForward
777
911
  {
778
- [((RNSScreenView *)self.view) notifyTransitionProgress:progress closing:closing goingForward:goingForward];
912
+ if ([self.view isKindOfClass:[RNSScreenView class]]) {
913
+ // if the view is already snapshot, there is not sense in sending progress since on JS side
914
+ // the component is already not present
915
+ [(RNSScreenView *)self.view notifyTransitionProgress:progress closing:closing goingForward:goingForward];
916
+ }
779
917
  }
780
918
 
919
+ #if !TARGET_OS_TV
920
+ // if the returned vc is a child, it means that it can provide config;
921
+ // if the returned vc is self, it means that there is no child for config and self has config to provide,
922
+ // so we return self which results in asking self for preferredStatusBarStyle/Animation etc.;
923
+ // if the returned vc is nil, it means none of children could provide config and self does not have config either,
924
+ // so if it was asked by parent, it will fallback to parent's option, or use default option if it is the top Screen
925
+ - (UIViewController *)findChildVCForConfigAndTrait:(RNSWindowTrait)trait includingModals:(BOOL)includingModals
926
+ {
927
+ UIViewController *lastViewController = [[self childViewControllers] lastObject];
928
+ if ([self.presentedViewController isKindOfClass:[RNSScreen class]]) {
929
+ lastViewController = self.presentedViewController;
930
+ // we don't want to allow controlling of status bar appearance when we present non-fullScreen modal
931
+ // and it is not possible if `modalPresentationCapturesStatusBarAppearance` is not set to YES, so even
932
+ // if we went into a modal here and ask it, it wouldn't take any effect. For fullScreen modals, the system
933
+ // asks them by itself, so we can stop traversing here.
934
+ // for screen orientation, we need to start the search again from that modal
935
+ return !includingModals
936
+ ? nil
937
+ : [(RNSScreen *)lastViewController findChildVCForConfigAndTrait:trait includingModals:includingModals]
938
+ ?: lastViewController;
939
+ }
940
+
941
+ UIViewController *selfOrNil = [self hasTraitSet:trait] ? self : nil;
942
+ if (lastViewController == nil) {
943
+ return selfOrNil;
944
+ } else {
945
+ if ([lastViewController conformsToProtocol:@protocol(RNScreensViewControllerDelegate)]) {
946
+ // If there is a child (should be VC of ScreenContainer or ScreenStack), that has a child that could provide the
947
+ // trait, we recursively go into its findChildVCForConfig, and if one of the children has the trait set, we return
948
+ // it, otherwise we return self if this VC has config, and nil if it doesn't we use
949
+ // `childViewControllerForStatusBarStyle` for all options since the behavior is the same for all of them
950
+ UIViewController *childScreen = [lastViewController childViewControllerForStatusBarStyle];
951
+ if ([childScreen isKindOfClass:[RNSScreen class]]) {
952
+ return [(RNSScreen *)childScreen findChildVCForConfigAndTrait:trait includingModals:includingModals]
953
+ ?: selfOrNil;
954
+ } else {
955
+ return selfOrNil;
956
+ }
957
+ } else {
958
+ // child vc is not from this library, so we don't ask it
959
+ return selfOrNil;
960
+ }
961
+ }
962
+ }
963
+
964
+ - (BOOL)hasTraitSet:(RNSWindowTrait)trait
965
+ {
966
+ switch (trait) {
967
+ case RNSWindowTraitStyle: {
968
+ return self.screenView.hasStatusBarStyleSet;
969
+ }
970
+ case RNSWindowTraitAnimation: {
971
+ return self.screenView.hasStatusBarAnimationSet;
972
+ }
973
+ case RNSWindowTraitHidden: {
974
+ return self.screenView.hasStatusBarHiddenSet;
975
+ }
976
+ case RNSWindowTraitOrientation: {
977
+ return self.screenView.hasOrientationSet;
978
+ }
979
+ case RNSWindowTraitHomeIndicatorHidden: {
980
+ return self.screenView.hasHomeIndicatorHiddenSet;
981
+ }
982
+ default: {
983
+ RCTLogError(@"Unknown trait passed: %d", (int)trait);
984
+ }
985
+ }
986
+ return NO;
987
+ }
988
+
989
+ - (UIViewController *)childViewControllerForStatusBarHidden
990
+ {
991
+ UIViewController *vc = [self findChildVCForConfigAndTrait:RNSWindowTraitHidden includingModals:NO];
992
+ return vc == self ? nil : vc;
993
+ }
994
+
995
+ - (BOOL)prefersStatusBarHidden
996
+ {
997
+ return self.screenView.statusBarHidden;
998
+ }
999
+
1000
+ - (UIViewController *)childViewControllerForStatusBarStyle
1001
+ {
1002
+ UIViewController *vc = [self findChildVCForConfigAndTrait:RNSWindowTraitStyle includingModals:NO];
1003
+ return vc == self ? nil : vc;
1004
+ }
1005
+
1006
+ - (UIStatusBarStyle)preferredStatusBarStyle
1007
+ {
1008
+ return [RNSScreenWindowTraits statusBarStyleForRNSStatusBarStyle:self.screenView.statusBarStyle];
1009
+ }
1010
+
1011
+ - (UIStatusBarAnimation)preferredStatusBarUpdateAnimation
1012
+ {
1013
+ UIViewController *vc = [self findChildVCForConfigAndTrait:RNSWindowTraitAnimation includingModals:NO];
1014
+
1015
+ if ([vc isKindOfClass:[RNSScreen class]]) {
1016
+ return ((RNSScreen *)vc).screenView.statusBarAnimation;
1017
+ }
1018
+ return UIStatusBarAnimationFade;
1019
+ }
1020
+
1021
+ - (UIInterfaceOrientationMask)supportedInterfaceOrientations
1022
+ {
1023
+ UIViewController *vc = [self findChildVCForConfigAndTrait:RNSWindowTraitOrientation includingModals:YES];
1024
+
1025
+ if ([vc isKindOfClass:[RNSScreen class]]) {
1026
+ return ((RNSScreen *)vc).screenView.screenOrientation;
1027
+ }
1028
+ return UIInterfaceOrientationMaskAllButUpsideDown;
1029
+ }
1030
+
1031
+ - (UIViewController *)childViewControllerForHomeIndicatorAutoHidden
1032
+ {
1033
+ UIViewController *vc = [self findChildVCForConfigAndTrait:RNSWindowTraitHomeIndicatorHidden includingModals:YES];
1034
+ return vc == self ? nil : vc;
1035
+ }
1036
+
1037
+ - (BOOL)prefersHomeIndicatorAutoHidden
1038
+ {
1039
+ return self.screenView.homeIndicatorHidden;
1040
+ }
1041
+ - (int)getParentChildrenCount
1042
+ {
1043
+ return (int)[[self.screenView.reactSuperview reactSubviews] count];
1044
+ }
1045
+ #endif
1046
+
1047
+ - (int)getIndexOfView:(UIView *)view
1048
+ {
1049
+ return (int)[[self.screenView.reactSuperview reactSubviews] indexOfObject:view];
1050
+ }
1051
+
1052
+ // since on Fabric the view of controller can be a snapshot of type `UIView`,
1053
+ // when we want to check props of ScreenView, we need to get them from _initialView
1054
+ - (RNSScreenView *)screenView
1055
+ {
1056
+ #ifdef RN_FABRIC_ENABLED
1057
+ return _initialView;
1058
+ #else
1059
+ return (RNSScreenView *)self.view;
1060
+ #endif
1061
+ }
1062
+
1063
+ - (void)hideHeaderIfNecessary
1064
+ {
1065
+ #if !TARGET_OS_TV
1066
+ // On iOS >=13, there is a bug when user transitions from screen with active search bar to screen without header
1067
+ // In that case default iOS header will be shown. To fix this we hide header when the screens that appears has header
1068
+ // hidden and search bar was active on previous screen. We need to do it asynchronously, because default header is
1069
+ // added after viewWillAppear.
1070
+ if (@available(iOS 13.0, *)) {
1071
+ NSUInteger currentIndex = [self.navigationController.viewControllers indexOfObject:self];
1072
+
1073
+ // we need to check whether reactSubviews array is empty, because on Fabric child nodes are unmounted first ->
1074
+ // reactSubviews array may be empty
1075
+ if (currentIndex > 0 && [self.screenView.reactSubviews count] > 0 &&
1076
+ [self.screenView.reactSubviews[0] isKindOfClass:[RNSScreenStackHeaderConfig class]]) {
1077
+ UINavigationItem *prevNavigationItem =
1078
+ [self.navigationController.viewControllers objectAtIndex:currentIndex - 1].navigationItem;
1079
+ RNSScreenStackHeaderConfig *config = ((RNSScreenStackHeaderConfig *)self.screenView.reactSubviews[0]);
1080
+
1081
+ BOOL wasSearchBarActive = prevNavigationItem.searchController.active;
1082
+
1083
+ #ifdef RN_FABRIC_ENABLED
1084
+ BOOL shouldHideHeader = !config.show;
1085
+ #else
1086
+ BOOL shouldHideHeader = config.hide;
1087
+ #endif
1088
+
1089
+ if (wasSearchBarActive && shouldHideHeader) {
1090
+ dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, 0);
1091
+ dispatch_after(popTime, dispatch_get_main_queue(), ^(void) {
1092
+ [self.navigationController setNavigationBarHidden:YES animated:NO];
1093
+ });
1094
+ }
1095
+ }
1096
+ }
1097
+ #endif
1098
+ }
1099
+
1100
+ #ifdef RN_FABRIC_ENABLED
1101
+ #pragma mark - Fabric specific
1102
+
1103
+ - (void)setViewToSnapshot:(UIView *)snapshot
1104
+ {
1105
+ // modals of native stack seem not to support
1106
+ // changing their view by just setting the view
1107
+ if (_initialView.stackPresentation != RNSScreenStackPresentationPush) {
1108
+ UIView *superView = self.view.superview;
1109
+ [self.view removeFromSuperview];
1110
+ self.view = snapshot;
1111
+ [superView addSubview:self.view];
1112
+ } else {
1113
+ [self.view removeFromSuperview];
1114
+ self.view = snapshot;
1115
+ }
1116
+ }
1117
+
1118
+ - (void)resetViewToScreen
1119
+ {
1120
+ if (self.view != _initialView) {
1121
+ [self.view removeFromSuperview];
1122
+ self.view = _initialView;
1123
+ }
1124
+ }
1125
+
1126
+ #else
1127
+ #pragma mark - Paper specific
1128
+
1129
+ - (void)traverseForScrollView:(UIView *)view
1130
+ {
1131
+ if (![[self.view valueForKey:@"_bridge"] valueForKey:@"_jsThread"]) {
1132
+ // we don't want to send `scrollViewDidEndDecelerating` event to JS before the JS thread is ready
1133
+ return;
1134
+ }
1135
+ if ([view isKindOfClass:[UIScrollView class]] &&
1136
+ ([[(UIScrollView *)view delegate] respondsToSelector:@selector(scrollViewDidEndDecelerating:)])) {
1137
+ [[(UIScrollView *)view delegate] scrollViewDidEndDecelerating:(id)view];
1138
+ }
1139
+ [view.subviews enumerateObjectsUsingBlock:^(__kindof UIView *_Nonnull obj, NSUInteger idx, BOOL *_Nonnull stop) {
1140
+ [self traverseForScrollView:obj];
1141
+ }];
1142
+ }
1143
+ #endif
1144
+
781
1145
  @end
782
1146
 
783
1147
  @implementation RNSScreenManager
@@ -789,6 +1153,8 @@ RCT_REMAP_VIEW_PROPERTY(activityState, activityStateOrNil, NSNumber)
789
1153
  RCT_EXPORT_VIEW_PROPERTY(customAnimationOnSwipe, BOOL);
790
1154
  RCT_EXPORT_VIEW_PROPERTY(fullScreenSwipeEnabled, BOOL);
791
1155
  RCT_EXPORT_VIEW_PROPERTY(gestureEnabled, BOOL)
1156
+ RCT_EXPORT_VIEW_PROPERTY(gestureResponseDistance, NSDictionary)
1157
+ RCT_EXPORT_VIEW_PROPERTY(hideKeyboardOnSwipe, BOOL)
792
1158
  RCT_EXPORT_VIEW_PROPERTY(preventNativeDismiss, BOOL)
793
1159
  RCT_EXPORT_VIEW_PROPERTY(replaceAnimation, RNSScreenReplaceAnimation)
794
1160
  RCT_EXPORT_VIEW_PROPERTY(stackPresentation, RNSScreenStackPresentation)
@@ -870,6 +1236,16 @@ RCT_ENUM_CONVERTER(
870
1236
  integerValue)
871
1237
 
872
1238
  #if !TARGET_OS_TV
1239
+ RCT_ENUM_CONVERTER(
1240
+ UIStatusBarAnimation,
1241
+ (@{
1242
+ @"none" : @(UIStatusBarAnimationNone),
1243
+ @"fade" : @(UIStatusBarAnimationFade),
1244
+ @"slide" : @(UIStatusBarAnimationSlide)
1245
+ }),
1246
+ UIStatusBarAnimationNone,
1247
+ integerValue)
1248
+
873
1249
  RCT_ENUM_CONVERTER(
874
1250
  RNSStatusBarStyle,
875
1251
  (@{