diff --git a/NavigationReactNative/src/NavigationStack.tsx b/NavigationReactNative/src/NavigationStack.tsx index 03cd94ad88..5ca36ddccd 100644 --- a/NavigationReactNative/src/NavigationStack.tsx +++ b/NavigationReactNative/src/NavigationStack.tsx @@ -193,6 +193,7 @@ const NavigationStack = ({underlayColor: underlayColorStack = '#000', title, cus fragmentTag={fragmentTag} ancestorFragmentTags={ancestorFragmentTags} mostRecentEventCount={mostRecentEventCount} + customAnimation={customAnimation} style={styles.stack} {...getAnimation()} onWillNavigateBack={onWillNavigateBack} diff --git a/NavigationReactNative/src/NavigationStackNativeComponent.js b/NavigationReactNative/src/NavigationStackNativeComponent.js index 640581a511..2bc02d05b1 100644 --- a/NavigationReactNative/src/NavigationStackNativeComponent.js +++ b/NavigationReactNative/src/NavigationStackNativeComponent.js @@ -40,6 +40,7 @@ type NativeProps = $ReadOnly<{| enterAnimOff: boolean, sharedElements: $ReadOnlyArray, containerTransform: boolean, + customAnimation: boolean, underlayColor: ColorValue, mostRecentEventCount: Int32, onNavigateToTop: DirectEventHandler, diff --git a/NavigationReactNative/src/android/src/main/java/com/navigation/reactnative/NavigationStackViewManager.java b/NavigationReactNative/src/android/src/main/java/com/navigation/reactnative/NavigationStackViewManager.java index dac1dce394..caeb6509d6 100644 --- a/NavigationReactNative/src/android/src/main/java/com/navigation/reactnative/NavigationStackViewManager.java +++ b/NavigationReactNative/src/android/src/main/java/com/navigation/reactnative/NavigationStackViewManager.java @@ -90,6 +90,10 @@ public void setContainerTransform(NavigationStackView view, boolean containerTra view.containerTransform = containerTransform; } + @Override + public void setCustomAnimation(NavigationStackView view, boolean value) { + } + @Override public void setUnderlayColor(NavigationStackView view, @Nullable Integer value) { } diff --git a/NavigationReactNative/src/ios/NVNavigationStackComponentView.mm b/NavigationReactNative/src/ios/NVNavigationStackComponentView.mm index b1f327d12f..e04715d826 100644 --- a/NavigationReactNative/src/ios/NVNavigationStackComponentView.mm +++ b/NavigationReactNative/src/ios/NVNavigationStackComponentView.mm @@ -8,6 +8,7 @@ #import "NVSceneTransitioning.h" #import "NVSharedElementComponentView.h" #import "NVNavigationBarComponentView.h" +#import "NVStackControllerDelegate.h" #import #import @@ -34,6 +35,8 @@ @implementation NVNavigationStackComponentView UIScreenEdgePanGestureRecognizer *_interactiveGestureRecognizer; UIPanGestureRecognizer *_interactiveContentGestureRecognizer; UIPercentDrivenInteractiveTransition *_interactiveTransition; + NVStackControllerDelegate *_stackControllerDelegate; + id _interactiveGestureRecognizerDelegate; BOOL _navigated; BOOL _presenting; } @@ -50,18 +53,14 @@ - (instancetype)initWithFrame:(CGRect)frame _navigationController.view.semanticContentAttribute = ![[RCTI18nUtil sharedInstance] isRTL] ? UISemanticContentAttributeForceLeftToRight : UISemanticContentAttributeForceRightToLeft; _navigationController.navigationBar.semanticContentAttribute = ![[RCTI18nUtil sharedInstance] isRTL] ? UISemanticContentAttributeForceLeftToRight : UISemanticContentAttributeForceRightToLeft; [self addSubview:_navigationController.view]; - _navigationController.delegate = self; - _navigationController.interactivePopGestureRecognizer.delegate = self; + _stackControllerDelegate = [[NVStackControllerDelegate alloc] initWithStackView:self]; + _navigationController.delegate = _stackControllerDelegate; _interactiveGestureRecognizer = [[UIScreenEdgePanGestureRecognizer alloc] initWithTarget:self action:@selector(handleInteractivePopGesture:)]; _interactiveGestureRecognizer.delegate = self; + _interactiveGestureRecognizerDelegate = _navigationController.interactivePopGestureRecognizer.delegate; _interactiveGestureRecognizer.edges = ![[RCTI18nUtil sharedInstance] isRTL] ? UIRectEdgeLeft : UIRectEdgeRight; - [_navigationController.view addGestureRecognizer:_interactiveGestureRecognizer]; - if (@available(iOS 26.0, *)) { - _navigationController.interactiveContentPopGestureRecognizer.delegate = self; - _interactiveContentGestureRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handleInteractivePopGesture:)]; - _interactiveContentGestureRecognizer.delegate = self; - [_navigationController.view addGestureRecognizer:_interactiveContentGestureRecognizer]; - } + _interactiveContentGestureRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handleInteractivePopGesture:)]; + _interactiveContentGestureRecognizer.delegate = self; } return self; } @@ -111,6 +110,28 @@ - (void)updateProps:(Props::Shared const &)props oldProps:(Props::Shared const & [_exitTransitions addObject:transition]; } _sharedElement = newViewProps.sharedElements.size() > 0 ? [NSString stringWithUTF8String: newViewProps.sharedElements[0].c_str()] : nil; + if (newViewProps.customAnimation) { + if (_navigationController.interactivePopGestureRecognizer.delegate != self) { + _stackControllerDelegate = [[NVStackControllerTransitionDelegate alloc] initWithStackView:self]; + _navigationController.delegate = _stackControllerDelegate; + _navigationController.interactivePopGestureRecognizer.delegate = self; + [_navigationController.view addGestureRecognizer:_interactiveGestureRecognizer]; + if (@available(iOS 26.0, *)) { + _navigationController.interactiveContentPopGestureRecognizer.delegate = self; + [_navigationController.view addGestureRecognizer:_interactiveContentGestureRecognizer]; + } + } + } else { + if (_navigationController.interactivePopGestureRecognizer.delegate == self) { + _stackControllerDelegate = [[NVStackControllerDelegate alloc] initWithStackView:self]; + _navigationController.delegate = _stackControllerDelegate; + [_navigationController.view removeGestureRecognizer:_interactiveGestureRecognizer]; + [_navigationController.view removeGestureRecognizer:_interactiveContentGestureRecognizer]; + _navigationController.interactivePopGestureRecognizer.delegate = _interactiveGestureRecognizerDelegate; + if (@available(iOS 26.0, *)) + _navigationController.interactiveContentPopGestureRecognizer.delegate = _interactiveGestureRecognizerDelegate; + } + } _navigationController.view.backgroundColor = RCTUIColorFromSharedColor(newViewProps.underlayColor); _mostRecentEventCount = newViewProps.mostRecentEventCount; if (!_navigated) { diff --git a/NavigationReactNative/src/ios/NVNavigationStackManager.m b/NavigationReactNative/src/ios/NVNavigationStackManager.m index 2b170547c7..61b53645dc 100644 --- a/NavigationReactNative/src/ios/NVNavigationStackManager.m +++ b/NavigationReactNative/src/ios/NVNavigationStackManager.m @@ -18,6 +18,7 @@ - (UIView *)view RCT_EXPORT_VIEW_PROPERTY(enterAnimOff, BOOL) RCT_EXPORT_VIEW_PROPERTY(enterTrans, NSDictionary) RCT_EXPORT_VIEW_PROPERTY(exitTrans, NSDictionary) +RCT_EXPORT_VIEW_PROPERTY(customAnimation, BOOL) RCT_EXPORT_VIEW_PROPERTY(sharedElements, NSArray) RCT_EXPORT_VIEW_PROPERTY(underlayColor, UIColor) RCT_EXPORT_VIEW_PROPERTY(mostRecentEventCount, NSInteger) diff --git a/NavigationReactNative/src/ios/NVNavigationStackView.m b/NavigationReactNative/src/ios/NVNavigationStackView.m index e9072c2714..8310d0551c 100644 --- a/NavigationReactNative/src/ios/NVNavigationStackView.m +++ b/NavigationReactNative/src/ios/NVNavigationStackView.m @@ -4,6 +4,7 @@ #import "NVSceneTransitioning.h" #import "NVSharedElementView.h" #import "NVNavigationBarView.h" +#import "NVStackControllerDelegate.h" #import #import @@ -23,6 +24,8 @@ @implementation NVNavigationStackView UIScreenEdgePanGestureRecognizer *_interactiveGestureRecognizer; UIPanGestureRecognizer *_interactiveContentGestureRecognizer; UIPercentDrivenInteractiveTransition *_interactiveTransition; + NVStackControllerDelegate *_stackControllerDelegate; + id _interactiveGestureRecognizerDelegate; BOOL _navigated; BOOL _presenting; } @@ -35,18 +38,14 @@ - (id)initWithBridge:(RCTBridge *)bridge _navigationController.view.semanticContentAttribute = ![[RCTI18nUtil sharedInstance] isRTL] ? UISemanticContentAttributeForceLeftToRight : UISemanticContentAttributeForceRightToLeft; _navigationController.navigationBar.semanticContentAttribute = ![[RCTI18nUtil sharedInstance] isRTL] ? UISemanticContentAttributeForceLeftToRight : UISemanticContentAttributeForceRightToLeft; [self addSubview:_navigationController.view]; - _navigationController.delegate = self; - _navigationController.interactivePopGestureRecognizer.delegate = self; + _stackControllerDelegate = [[NVStackControllerDelegate alloc] initWithStackView:self]; + _navigationController.delegate = _stackControllerDelegate; _interactiveGestureRecognizer = [[UIScreenEdgePanGestureRecognizer alloc] initWithTarget:self action:@selector(handleInteractivePopGesture:)]; _interactiveGestureRecognizer.delegate = self; + _interactiveGestureRecognizerDelegate = _navigationController.interactivePopGestureRecognizer.delegate; _interactiveGestureRecognizer.edges = ![[RCTI18nUtil sharedInstance] isRTL] ? UIRectEdgeLeft : UIRectEdgeRight; - [_navigationController.view addGestureRecognizer:_interactiveGestureRecognizer]; - if (@available(iOS 26.0, *)) { - _navigationController.interactiveContentPopGestureRecognizer.delegate = self; - _interactiveContentGestureRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handleInteractivePopGesture:)]; - _interactiveContentGestureRecognizer.delegate = self; - [_navigationController.view addGestureRecognizer:_interactiveContentGestureRecognizer]; - } + _interactiveContentGestureRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handleInteractivePopGesture:)]; + _interactiveContentGestureRecognizer.delegate = self; _scenes = [[NSMutableDictionary alloc] init]; _enterTransitions = [[NSMutableArray alloc] init]; _exitTransitions = [[NSMutableArray alloc] init]; @@ -75,6 +74,32 @@ - (void)setSharedElements:(NSArray *)sharedElements _sharedElement = [sharedElements objectAtIndex:0]; } +- (void)setCustomAnimation:(BOOL)customAnimation +{ + if (customAnimation) { + if (_navigationController.interactivePopGestureRecognizer.delegate != self) { + _stackControllerDelegate = [[NVStackControllerTransitionDelegate alloc] initWithStackView:self]; + _navigationController.delegate = _stackControllerDelegate; + _navigationController.interactivePopGestureRecognizer.delegate = self; + [_navigationController.view addGestureRecognizer:_interactiveGestureRecognizer]; + if (@available(iOS 26.0, *)) { + _navigationController.interactiveContentPopGestureRecognizer.delegate = self; + [_navigationController.view addGestureRecognizer:_interactiveContentGestureRecognizer]; + } + } + } else { + if (_navigationController.interactivePopGestureRecognizer.delegate == self) { + _stackControllerDelegate = [[NVStackControllerDelegate alloc] initWithStackView:self]; + _navigationController.delegate = _stackControllerDelegate; + [_navigationController.view removeGestureRecognizer:_interactiveGestureRecognizer]; + [_navigationController.view removeGestureRecognizer:_interactiveContentGestureRecognizer]; + _navigationController.interactivePopGestureRecognizer.delegate = _interactiveGestureRecognizerDelegate; + if (@available(iOS 26.0, *)) + _navigationController.interactiveContentPopGestureRecognizer.delegate = _interactiveGestureRecognizerDelegate; + } + } +} + - (void)setUnderlayColor:(UIColor *)underlayColor { _navigationController.view.backgroundColor = underlayColor; diff --git a/NavigationReactNative/src/ios/NVStackControllerDelegate.h b/NavigationReactNative/src/ios/NVStackControllerDelegate.h new file mode 100644 index 0000000000..bb5034126f --- /dev/null +++ b/NavigationReactNative/src/ios/NVStackControllerDelegate.h @@ -0,0 +1,11 @@ +#import + +@interface NVStackControllerDelegate : NSObject + +-(id)initWithStackView: (id)stackView; + +@end + +@interface NVStackControllerTransitionDelegate : NVStackControllerDelegate + +@end diff --git a/NavigationReactNative/src/ios/NVStackControllerDelegate.m b/NavigationReactNative/src/ios/NVStackControllerDelegate.m new file mode 100644 index 0000000000..33c0613ed6 --- /dev/null +++ b/NavigationReactNative/src/ios/NVStackControllerDelegate.m @@ -0,0 +1,55 @@ +#import "NVStackControllerDelegate.h" + +#import + + +@implementation NVStackControllerDelegate +{ + __weak id _stackView; +} + +- (id)initWithStackView:(id)stackView +{ + if (self = [super init]) { + _stackView = stackView; + } + return self; +} + +- (void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated +{ + [_stackView navigationController:navigationController willShowViewController:viewController animated:animated]; +} + +- (void)navigationController:(UINavigationController *)navigationController didShowViewController:(UIViewController *)viewController animated:(BOOL)animated +{ + [_stackView navigationController:navigationController didShowViewController:viewController animated:animated]; +} + +@end + + +@implementation NVStackControllerTransitionDelegate +{ + __weak id _stackView; +} + +- (id)initWithStackView:(id)stackView +{ + if (self = [super initWithStackView:stackView]) { + _stackView = stackView; + } + return self; +} + +- (id)navigationController:(UINavigationController *)navigationController animationControllerForOperation:(UINavigationControllerOperation)operation fromViewController:(UIViewController *)fromVC toViewController:(UIViewController *)toVC +{ + return [_stackView navigationController:navigationController animationControllerForOperation:operation fromViewController:fromVC toViewController:toVC]; +} + +- (id)navigationController:(UINavigationController *)navigationController interactionControllerForAnimationController:(id)animationController +{ + return [_stackView navigationController:navigationController interactionControllerForAnimationController:animationController]; +} + +@end diff --git a/NavigationReactNative/src/ios/NavigationReactNative.xcodeproj/project.pbxproj b/NavigationReactNative/src/ios/NavigationReactNative.xcodeproj/project.pbxproj index 99c8bd6159..177ade77ac 100644 --- a/NavigationReactNative/src/ios/NavigationReactNative.xcodeproj/project.pbxproj +++ b/NavigationReactNative/src/ios/NavigationReactNative.xcodeproj/project.pbxproj @@ -108,6 +108,8 @@ 70BB32A3212B2D4100DE0D13 /* NVBarView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = NVBarView.m; sourceTree = ""; }; 70CCAAC42B18F9B200747AFF /* NVBottomSheetDialogComponentView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = NVBottomSheetDialogComponentView.h; sourceTree = ""; }; 70CCAAC52B18F9C500747AFF /* NVBottomSheetDialogComponentView.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = NVBottomSheetDialogComponentView.mm; sourceTree = ""; }; + 70DB41D82E9943F100D6D676 /* NVStackControllerDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = NVStackControllerDelegate.h; sourceTree = ""; }; + 70DB41D92E99455F00D6D676 /* NVStackControllerDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = NVStackControllerDelegate.m; sourceTree = ""; }; 70DC9BDC2843AA930047EEF4 /* NVActionBarComponentView.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = NVActionBarComponentView.mm; sourceTree = ""; }; 70DC9BDD2843AAA00047EEF4 /* NVActionBarComponentView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = NVActionBarComponentView.h; sourceTree = ""; }; 70DC9BDE2843AB800047EEF4 /* NVCollapsingBarComponentView.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = NVCollapsingBarComponentView.mm; sourceTree = ""; }; @@ -161,6 +163,8 @@ 7020F6B520ECD07A00E7A74E = { isa = PBXGroup; children = ( + 70DB41D92E99455F00D6D676 /* NVStackControllerDelegate.m */, + 70DB41D82E9943F100D6D676 /* NVStackControllerDelegate.h */, 70F1854E2D04B4210094022B /* NVSharedElementManager.m */, 70F1854D2D04B40A0094022B /* NVSharedElementView.h */, 70F1854C2D04B3FF0094022B /* NVSharedElementView.m */,