feat(ios): improved handling for navigation events (#10757)

This commit is contained in:
Dimitris-Rafail Katsampas
2025-07-12 02:13:20 +03:00
committed by GitHub
parent 1ef47301bf
commit bca3452722
9 changed files with 118 additions and 156 deletions

View File

@@ -4,7 +4,7 @@ import { Page } from '../page';
import { View, CustomLayoutView, CSSType } from '../core/view';
import { Property } from '../core/properties';
import { Trace } from '../../trace';
import { frameStack, topmost as frameStackTopmost, _pushInFrameStack, _popFromFrameStack, _removeFromFrameStack } from './frame-stack';
import { frameStack, topmost as frameStackTopmost, _pushInFrameStack, _popFromFrameStack, _removeFromFrameStack, _isFrameStackEmpty } from './frame-stack';
import { viewMatchesModuleContext } from '../core/view/view-common';
import { getAncestor } from '../core/view-base';
import { Builder } from '../builder';
@@ -542,6 +542,10 @@ export class FrameBase extends CustomLayoutView {
}
}
public _isFrameStackEmpty() {
return _isFrameStackEmpty();
}
public _pushInFrameStack() {
_pushInFrameStack(this);
}

View File

@@ -36,8 +36,10 @@ export interface NavigationEntry extends ViewEntry {
}
export interface NavigationContext {
entry: BackstackEntry;
// TODO: remove isBackNavigation for NativeScript 7.0
entry?: BackstackEntry;
/**
* @deprecated Use navigationType instead.
*/
isBackNavigation: boolean;
navigationType: NavigationType;
}

View File

