The above situation may happen when grandparent is unmounted from react. In such a case children are not removed from non direct ancestors but still get invalidated.
Reverting that change as it turned out not to be necessary. At the moment of attaching screen we already have the frame set on the child VC. Updating it there again was cauinsg some visual glitches with old react-navigation screen container based implementation.
After exaiming memory graph it turned out that screens for which we access presentationController property ends up leaking, likely because of some issue in UIKit. This turns out not to affect screens that are actually being presented as modals (when presentationController is required). This change updates presentation mode setter to avoid accessing presentationController for push screens and also warns if the prop ever switches from modal to push (in which case we cannot reverse the leak source).
This chnage fixes the way we'd managed parent<>child VC relation. With this change in we hook child VC to parent in didMoveToWindow to match Stack behavior. We also wait with updating child view frame untill the child screen is attached. Finally we utilize RCTInvalidating interface to spot moments when screen controller is unmounted from react such that we can break reference cycle between screen and screen view (we don't do it in invalidate directly as sometimes we need to wait till transition end).
This change fixes logic that we run when stack is moved to a window. Before we'd first attach stack to parent VC and then run updateContainer. This could've led to a buggy behavior in case the stack is mounted within other stack that is running a transition (e.g. dismissing a modal). In such a case setting initial push VCs would fail (because UIKit does not allow push to be modified while transitioning). Because of that stack would initialize with the dummy VC and the initial VC would get added to dismissedScreens (which is another side effect of the logic that keeps track of dismissed screens).
The fix was to add call to updateContainer before VC is added to parent. This makes it possible for push screens to be properly initialized (since VC is not yet added it does not know its parent is transitioning). Then, since updating modals handles gracefully the case when modals cannot be shown (and they wont be shown unless added to parent VC) we can safely run updateContainer yet another time after the VC is added to parent.
Howeer after the above fix was applied we observed another issue that was due to an invalida appear/disappear event management within view controllers. To address that fix we no longer allow navigaton VC's view to be added to the container view unless it starts transition to parent view controller. We also fixed missing calls to super in willMoveToParentViewController callback of RNSScreen controller and also fixed view reference management withing RNScreen to keep views as weak references while the view is not attached. The latter fixes the problem with screen view leaking under certain conditions.
This change fixes the problem of container view controllers not being detached properly from its parent container VC. We fix this by detaching VC from didMoveToWindow. On top of that we also added a check to ensure the update of containers won't run unless JS batch is complete. This is to prevent partially updated headers.
Since the config is only updated in the HeaderConfig view it will not get executed for stacks with no header. This fixes it by setting the prop directly in the Screen when the gestureEnabled prop is set.
This change addresses two problem. First was related to header subviews that were not laid out properly before we installed them in the header. This was causing occasional bugs where the header subviews were misplaced. The fix was to enforce container update run after layout is done (we enqueue update on ui thread directly from didUpdateReactSubviews).
The second problem was related to UINavController nlifecycle methods not triggering correctly in case when updates are being made to the nav controller which isn't mounted. Previously we fixed similar issue for modal controllers where because of the fact container wasn't mounted we couldn't run modal screen updates. With push controllers the problem is very similar however the VC do update just stop getting proper lifecycle updates and warning "Unbalanced calls to begin/end appearance transitions" is displayed. The fix was similar as in the modal case, that is, we wait until container is installed in window.
This update changes the logic of how we present modals. Previously presentedModals array could get out of sync with the actual app state because in case when parent VC is not mounted, presenting a modal would silently fail. In order to handle that we added additional update trigger from where the screen stack is mounted to a window. Second change that we added here addresses a problem of concurrent calls to update modals. As we cannot update presentedModals array synchrounously (all presentation callbacks are async even when no animation is involved), we need to block future updates unless the previous batch of updates is done. To do that we added a flag that prevents method re-entry. The flag is reset when all the presentation animations are over.
Allows you to choose additional modal presentation styles for iOS. It adds the ability to force a full screen modal or choose a "form sheet" style. This only affects iOS.
The previous condition was broken as under certain circumstances we would've receive a setReactFrame with no active screen rendered to only get the screen activate in a while. This resulted in the view dimention not being properly updated. This diff changes the condition and verifies whether a screen is mounted under UINavController or not. When not, we assume it is mounted under regular screen container and allow the frame to be adjusted from react.
This change adds new event that gets dispatched from native stack when screen transitioning is finished.
The new event is used by react-navigation to update the library internal state that the triggered action has been finished. Previously we were relying solely on onAppear and onDisappear events, however those does not get triggered when new iOS 13 modals are used or when transparent modals are displayed. This is because with transparent modals the view below is still visible. We therefore needed another way of notifying the library that screen transition have finished despite the fact that disappear event couldn't be triggered.
As a part of this change I also refactored invalid ref cycle-break code on iOS which was ought to remove reference cycle between view and view controller. This code have been moved to viewWillDisappear callback.
Also on Android part small refactoring has been done and we removed the necessity to keep mActiveScreens array which was occasionally getting out of sync with the list of active fragments.
This fixes a problem when image attribute of image view would get reset and we wouldn't be able to access it even in release mode. This could happen when the app is backgrounded as at that point image views release their resources. Another case that was quite frequent was that the image would start loading before the image view frame was properly set. The solution is to trigger image reload in cases when image is missing and to force the image view frame to match the size of an image.
This change also fixes a problem on iOS pre 13 where we were updating back indicator inside of the method which would run during the transition. Turns out updating this property isn't supported during animation and so we moved it out such that it is updated before the transition starts.
This change fixes the problem when navigation bar would not get properly updated for modal transitions. The problem was due to the fact that for modal transitions the animateAlongsideTransition block was not getting trriggered. This was noticeable on iOS versions before 13, as starting with iOS 13 we can use NavigationItem to specify most of the nav bar customization. On older versions however it is required that for a lot of navbar settings we need to update them durion animation. This wasn't the case for navigators presented modally. In case we show a modal we can update nav bar without animation because it actually renders a completely new navigation bar which isn't shared.
On top of that this PR fixes the problem with bridge not being set for header subviews.
This change removes ScreenStackHeaderTitleView component and code that used to handle title view scaling. There were multiple issues related to scaling toolbar title views and it is not a priority at the moment to us to work on addressing those. The most frequent usecase is to put a fixed-dimensions view (e.g. logo or text) which can be now handled w/o this extra code.
This change fixes a bug when header property updates happens during or at the same time of stack transition, e.g., when we both pop a screen and change parent VC header properties. When this happens we trigger header update code, but because we used to compare current VC to visibleViewController, which at that moment points to controller that we are transitioning away from, the update would've been prevented. With this change instead of using visibleViewController to compare with updated VC, we check if there is transition happening (using transitransitionCoordinator property), and in such case we use topController which points to the controller we are transitioning to.
When you have 2 screens in a stack with the bottom one with gestureEnabled=false using the back gesture causes the screen to become unresponsive. This seems to be caused by setting interactivePopGestureRecognizer.enabled = NO as soon as the gesture starts. This causes the gesture to cancel immediately and leaves the screen in an unresponsive state.
<Stack><Screen gestureEnabled={false} /><Screen /></Stack>
To fix this instead of using interactivePopGestureRecognizer.enabled we can leverage the existing delegate that we have in RNScreenStack. In gestureRecognizerShouldBegin we can check if the top screen has gestures enabled.
To make this simpler I moved the gestureEnabled config to Screen instead of HeaderConfig. I think it also makes more sense conceptually since the gesture is tied to the screen and not the header. It also simplifies the android code a bit.
This is a breaking change.
Update
This now only moves the config to screen since a separate fix was merged for the bug.
This issue is related to #92.
Issue
I got issue when playing video with react-native-video. The video keep playing (still hear sound) when go back.
react-native: 0.61.5
react-native-screens: 2.0.0-alpha.22
Solution
When controller popped, try to assign view to nil to break the cycle.
This change fixes a problem in modal dismiss logic. The problem would occur when we had a nested stack that both have screens pushed and presented modally. In such case when we only wanted to pop one of the screens pushed (even though the screen was obscured by the one presented modally) we'd still run setModalViewControllers method. As a result that method could've find the navigato that is presenting the screens modally and trigger dismiss on them. This was the case because we used to only check in that method whether the top-most unchanged screen is presenting modals. We shouldn't only be checking whether the screen is presenting because it may be presenting modals that belong to a different stack that did not change.
To prevent the issue from happening we make sure that we run dismiss logic only when a change on a give stack occur. We verify that the list of presented VCs is not equal to the list of new controllers before we run push-stack or modal-stack presenting/dismiss logic.
On top of that change I also modified push logic to avoid running animation when the push stack is obscured by modals. This prevents some weird effects when you could see the end of animation after closing the modal immediately after pop done in the hidden stack.
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.