Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions ios/RNSBarButtonItem.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,14 @@

typedef void (^RNSBarButtonItemAction)(NSString *buttonId);
typedef void (^RNSBarButtonMenuItemAction)(NSString *menuId);
typedef void (^RNSBarButtonMenuPresentedCallback)(void);

@interface RNSBarButtonItem : UIBarButtonItem

- (instancetype)initWithConfig:(NSDictionary<NSString *, id> *)dict
action:(RNSBarButtonItemAction)action
menuAction:(RNSBarButtonMenuItemAction)menuAction
menuPresented:(RNSBarButtonMenuPresentedCallback)menuPresented
imageLoader:(RCTImageLoader *)imageLoader;

@end
27 changes: 26 additions & 1 deletion ios/RNSBarButtonItem.mm
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ @implementation RNSBarButtonItem {
- (instancetype)initWithConfig:(NSDictionary<NSString *, id> *)dict
action:(RNSBarButtonItemAction)action
menuAction:(RNSBarButtonMenuItemAction)menuAction
menuPresented:(RNSBarButtonMenuPresentedCallback)menuPresented
imageLoader:(RCTImageLoader *)imageLoader
{
self = [super init];
Expand Down Expand Up @@ -115,7 +116,31 @@ - (instancetype)initWithConfig:(NSDictionary<NSString *, id> *)dict
if (@available(tvOS 17.0, *)) {
NSDictionary *menu = dict[@"menu"];
if (menu) {
self.menu = [[self class] initUIMenuWithDict:menu menuAction:menuAction imageLoader:imageLoader];
UIMenu *builtMenu = [[self class] initUIMenuWithDict:menu menuAction:menuAction imageLoader:imageLoader];

#if !TARGET_OS_TV
if (menuPresented && builtMenu) {
if (@available(iOS 15.0, *)) {
NSArray<UIMenuElement *> *children = builtMenu.children;
UIDeferredMenuElement *sentinel =
[UIDeferredMenuElement elementWithUncachedProvider:^(void (^completion)(NSArray<UIMenuElement *> *)) {
dispatch_async(dispatch_get_main_queue(), ^{
if (menuPresented) {
menuPresented();
}
completion(children);
});
}];
builtMenu = [UIMenu menuWithTitle:builtMenu.title
image:builtMenu.image
identifier:builtMenu.identifier
options:builtMenu.options
children:@[sentinel]];
}
}
#endif // !TARGET_OS_TV

self.menu = builtMenu;
}
}
#endif
Expand Down
3 changes: 3 additions & 0 deletions ios/RNSScreenStack.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ NS_ASSUME_NONNULL_BEGIN
- (void)updateScreenTransition:(double)progress;
- (void)finishScreenTransition:(BOOL)canceled;

- (void)headerMenuWillPresent;
- (void)headerMenuDidDismiss;

@property (nonatomic) BOOL customAnimation;
@property (nonatomic) BOOL disableSwipeBack;

Expand Down
69 changes: 69 additions & 0 deletions ios/RNSScreenStack.mm
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,41 @@ @implementation RNSPanGestureRecognizer
@end
#endif

@interface RNSScreenStackView (RNSHeaderMenuPrivate)
- (void)cancelTouchesInParent;
@end

@interface RNSMenuDismissGestureRecognizer : UIGestureRecognizer
@property (nonatomic, weak) RNSScreenStackView *stackView;
@property (nonatomic, weak) UINavigationBar *navigationBar;
@end

@implementation RNSMenuDismissGestureRecognizer

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
[super touchesBegan:touches withEvent:event];

UITouch *touch = touches.anyObject;
if (!touch) {
self.state = UIGestureRecognizerStateFailed;
return;
}

CGPoint point = [touch locationInView:self.stackView];

if (self.navigationBar && CGRectContainsPoint(self.navigationBar.frame, point)) {
self.state = UIGestureRecognizerStateFailed;
return;
}

[self.stackView cancelTouchesInParent];
self.state = UIGestureRecognizerStateRecognized;
[self.stackView headerMenuDidDismiss];
}

@end

@implementation RNSScreenStackView {
UINavigationController *_controller;
NSMutableArray<RNSScreenView *> *_reactSubviews;
Expand All @@ -175,6 +210,8 @@ @implementation RNSScreenStackView {
RNSPercentDrivenInteractiveTransition *_interactionController;
__weak RNSScreenStackManager *_manager;
BOOL _updateScheduled;
BOOL _isMenuPresented;
RNSMenuDismissGestureRecognizer *_menuDismissGestureRecognizer;
}

- (instancetype)initWithFrame:(CGRect)frame
Expand Down Expand Up @@ -781,6 +818,38 @@ - (void)cancelTouchesInParent
[[self rnscreens_findTouchHandlerInAncestorChain] rnscreens_cancelTouches];
}

- (void)headerMenuWillPresent
{
if (_isMenuPresented) {
return;
}
_isMenuPresented = YES;

[self cancelTouchesInParent];
_controller.topViewController.view.userInteractionEnabled = NO;

_menuDismissGestureRecognizer = [[RNSMenuDismissGestureRecognizer alloc] initWithTarget:nil action:nil];
_menuDismissGestureRecognizer.stackView = self;
_menuDismissGestureRecognizer.navigationBar = _controller.navigationBar;
_menuDismissGestureRecognizer.cancelsTouchesInView = YES;
[self addGestureRecognizer:_menuDismissGestureRecognizer];
}

- (void)headerMenuDidDismiss
{
if (!_isMenuPresented) {
return;
}
_isMenuPresented = NO;

_controller.topViewController.view.userInteractionEnabled = YES;

if (_menuDismissGestureRecognizer) {
[self removeGestureRecognizer:_menuDismissGestureRecognizer];
_menuDismissGestureRecognizer = nil;
}
}

- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer
{
if (_disableSwipeBack) {
Expand Down
10 changes: 10 additions & 0 deletions ios/RNSScreenStackHeaderConfig.mm
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
#import "RNSConvert.h"
#import "RNSDefines.h"
#import "RNSScreen.h"
#import "RNSScreenStack.h"
#import "RNSSearchBar.h"
#import "UINavigationBar+RNSUtility.h"

Expand Down Expand Up @@ -777,6 +778,10 @@ - (void)applySemanticContentAttributeIfNeededToNavCtrl:(UINavigationController *
for (NSUInteger i = 0; i < dicts.count; i++) {
NSDictionary *dict = dicts[i];
if (dict[@"buttonId"] || dict[@"menu"]) {
UIView *stackCandidate = self.screenView.controller.navigationController.view.superview;
__weak RNSScreenStackView *weakStackView =
[stackCandidate isKindOfClass:RNSScreenStackView.class] ? (RNSScreenStackView *)stackCandidate : nil;

RNSBarButtonItem *item = [[RNSBarButtonItem alloc] initWithConfig:dict
action:^(NSString *buttonId) {
auto eventEmitter = std::static_pointer_cast<const facebook::react::RNSScreenStackHeaderConfigEventEmitter>(
Expand All @@ -788,6 +793,8 @@ - (void)applySemanticContentAttributeIfNeededToNavCtrl:(UINavigationController *
}
}
menuAction:^(NSString *menuId) {
[weakStackView headerMenuDidDismiss];

auto eventEmitter = std::static_pointer_cast<const facebook::react::RNSScreenStackHeaderConfigEventEmitter>(
self->_eventEmitter);
if (eventEmitter && menuId) {
Expand All @@ -796,6 +803,9 @@ - (void)applySemanticContentAttributeIfNeededToNavCtrl:(UINavigationController *
.menuId = std::string([menuId UTF8String])});
}
}
menuPresented:^{
[weakStackView headerMenuWillPresent];
}
imageLoader:_imageLoader];
NSNumber *index = dict[@"index"];
if (index.integerValue < items.count) {
Expand Down