For customizing back button image we use platform native functionality that is: `setBackIndicatorImage` on iOS and `setHomeAsUpIndicator` on Android.
The reason we don't do that just by setting left item is that we get a couple of things for free such as handling RTL properly, working accessibility features, handling prop for hiding back button and a couple more.
Unfortunately there are some downsides to that approach too. We need to install the back button as an Image component from the JS side, and the extract the image payload on the native side to set it with the navigator. This is specifically problematic in DEV mode where images are loaded asynchronously over HTTP from the packager. In order for that to work we had to employ a few hacks (more comments on that in the code).
fixes#208
For stack overriding reactSetFrame for active does nothing because active is always NO, so there's no regression.
But it fixes in other cases (like stack navigation - not native one).
Appear event is used by react-navigation to properly dispatch focus. It is important that appear is dispatched after dismissed event. The reverse order of actions would result in getting react-navigation stack in a weird state.
It is relatively streightforward to implement onAppear event on iOS where we hook into didAppear callback from UIViewController. It gets dispatched in the right moment, that is when the transition is fully over.
On Android however it is much more tricky. There is no standard way to be notified from the fragment level that fragment transition finished. One way that is frequently recommended is to override Fragment.onCreateAnimation. However, this only works when custom transitions are provided (e.g. if we set the transition to use fade animation). As we want the platform native transition to be run by default we had to look for other ways. The current approach relies on fragment container's callbacks startViewTransition and endViewTransition, with the latter being triggered once the animation is over. We also need to take into account that a good starting point for the transition is when we call commit on fragment transaction. We use these two methods to determine if the fragment is instantiated (onCreate) within a running transaction and if so we schedule event dispatch at the moment when endViewTransition is called.
Another change this commit introduces on the Android side is that we no longer rely on show/hide for replacing fragments on stack and we now use add/remove transaction methods. Due to this change we had to make our fragments reusable and make onCreateView indempotent.
The previous algorithm was buggy and did not handle the case when multiple VCs are being dismissed. Unfortunately I couldn't find a reliable way that'd allow for reshuffling modally presented VCs (inserting or deleting VCs not from the top) and so I added a special check that'd throw in the case someone attemted to do this.
This fixes the problem when navbar settings update from ones that have header items (e.g. custom title object) to a config without such items. In such a case we'd set navbar items in the first go and never reset those items.
When some of config props change or new header items are added we need to perform a header update. This diff adds logic to trigger updating header props when that happens.
After #184 we no longer were acconting for the size of navigation bar when laying out screens on the stack. This was causing elements to be drawn under a non-translucent bar unless SafeAreaView's been used. As this seem not to be desirable in most of the cases (there is no way of seeing items rendered underneath non-translucent header) this change brings back the previous behavior. Instead of using manual method of calculating top inset as it's been done before we rely on edgesForExtendedLayout property of the view controller.
When more than one screen is initially mounted we were only installing the top navigator with UINavController. This was cauing the back-handling logic to not work properly.
For native stack we introduced a new way of layouting screen controllers. We no longer rely on flexbox to layout them so that we can use native stack to provide a correct dimensions and safe area paddings. Unfortunately this change broke the old screen container. With this commit we are adapting layout change to old screen container that will now layout its direct children on the native side by making them take up the whole space of the container.
iOS navbar largeTitle setting allows the navbar to be automatically collapsed when scrolling. For that to work iOS expects scrollable component to be rendered in the screen view hierarchy as a first children (or just to be looked up on the ancestor path using first children when navigating down). On top of that in RN we should use contentInsetAdjustmentBehavior="automatic" for SV's contentInsets to adjust as expected when the header scrolls and to allow for the SV background to be present below the navigation bar.
This works pretty well w/o any changes. However when testing I disovered one issue with the navigation bar rendering in a collapsed state on the first render. After that you could've scroll down to reveal large title bar but the initial state would be collapsed. As on iOS it is expected for large title enabled screens to initially render in the revealed state a workaround was required. It turned out that the header rendered in collapsed state because at the moment when navigation controller is created is has an empty list of view controllers. The list is empty because react first creates nnavigation controller and only then adds children to it. It turned out the fix was to populate viewControllers with a single item at first, then when controllers list is replaced with the children provided from react the header renders properly in the revealed state.
This change makes it possible for header customization options to use appearence API introduces in iOS 13. Thanks to this the default header setting will respect header translucency. Thanks to the appearence API we no longer need to set so many properties in setAnimatedConfig.
- `@available` should not be used with other conditions (not sure if it breaks anything but it does trigger warnings in xcode)
- missing runtime check before using `modalInPresentation`, kept the precompiler instruction so it still builds on older xcodes.
Supporting fade in for modals can be done by using `modalTransitionStyle = UIModalTransitionStyleCrossDissolve`. For other type of animations we leave this as the default value.
Tested via `@react-navigation/native-stack` with `{ animation: 'fade', presentation: 'transparentModal' }`
* Fix non-default modal presentation styles
Using alternative modal presentation styles like `transparentModal` is not working. This is because accessing `presentationController` before setting the `modalPresentationStyle` causes the presentation controller to be created and cannot be changed afterwards. This is documented https://developer.apple.com/documentation/uikit/uiviewcontroller/1621426-presentationcontroller?language=objc.
Yes very cool API
Tested via @react-navigation/native-stack using presentation: 'transparentModal'.
* Update RNSScreen.m
* Let UINavController control subcontroller view's frames.
This PR changes the way we've been handling yoga <> NavController layout interactions. Now we ignore frame updates coming from react for the main Screen view to allow NavController take the controll. In order to keep yoga working we now use `setSize` to pass the dimensions of the view back to yoga such that it can properly calculate layout of the views under Screen component.
* Header resizing fixes for Android.
In this change we use CoordinatorLayout as a stack screen container to handle rendering of toolbar and screen content. Thanks to this approach we can support collapsable bars in the future. Instead of relying on RN to layout screen container when renered under ScreenStack we rely on Android native layout to measure and position screen content and then use UIManager.setNodeSize to communicate that back to react.
This change fixes the problem when header would update to the previous screen configuration as a result of starting swipe back gesture but never restore to the original one in case swipe back gets cancelled (e.g. user didn't swipe far enough). The problem was that as a result of swipe back we'd apply header config changes but after cancel there was no trigger to reset header to the previous state.
On iOS by default modals show up in full-screen mode. This behavior can be customized and instead of mouting new screens in top level window they can be mounted under a given UINavController. We used to be relying on that behavior (see "CurrentContext" presentation mode). This, apparently wasn't matching the default functionality of the OS. This change is adding it as a default and keeping the old way under newly exposed presentation modes: containedModal and containedTransparentModal
Thanks to this change, iOS 13 modals can work properly.
Previously we weren't triggering transaction finish when going from none active screens to 1 active screen. This turns out to be the case in tab navigator where for a sub-frame moment the active state changes for the current screen to `NO` and then new screen isn't active yet.
Fixes#53
This is a follow up to #48 which makes Screen component ignore setting of pointerEvents. As it turns out react-navigation tries to set this setting and in addition to handling it manually we also need to prevent it to be set via React prop.
This change automates first responder restoring when screen is deactivated and the activated back (e.g. when we push new screen on top and then go back). In addition we disable pointerEvent setting for the Screen component as changes made to that prop would cause underlying views to resign responder before we can remember it. Because of that we add an automatic handling for pointer events for the Screen component that disables all touch interactions when screen is transitioning.
With this change in the screens that are being transitioned get detach+attach events similarily to how it is done in UINavigationController. This allows views underneath to get notified that the transition is started and react to it when necessary (e.g. hide keyboard)