react-native-screens 1.0.0-alpha.2 → 1.0.0-alpha.23

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 (27) hide show
  1. package/README.md +163 -1
  2. package/RNScreens.podspec +24 -0
  3. package/android/build.gradle +17 -10
  4. package/android/src/main/java/com/swmansion/rnscreens/LifecycleHelper.java +4 -8
  5. package/android/src/main/java/com/swmansion/rnscreens/{RNScreenPackage.java → RNScreensPackage.java} +1 -2
  6. package/android/src/main/java/com/swmansion/rnscreens/Screen.java +44 -3
  7. package/android/src/main/java/com/swmansion/rnscreens/ScreenContainer.java +61 -18
  8. package/android/src/main/java/com/swmansion/rnscreens/ScreenContainerViewManager.java +1 -1
  9. package/android/src/main/java/com/swmansion/rnscreens/ScreenViewManager.java +3 -3
  10. package/ios/RNSScreen.h +2 -0
  11. package/ios/RNSScreen.m +51 -7
  12. package/ios/RNSScreenContainer.h +0 -8
  13. package/ios/RNSScreenContainer.m +93 -24
  14. package/ios/RNScreens.xcodeproj/project.pbxproj +170 -7
  15. package/package.json +11 -6
  16. package/src/screens.d.ts +22 -0
  17. package/src/screens.native.js +120 -0
  18. package/src/screens.web.js +113 -0
  19. package/android/src/main/java/com/swmansion/rnscreens/ScreenStack.java +0 -35
  20. package/android/src/main/java/com/swmansion/rnscreens/ScreenStackViewManager.java +0 -24
  21. package/ios/RNSScreenStack.h +0 -14
  22. package/ios/RNSScreenStack.m +0 -245
  23. package/ios/RNScreens.xcodeproj/project.xcworkspace/contents.xcworkspacedata +0 -7
  24. package/ios/RNScreens.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +0 -8
  25. package/ios/RNScreens.xcodeproj/project.xcworkspace/xcuserdata/mdk.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
  26. package/ios/RNScreens.xcodeproj/xcuserdata/mdk.xcuserdatad/xcschemes/xcschememanagement.plist +0 -24
  27. package/src/screens.js +0 -14
package/README.md CHANGED
@@ -1,3 +1,165 @@
1
1
  # react-native-screens
2
2
 