@@ -11,6 +11,10 @@ export function topmost(): FrameBase {
return undefined;
}
export function _isFrameStackEmpty(): boolean {
return frameStack.length === 0;
}
export function _pushInFrameStack(frame: FrameBase): void {
if (frame._isInFrameStack && frameStack[frameStack.length - 1] === frame) {
return;

View File

@@ -258,6 +258,10 @@ export class Frame extends FrameBase {
* @private
*/
_updateBackstack(entry: BackstackEntry, navigationType: NavigationType): void;
/**
* @private
*/
_isFrameStackEmpty(): boolean;
/**
* @private
*/
@@ -404,7 +408,10 @@ export interface NavigationEntry extends ViewEntry {
* Represents a context passed to navigation methods.
*/
export interface NavigationContext {
entry: BackstackEntry;
entry?: BackstackEntry;
/**
* @deprecated Use navigationType instead.
*/
isBackNavigation: boolean;
navigationType: NavigationType;
}

View File

@@ -7,7 +7,6 @@ import { IOSHelper } from '../core/view/view-helper';
import { profile } from '../../profiling';
import { CORE_ANIMATION_DEFAULTS, ios as iOSUtils, layout } from '../../utils';
import { Trace } from '../../trace';
import type { PageTransition } from '../transition/page-transition';
import { SlideTransition } from '../transition/slide-transition';
import { FadeTransition } from '../transition/fade-transition';
import { SharedTransition } from '../transition/shared-transition';
@@ -22,11 +21,11 @@ const NAV_DEPTH = '_navDepth';
const TRANSITION = '_transition';
const NON_ANIMATED_TRANSITION = 'non-animated';
let navDepth = -1;
let navDepth: number = -1;
let navControllerDelegate: UINavigationControllerDelegate = null;
export class Frame extends FrameBase {
viewController: UINavigationControllerImpl;
_animatedDelegate: UINavigationControllerDelegate;
public _ios: iOSFrame;
iosNavigationBarClass: typeof NSObject;
iosToolbarClass: typeof NSObject;
@@ -38,6 +37,11 @@ export class Frame extends FrameBase {
}
createNativeView() {
// Push frame back in frame stack since it was removed in disposeNativeView() method.
if (this._currentEntry) {
this._pushInFrameStack();
}
return this.viewController.view;
}
@@ -61,7 +65,12 @@ export class Frame extends FrameBase {
this._removeFromFrameStack();
this.viewController = null;
this._animatedDelegate = null;
// This was the last frame so we can get rid of the controller delegate reference
if (this._isFrameStackEmpty()) {
navControllerDelegate = null;
}
if (this._ios) {
this._ios.controller = null;
this._ios = null;
@@ -120,11 +129,12 @@ export class Frame extends FrameBase {
const nativeTransition = _getNativeTransition(navigationTransition, true);
if (!nativeTransition && navigationTransition) {
if (!this._animatedDelegate) {
this._animatedDelegate = <UINavigationControllerDelegate>UINavigationControllerAnimatedDelegate.initWithOwner(new WeakRef(this));
if (!navControllerDelegate) {
navControllerDelegate = <UINavigationControllerDelegate>UINavigationControllerAnimatedDelegate.new();
}
this._ios.controller.delegate = this._animatedDelegate;
viewController[DELEGATE] = this._animatedDelegate;
this._ios.controller.delegate = navControllerDelegate;
viewController[DELEGATE] = navControllerDelegate;
if (navigationTransition.instance) {
this.transitionId = navigationTransition.instance.id;
const transitionState = SharedTransition.getState(this.transitionId);
@@ -346,19 +356,6 @@ export class Frame extends FrameBase {
public _setNativeViewFrame(nativeView: UIView, frame: CGRect) {
//
}
public _onNavigatingTo(backstackEntry: BackstackEntry, isBack: boolean) {
// for now to not break iOS events chain (calling navigation events from controller delegates)
// we dont call super(which would also trigger events) but only notify the frame of the navigation
// though it means events are not triggered at the same time (lifecycle) on iOS / Android
this.notify({
eventName: Page.navigatingToEvent,
object: this,
isBack,
entry: backstackEntry,
fromEntry: this._currentEntry,
});
}
}
const transitionDelegates = new Array<TransitionDelegate>();
@@ -413,14 +410,6 @@ class TransitionDelegate extends NSObject {
@NativeClass
class UINavigationControllerAnimatedDelegate extends NSObject implements UINavigationControllerDelegate {
public static ObjCProtocols = [UINavigationControllerDelegate];
owner: WeakRef<Frame>;
transition: PageTransition;
static initWithOwner(owner: WeakRef<Frame>) {
const delegate = <UINavigationControllerAnimatedDelegate>UINavigationControllerAnimatedDelegate.new();
delegate.owner = owner;
return delegate;
}
navigationControllerAnimationControllerForOperationFromViewControllerToViewController(navigationController: UINavigationController, operation: number, fromVC: UIViewController, toVC: UIViewController): UIViewControllerAnimatedTransitioning {
let viewController: UIViewController;
@@ -445,29 +434,30 @@ class UINavigationControllerAnimatedDelegate extends NSObject implements UINavig
if (Trace.isEnabled()) {
Trace.write(`UINavigationControllerImpl.navigationControllerAnimationControllerForOperationFromViewControllerToViewController(${operation}, ${fromVC}, ${toVC}), transition: ${JSON.stringify(navigationTransition)}`, Trace.categories.NativeLifecycle);
}
this.transition = navigationTransition.instance;
if (!this.transition) {
let transition = navigationTransition.instance;
if (!transition) {
if (navigationTransition.name) {
const curve = _getNativeCurve(navigationTransition);
const name = navigationTransition.name.toLowerCase();
if (name.indexOf('slide') === 0) {
const direction = name.substring('slide'.length) || 'left'; //Extract the direction from the string
this.transition = new SlideTransition(direction, navigationTransition.duration, curve);
const direction = name.substring('slide'.length) || 'left'; // Extract the direction from the string
transition = new SlideTransition(direction, navigationTransition.duration, curve);
} else if (name === 'fade') {
this.transition = new FadeTransition(navigationTransition.duration, curve);
transition = new FadeTransition(navigationTransition.duration, curve);
}
}
}
if (this.transition?.iosNavigatedController) {
return this.transition.iosNavigatedController(navigationController, operation, fromVC, toVC);
if (transition?.iosNavigatedController) {
return transition.iosNavigatedController(navigationController, operation, fromVC, toVC);
}
return null;
}
navigationControllerInteractionControllerForAnimationController(navigationController: UINavigationController, animationController: UIViewControllerAnimatedTransitioning): UIViewControllerInteractiveTransitioning {
const owner = this.owner?.deref();
navigationControllerInteractionControllerForAnimationController(navigationController: UINavigationControllerImpl, animationController: UIViewControllerAnimatedTransitioning): UIViewControllerInteractiveTransitioning {
const owner = navigationController.owner;
if (owner) {
const state = SharedTransition.getState(owner.transitionId);
if (state?.instance?.iosInteractionDismiss) {