3
- First incomplete navigation solution for your React Native app
3
+ This project aims to expose native navigation container components to React Native. It is not designed to be used as a standalone library but rather as a dependency of a [full-featured navigation library](https://github.com/react-navigation/react-navigation).
4
+
5
+ ## How can I take advantage of that?
6
+
7
+ Screens are already integrated with the React Native's most popular navigation library [react-navigation](https://github.com/react-navigation/react-navigation) and [Expo](https://expo.io).
8
+ Read usage guide depending on if you are [using Expo](#usage-in-expo-with-react-navigation) or [not](#usage-with-react-navigation-without-expo).
9
+
10
+ ## Usage with [react-navigation](https://github.com/react-navigation/react-navigation) (without Expo)
11
+
12
+ Screens support is built into [react-navigation](https://github.com/react-navigation/react-navigation) starting from version [2.14.0](https://github.com/react-navigation/react-navigation/releases/tag/2.14.0) for all the different navigator types (stack, tab, drawer, etc). We plan on adding it to other navigators in near future.
13
+
14
+ To configure react-navigation to use screens instead of plain RN Views for rendering screen views, follow the steps below:
15
+
16
+ 1. Add this library as a depedency to your project:
17
+ ```
18
+ yarn add react-native-screens
19
+ ```
20
+
21
+ 2. Link native modules this library ships with into your app:
22
+ ```
23
+ react-native link react-native-screens
24
+ ```
25
+
26
+ > If you are not familiar with the concept of linking libraries [read on here](https://facebook.github.io/react-native/docs/linking-libraries-ios).
27
+
28
+ 3. Enable screens support before any of your navigation screen renders. Add the following code to your main application file (e.g. App.js):
29
+ ```js
30
+ import { useScreens } from 'react-native-screens';
31
+
32
+ useScreens();
33
+ ```
34
+
35
+ Note that the above code need to execute before first render of a navigation screen. You can check Example's app [App.js](https://github.com/kmagiera/react-native-screens/blob/master/Example/App.js#L16) file as a reference.
36
+
37
+ 4. On Android change your main activity class to extend [`ReactFragmentActivity`](https://github.com/facebook/react-native/blob/0.57-stable/ReactAndroid/src/main/java/com/facebook/react/ReactFragmentActivity.java). The file you'll have to change is likely called `MainActivity.java` unless you customized it after creating your project:
38
+ ```diff
39
+ -import com.facebook.react.ReactActivity;
40
+ +import android.os.Bundle;
41
+ +import com.facebook.react.ReactFragmentActivity;
42
+ import com.facebook.react.ReactActivityDelegate;
43
+
44
+ -public class MainActivity extends ReactActivity {
45
+ +public class MainActivity extends ReactFragmentActivity {
46
+ +
47
+ + @Override
48
+ + protected void onCreate(Bundle savedInstanceState) {
49
+ + super.onCreate(null);
50
+ + }
51
+
52
+ @Override
53
+ protected String getMainComponentName() {
54
+ ```
55
+
56
+ 5. Make sure that the version of [react-navigation](https://github.com/react-navigation/react-navigation) you are using is 2.14.0 or higher
57
+
58
+ 5. You are all set 🎉 – when screens are enabled in your application code react-navigation will automatically use them instead of relying on plain React Native Views.
59
+
60
+ ## Usage in Expo with [react-navigation](https://github.com/react-navigation/react-navigation)
61
+
62
+ Screens support is built into Expo [SDK 30](https://blog.expo.io/expo-sdk-30-0-0-is-now-available-e64d8b1db2a7) and react-navigation starting from [2.14.0](https://github.com/react-navigation/react-navigation/releases/tag/2.14.0). Make sure your app use these versions before you start.
63
+
64
+ 1. Add screens library as dependency to your project – you can skip this step when using snack as the dependency will be imported when you import it in one of the JS files
65
+ ```
66
+ yarn add react-native-screens
67
+ ```
68
+
69
+ 2. Open your App.js file and add the following snippet somewhere near the top of the file (e.g. right after import statements):
70
+ ```
71
+ import { useScreens } from 'react-native-screens';
72
+
73
+ useScreens();
74
+ ```
75
+
76
+ 3. That's all 🎉 – enjoy faster navigation in your Expo app. Keep in mind screens are in pretty early phase so please report if you discover some issues.
77
+
78
+ ## Interop with [react-native-navigation](https://github.com/wix/react-native-navigation)
79
+
80
+ React-native-navigation library already uses native containers for rendering navigation scenes so wrapping these scenes with `<ScreenContainer>` or `<Screen>` component does not provide any benefits. Yet if you would like to build a component that uses screens primitives under the hood (for example a view pager component) it is safe to use `<ScreenContainer>` and `<Screen>` components for that as these work out of the box when rendered on react-native-navigation scenes.
81
+
82
+ ## Interop with other libraries
83
+
84
+ This library should work out of the box with all existing react-native libraries. If you expirience problems with interoperability please [report an issue](https://github.com/kmagiera/react-native-screens/issues).
85
+
86
+
87
+ ## Guide for navigation library authors
88
+
89
+ If you are building navigation library you may want to use react-native-screens to have a control which parts of the react component tree are attached to the native view hierarchy.
90
+ To do that react-native-screens provides you with two components documented below:
91
+
92
+ ### `<ScreenContainer/>`
93
+
94
+ This component is a container for one or more `Screen` components.
95
+ It does not accept other component types are direct children.
96
+ The role of container is to control which of its children screens should be attached to the view hierarchy.
97
+ It does that by monitoring `active` property of each of its children.
98
+ It it possible to have as many `active` children as you'd like but in order for the component to be the most effictent we should keep the number of active screens to the minimum.
99
+ In a case of stack navigator or tabs navigator we only want to have one active screen (the top most view on a stack or the selected tab).
100
+ Then for the time of transitioning between views we may want to activate a second screen for the duration of transition, and then go back to just one active screen.
101
+
102
+ ### `<Screen/>`
103
+
104
+ This component is a container for views we want to display on a navigation screen.
105
+ It is designed to only be rendered as a direct child of `ScreenContainer`.
106
+ In addition to plain React Native [View props](http://facebook.github.io/react-native/docs/view#props) this component only accepts a single additional property called `active`.
107
+ When `active` is set to `0`, the parent container will detach its views from the native view hierarchy.
108
+ Otherwise the views will be attached as long as the parent container is attached too.
109
+
110
+ ### Example
111
+
112
+ ```js
113
+ <ScreenContainer>
114
+ <Screen>{tab1}</Screen>
115
+ <Screen active={1}>{tab2}</Screen>
116
+ <Screen>{tab3}</Screen>
117
+ </ScreenContainer>
118
+ ```
119
+
120
+ ## Guide for native component authors
121
+
122
+ If you are adding a new native component to be used from the React Native app, you may want it to respond to navigation lifecycle events.
123
+
124
+ Good example is a map component that shows current user location. When component is on the top-most screen it should register for location updates and display users location on the map. But if we navigate away from a screen that has a map, e.g. by pushing new screen on top of it or if it is in one of a tabs and user just switched to the previous app, we may want to stop listening to location updates.
125
+
126
+ In order to achieve that we need to know at the native component level when our native view goes out of sight. With react-native-screens you can do that in the following way:
127
+
128
+ ### Navigation lifecycle on iOS
129
+
130
+ In order for your native view on iOS to be notified when its parent navigation container goes into background override `didMoveToWindow` method:
131
+
132
+ ```objective-c
133
+ - (void)didMoveToWindow
134
+ {
135
+ [super didMoveToWindow];
136
+ BOOL isVisible = self.superview && self.window;
137
+ if (isVisible) {
138
+ // navigation container this view belongs to became visible
139
+ } else {
140
+ // we are in a background
141
+ }
142
+ }
143
+ ```
144
+
145
+ You can check our example app for a fully functional demo see [RNSSampleLifecycleAwareView.m](https://github.com/kmagiera/react-native-screens/blob/master/Example/ios/ScreensExample/RNSSampleLifecycleAwareView.m) for more details.
146
+
147
+ ### Navigation lifecycle on Android
148
+
149
+ On Android you can use [LifecycleObserver](https://developer.android.com/reference/android/arch/lifecycle/LifecycleObserver) interface which is a part of Android compat library to make your view handle lifecycle events.
150
+ Check [LifecycleAwareView.java](https://github.com/kmagiera/react-native-screens/blob/master/Example/android/app/src/main/java/com/swmansion/rnscreens/example/LifecycleAwareView.java) from our example app for more details on that.
151
+
152
+ In addition to that you will need to register for receiving these updates. This can be done using [`LifecycleHelper.register`](https://github.com/kmagiera/react-native-screens/blob/master/android/src/main/java/com/swmansion/rnscreens/LifecycleHelper.java#L50).
153
+ Remember to call [`LifecycleHelper.unregister`](https://github.com/kmagiera/react-native-screens/blob/master/android/src/main/java/com/swmansion/rnscreens/LifecycleHelper.java#L59) before the view is dropped.
154
+ Please refer to [SampleLifecycleAwareViewManager.java](https://github.com/kmagiera/react-native-screens/blob/master/Example/android/app/src/main/java/com/swmansion/rnscreens/example/SampleLifecycleAwareViewManager.java) from our example app to see what are the best ways of using the above methods.
155
+
156
+ ## License
157
+
158
+ React native screens library is licensed under [The MIT License](LICENSE).
159
+
160
+ ## Credits
161
+
162
+ This project is supported by amazing people from [Expo.io](https://expo.io) and [Software Mansion](https://swmansion.com)
163
+
164
+ [![expo](https://avatars2.githubusercontent.com/u/12504344?v=3&s=100 "Expo.io")](https://expo.io)
165
+ [![swm](https://avatars1.githubusercontent.com/u/6952717?v=3&s=100 "Software Mansion")](https://swmansion.com)
@@ -0,0 +1,24 @@
1
+ require "json"
2
+
3
+ package = JSON.parse(File.read(File.join(__dir__, "package.json")))
4
+
5
+ Pod::Spec.new do |s|
6
+ s.name = "RNScreens"
7
+ s.version = package["version"]
8
+ s.summary = package["description"]
9
+ s.description = <<-DESC
10
+ RNScreens - first incomplete navigation solution for your React Native app
11
+ DESC
12
+ s.homepage = "https://github.com/kmagiera/react-native-screens"
13
+ s.license = "MIT"
14
+ # s.license = { :type => "MIT", :file => "FILE_LICENSE" }
15
+ s.author = { "author" => "author@domain.cn" }
16
+ s.platform = :ios, "7.0"
17
+ s.source = { :git => "https://github.com/kmagiera/react-native-screens.git", :tag => "#{s.version}" }
18
+
19
+ s.source_files = "ios/**/*.{h,m}"
20
+ s.requires_arc = true
21
+
22
+ s.dependency "React"
23
+ end
24
+
@@ -1,26 +1,29 @@
1
1
 
2
2
  buildscript {
3
3
  repositories {
4
+ google()
4
5
  jcenter()
5
6
  }
6
7
 
7
8
  dependencies {
8
- // Matches the RN Hello World template
9
- // https://github.com/facebook/react-native/blob/1e8f3b11027fe0a7514b4fc97d0798d3c64bc895/local-cli/templates/HelloWorld/android/build.gradle#L8
10
- classpath 'com.android.tools.build:gradle:2.2.3'
9
+ classpath 'com.android.tools.build:gradle:3.3.1'
11
10
  }
12
11
  }
13
12
 
14
13
  apply plugin: 'com.android.library'
15
14
  apply plugin: 'maven'
16
15
 
16
+ def safeExtGet(prop, fallback) {
17
+ rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback
18
+ }
19
+
17
20
  android {
18
- compileSdkVersion 26
19
- buildToolsVersion "26.0.3"
21
+ compileSdkVersion safeExtGet('compileSdkVersion', 28)
22
+ buildToolsVersion safeExtGet('buildToolsVersion', '28.0.3')
20
23
 
21
24
  defaultConfig {
22
- minSdkVersion 16
23
- targetSdkVersion 22
25
+ minSdkVersion safeExtGet('minSdkVersion', 16)
26
+ targetSdkVersion safeExtGet('targetSdkVersion', 22)
24
27
  versionCode 1
25
28
  versionName "1.0"
26
29
  }
@@ -37,10 +40,14 @@ repositories {
37
40
  url "$projectDir/../node_modules/react-native/android"
38
41
  }
39
42
  mavenCentral()
43
+ mavenLocal()
44
+ google()
45
+ jcenter()
46
+
40
47
  }
41
48
 
42
49
  dependencies {
43
- compile 'com.facebook.react:react-native:+'
50
+ implementation 'com.facebook.react:react-native:+'
44
51
  }
45
52
 
46
53
  def configureReactNativePom(def pom) {
@@ -93,8 +100,8 @@ afterEvaluate { project ->
93
100
 
94
101
  android.libraryVariants.all { variant ->
95
102
  def name = variant.name.capitalize()
96
- task "jar${name}"(type: Jar, dependsOn: variant.javaCompile) {
97
- from variant.javaCompile.destinationDir
103
+ task "jar${name}"(type: Jar, dependsOn: variant.javaCompileProvider.get()) {
104
+ from variant.javaCompileProvider.get().destinationDir
98
105
  }
99
106
  }
100
107
 
@@ -1,16 +1,12 @@
1
1
  package com.swmansion.rnscreens;
2
2
 
3
- import android.arch.lifecycle.Lifecycle;
4
- import android.arch.lifecycle.LifecycleObserver;
5
- import android.support.v4.app.Fragment;
6
- import android.util.Log;
3
+ import androidx.lifecycle.Lifecycle;
4
+ import androidx.lifecycle.LifecycleObserver;
5
+ import androidx.fragment.app.Fragment;
6
+
7
7
  import android.view.View;
8
8
  import android.view.ViewParent;
9
9
 
10
- import com.facebook.react.modules.core.ChoreographerCompat;
11
- import com.facebook.react.modules.core.ReactChoreographer;
12
-
13
- import java.util.ArrayList;
14
10
  import java.util.HashMap;
15
11
  import java.util.Map;
16
12
 
@@ -9,7 +9,7 @@ import java.util.Arrays;
9
9
  import java.util.Collections;
10
10
  import java.util.List;
11
11
 
12
- public class RNScreenPackage implements ReactPackage {
12
+ public class RNScreensPackage implements ReactPackage {
13
13
  @Override
14
14
  public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
15
15
  return Collections.emptyList();
@@ -19,7 +19,6 @@ public class RNScreenPackage implements ReactPackage {
19
19
  public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
20
20
  return Arrays.<ViewManager>asList(
21
21
  new ScreenContainerViewManager(),
22
- new ScreenStackViewManager(),
23
22
  new ScreenViewManager()
24
23
  );
25
24
  }
@@ -1,15 +1,20 @@
1
1
  package com.swmansion.rnscreens;
2
2
 
3
+ import androidx.annotation.Nullable;
4
+ import androidx.fragment.app.Fragment;
5
+
3
6
  import android.annotation.SuppressLint;
4
7
  import android.content.Context;
8
+ import android.graphics.Paint;
5
9
  import android.os.Bundle;
6
- import android.support.annotation.Nullable;
7
- import android.support.v4.app.Fragment;
8
10
  import android.view.LayoutInflater;
9
11
  import android.view.View;
10
12
  import android.view.ViewGroup;
11
13
 
12
- public class Screen extends ViewGroup {
14
+ import com.facebook.react.uimanager.PointerEvents;
15
+ import com.facebook.react.uimanager.ReactPointerEventsView;
16
+
17
+ public class Screen extends ViewGroup implements ReactPointerEventsView {
13
18
 
14
19
  public static class ScreenFragment extends Fragment {
15
20
 
@@ -36,6 +41,7 @@ public class Screen extends ViewGroup {
36
41
  private final Fragment mFragment;
37
42
  private @Nullable ScreenContainer mContainer;
38
43
  private boolean mActive;
44
+ private boolean mTransitioning;
39
45
 
40
46
  public Screen(Context context) {
41
47
  super(context);
@@ -47,6 +53,38 @@ public class Screen extends ViewGroup {
47
53
  // no-op
48
54
  }
49
55
 
56
+ /**
57
+ * While transitioning this property allows to optimize rendering behavior on Android and provide
58
+ * a correct blending options for the animated screen. It is turned on automatically by the container
59
+ * when transitioning is detected and turned off immediately after
60
+ */
61
+ public void setTransitioning(boolean transitioning) {
62
+ if (mTransitioning == transitioning) {
63
+ return;
64
+ }
65
+ mTransitioning = transitioning;
66
+ super.setLayerType(transitioning ? View.LAYER_TYPE_HARDWARE : View.LAYER_TYPE_NONE, null);
67
+ }
68
+
69
+ @Override
70
+ public boolean hasOverlappingRendering() {
71
+ return mTransitioning;
72
+ }
73
+
74
+ @Override
75
+ public PointerEvents getPointerEvents() {
76
+ return mTransitioning ? PointerEvents.NONE : PointerEvents.AUTO;
77
+ }
78
+
79
+ @Override
80
+ public void setLayerType(int layerType, @Nullable Paint paint) {
81
+ // ignore - layer type is controlled by `transitioning` prop
82
+ }
83
+
84
+ public void setNeedsOffscreenAlphaCompositing(boolean needsOffscreenAlphaCompositing) {
85
+ // ignore - offscreen alpha is controlled by `transitioning` prop
86
+ }
87
+
50
88
  protected void setContainer(@Nullable ScreenContainer mContainer) {
51
89
  this.mContainer = mContainer;
52
90
  }
@@ -60,6 +98,9 @@ public class Screen extends ViewGroup {
60
98
  }
61
99
 
62
100
  public void setActive(boolean active) {
101
+ if (active == mActive) {
102
+ return;
103
+ }
63
104
  mActive = active;
64
105
  if (mContainer != null) {
65
106
  mContainer.notifyChildUpdate();
@@ -1,15 +1,16 @@
1
1
  package com.swmansion.rnscreens;
2
2
 
3
- import android.app.Activity;
3
+ import androidx.annotation.Nullable;
4
+ import androidx.fragment.app.Fragment;
5
+ import androidx.fragment.app.FragmentActivity;
6
+ import androidx.fragment.app.FragmentTransaction;
7
+
4
8
  import android.content.Context;
5
- import android.support.annotation.Nullable;
6
- import android.support.v4.app.Fragment;
7
- import android.support.v4.app.FragmentActivity;
8
- import android.support.v4.app.FragmentManager;
9
- import android.support.v4.app.FragmentTransaction;
9
+ import android.content.ContextWrapper;
10
10
  import android.view.ViewGroup;
11
+ import android.view.ViewParent;
11
12
 
12
- import com.facebook.react.bridge.ReactContext;
13
+ import com.facebook.react.ReactRootView;
13
14
  import com.facebook.react.modules.core.ChoreographerCompat;
14
15
  import com.facebook.react.modules.core.ReactChoreographer;
15
16
 
@@ -22,10 +23,10 @@ public class ScreenContainer extends ViewGroup {
22
23
 
23
24
  private final ArrayList<Screen> mScreens = new ArrayList<>();
24
25
  private final Set<Screen> mActiveScreens = new HashSet<>();
25
- private final FragmentManager mFragmentManager;
26
26
 
27
27
  private @Nullable FragmentTransaction mCurrentTransaction;
28
28
  private boolean mNeedUpdate;
29
+ private boolean mIsAttached;
29
30
 
30
31
  private ChoreographerCompat.FrameCallback mFrameCallback = new ChoreographerCompat.FrameCallback() {
31
32
  @Override
@@ -36,13 +37,6 @@ public class ScreenContainer extends ViewGroup {
36
37
 
37
38
  public ScreenContainer(Context context) {
38
39
  super(context);
39
- Activity activity = ((ReactContext) context).getCurrentActivity();
40
- if (activity instanceof FragmentActivity) {
41
- mFragmentManager = ((FragmentActivity) activity).getSupportFragmentManager();
42
- } else {
43
- throw new IllegalStateException(
44
- "In order to use RNScreen components your app's activity need to extend ReactFragmentActivity or ReactCompatActivity");
45
- }
46
40
  }
47
41
 
48
42
  @Override
@@ -85,9 +79,35 @@ public class ScreenContainer extends ViewGroup {
85
79
  return mScreens.get(index);
86
80
  }
87
81
 
82
+ private FragmentActivity findRootFragmentActivity() {
83
+ ViewParent parent = this;
84
+ while (!(parent instanceof ReactRootView) && parent.getParent() != null) {
85
+ parent = parent.getParent();
86
+ }
87
+ // we expect top level view to be of type ReactRootView, this isn't really necessary but in order
88
+ // to find root view we test if parent is null. This could potentially happen also when the view
89
+ // is detached from the hierarchy and that test would not correctly indicate the root view. So
90
+ // in order to make sure we indeed reached the root we test if it is of a correct type. This
91
+ // allows us to provide a more descriptive error message for the aforementioned case.
92
+ if (!(parent instanceof ReactRootView)) {
93
+ throw new IllegalStateException("ScreenContainer is not attached under ReactRootView");
94
+ }
95
+ // ReactRootView is expected to be initialized with the main React Activity as a context but
96
+ // in case of Expo the activity is wrapped in ContextWrapper and we need to unwrap it
97
+ Context context = ((ReactRootView) parent).getContext();
98
+ while (!(context instanceof FragmentActivity) && context instanceof ContextWrapper) {
99
+ context = ((ContextWrapper) context).getBaseContext();
100
+ }
101
+ if (!(context instanceof FragmentActivity)) {
102
+ throw new IllegalStateException(
103
+ "In order to use RNScreens components your app's activity need to extend ReactFragmentActivity or ReactCompatActivity");
104
+ }
105
+ return (FragmentActivity) context;
106
+ }
107
+
88
108
  private FragmentTransaction getOrCreateTransaction() {
89
109
  if (mCurrentTransaction == null) {
90
- mCurrentTransaction = mFragmentManager.beginTransaction();
110
+ mCurrentTransaction = findRootFragmentActivity().getSupportFragmentManager().beginTransaction();
91
111
  mCurrentTransaction.setReorderingAllowed(true);
92
112
  }
93
113
  return mCurrentTransaction;
@@ -95,7 +115,7 @@ public class ScreenContainer extends ViewGroup {
95
115
 
96
116
  private void tryCommitTransaction() {
97
117
  if (mCurrentTransaction != null) {
98
- mCurrentTransaction.commitNow();
118
+ mCurrentTransaction.commitAllowingStateLoss();
99
119
  mCurrentTransaction = null;
100
120
  }
101
121
  }
@@ -121,8 +141,21 @@ public class ScreenContainer extends ViewGroup {
121
141
  return screen.isActive();
122
142
  }
123
143
 
144
+ @Override
145
+ protected void onAttachedToWindow() {
146
+ super.onAttachedToWindow();
147
+ mIsAttached = true;
148
+ updateIfNeeded();
149
+ }
150
+
151
+ @Override
152
+ protected void onDetachedFromWindow() {
153
+ super.onDetachedFromWindow();
154
+ mIsAttached = false;
155
+ }
156
+
124
157
  private void updateIfNeeded() {
125
- if (!mNeedUpdate || mFragmentManager.isDestroyed()) {
158
+ if (!mNeedUpdate || !mIsAttached) {
126
159
  return;
127
160
  }
128
161
  mNeedUpdate = false;
@@ -144,6 +177,15 @@ public class ScreenContainer extends ViewGroup {
144
177
  }
145
178
  }
146
179
 
180
+ // detect if we are "transitioning" based on the number of active screens
181
+ int activeScreens = 0;
182
+ for (int i = 0, size = mScreens.size(); i < size; i++) {
183
+ if (isScreenActive(mScreens.get(i), mScreens)) {
184
+ activeScreens += 1;
185
+ }
186
+ }
187
+ boolean transitioning = activeScreens > 1;
188
+
147
189
  // attach newly activated screens
148
190
  boolean addedBefore = false;
149
191
  for (int i = 0, size = mScreens.size(); i < size; i++) {
@@ -155,6 +197,7 @@ public class ScreenContainer extends ViewGroup {
155
197
  } else if (isActive && addedBefore) {
156
198
  moveToFront(screen);
157
199
  }
200
+ screen.setTransitioning(transitioning);
158
201
  }
159
202
  tryCommitTransaction();
160
203
  }
@@ -24,7 +24,7 @@ public class ScreenContainerViewManager extends ViewGroupManager<ScreenContainer
24
24
  @Override
25
25
  public void addView(ScreenContainer parent, View child, int index) {
26
26
  if (!(child instanceof Screen)) {
27
- throw new IllegalArgumentException("Attempt attach child that is not of type RNScreen");
27
+ throw new IllegalArgumentException("Attempt attach child that is not of type RNScreens");
28
28
  }
29
29
  parent.addScreen((Screen) child, index);
30
30
  }
@@ -20,8 +20,8 @@ public class ScreenViewManager extends ViewGroupManager<Screen> {
20
20
  return new Screen(reactContext);
21
21
  }
22
22
 
23
- @ReactProp(name = "active", defaultBoolean = false)
24
- public void setActive(Screen view, boolean active) {
25
- view.setActive(active);
23
+ @ReactProp(name = "active", defaultFloat = 0)
24
+ public void setActive(Screen view, float active) {
25
+ view.setActive(active != 0);
26
26
  }
27
27
  }
package/ios/RNSScreen.h CHANGED
@@ -13,6 +13,8 @@
13
13
  @property (nonatomic, retain) UIViewController *controller;
14
14
  @property (nonatomic) BOOL active;
15
15
 
16
+ - (void)notifyFinishTransitioning;
17
+
16
18
  @end
17
19
 
18
20
  @interface UIView (RNSScreen)
package/ios/RNSScreen.m CHANGED
@@ -4,10 +4,15 @@
4
4
  @interface RNSScreen : UIViewController
5
5
 
6
6
  - (instancetype)initWithView:(UIView *)view;
7
+ - (void)notifyFinishTransitioning;
7
8
 
8
9
  @end
9
10
 
10
- @implementation RNSScreenView
11
+ @implementation RNSScreenView {
12
+ RNSScreen *_controller;
13
+ }
14
+
15
+ @synthesize controller = _controller;
11
16
 
12
17
  - (instancetype)init
13
18
  {
@@ -20,18 +25,21 @@
20
25
 
21
26
  - (void)setActive:(BOOL)active
22
27
  {
23
- _active = active;
24
- [_reactSuperview markChildUpdated];
28
+ if (active != _active) {
29
+ _active = active;
30
+ [_reactSuperview markChildUpdated];
31
+ }
25
32
  }
26
33
 
27
- - (UIView *)reactSuperview
34
+ - (void)setPointerEvents:(RCTPointerEvents)pointerEvents
28
35
  {
29
- return _reactSuperview;
36
+ // pointer events settings are managed by the parent screen container, we ignore any attempt
37
+ // of setting that via React props
30
38
  }
31
39
 
32
- - (void)didSetProps:(NSArray<NSString *> *)changedProps
40
+ - (UIView *)reactSuperview
33
41
  {
34
- [_reactSuperview didUpdateChildren];
42
+ return _reactSuperview;
35
43
  }
36
44
 
37
45
  - (void)invalidate
@@ -40,10 +48,16 @@
40
48
  _controller = nil;
41
49
  }
42
50
 
51
+ - (void)notifyFinishTransitioning
52
+ {
53
+ [_controller notifyFinishTransitioning];
54
+ }
55
+
43
56
  @end
44
57
 
45
58
  @implementation RNSScreen {
46
59
  __weak UIView *_view;
60
+ __weak id _previousFirstResponder;
47
61
  }
48
62
 
49
63
  - (instancetype)initWithView:(UIView *)view
@@ -54,6 +68,36 @@
54
68
  return self;
55
69
  }
56
70
 
71
+ - (id)findFirstResponder:(UIView*)parent
72
+ {
73
+ if (parent.isFirstResponder) {
74
+ return parent;
75
+ }
76
+ for (UIView *subView in parent.subviews) {
77
+ id responder = [self findFirstResponder:subView];
78
+ if (responder != nil) {
79
+ return responder;
80
+ }
81
+ }
82
+ return nil;
83
+ }
84
+
85
+ - (void)willMoveToParentViewController:(UIViewController *)parent
86
+ {
87
+ if (parent == nil) {
88
+ id responder = [self findFirstResponder:self.view];
89
+ if (responder != nil) {
90
+ _previousFirstResponder = responder;
91
+ }
92
+ }
93
+ }
94
+
95
+ - (void)notifyFinishTransitioning
96
+ {
97
+ [_previousFirstResponder becomeFirstResponder];
98
+ _previousFirstResponder = nil;
99
+ }
100
+
57
101
  - (void)loadView
58
102
  {
59
103
  self.view = _view;
@@ -3,16 +3,8 @@
3
3
  @protocol RNSScreenContainerDelegate
4
4
 
5
5
  - (void)markChildUpdated;
6
- - (void)didUpdateChildren;
7
6
 
8
7
  @end
9
8
 
10
9
  @interface RNSScreenContainerView : UIView <RNSScreenContainerDelegate>
11
-
12
- - (void)markChildUpdated;
13
- - (void)didUpdateChildren;
14
-
15
- @end
16
-
17
- @interface RNSScreenContainerManager : RCTViewManager
18
10
  @end