diff --git a/ios/MenuView.mm b/ios/MenuView.mm index 228583b..85d46c6 100644 --- a/ios/MenuView.mm +++ b/ios/MenuView.mm @@ -19,6 +19,8 @@ @implementation MenuView { UIColor *_textColor; UIColor *_checkedColor; UIColor *_uncheckedColor; + BOOL _isChildViewButton; + NSHashTable *_disabledViews; } + (ComponentDescriptorProvider)componentDescriptorProvider @@ -31,6 +33,8 @@ - (instancetype)initWithFrame:(CGRect)frame if (self = [super initWithFrame:frame]) { static const auto defaultProps = std::make_shared(); _props = defaultProps; + _disabledViews = [NSHashTable weakObjectsHashTable]; + _isChildViewButton = NO; } return self; @@ -42,10 +46,8 @@ - (void)mountChildComponentView:(UIView *)childCompone if (index == 0) { // Clean up old child view if exists if (_childView && _childView != childComponentView) { - // Remove from our manual tracking without calling removeFromSuperview - // since React will handle the view hierarchy cleanup - _childView = nil; - _menuButton = nil; + [self cleanupMenuButton]; + [self restoreUserInteractionForDisabledViews]; } _childView = (UIView *)childComponentView; @@ -64,8 +66,9 @@ - (void)unmountChildComponentView:(UIView *)childCompo { // Clean up our references before React unmounts if (index == 0 && _childView == childComponentView) { + [self cleanupMenuButton]; + [self restoreUserInteractionForDisabledViews]; _childView = nil; - _menuButton = nil; } // Let React handle the unmounting @@ -78,9 +81,11 @@ - (void)setupChildViewAsMenuTrigger:(UIView *)childView if ([childView isKindOfClass:[UIButton class]]) { _menuButton = (UIButton *)childView; _menuButton.showsMenuAsPrimaryAction = YES; + _isChildViewButton = YES; [self updateMenuItems:_menuItems selectedIdentifier:nil]; } else { // For non-button children, create an invisible button overlay to show the menu + _isChildViewButton = NO; [self disableUserInteractionRecursively:childView]; // Create an invisible button that covers the entire view @@ -105,12 +110,50 @@ - (void)setupChildViewAsMenuTrigger:(UIView *)childView - (void)disableUserInteractionRecursively:(UIView *)view { - view.userInteractionEnabled = NO; + if (view.userInteractionEnabled) { + [_disabledViews addObject:view]; + view.userInteractionEnabled = NO; + } for (UIView *subview in view.subviews) { [self disableUserInteractionRecursively:subview]; } } +- (void)restoreUserInteractionForDisabledViews +{ + // Create a copy of the objects to iterate over since NSHashTable with weak references + // can have objects deallocated during iteration + NSArray *viewsToRestore = [_disabledViews allObjects]; + + for (UIView *view in viewsToRestore) { + // The view might have been deallocated (weak reference), so check if it's still valid + if (view && view.superview != nil) { + view.userInteractionEnabled = YES; + } + } + [_disabledViews removeAllObjects]; +} + +- (void)cleanupMenuButton +{ + if (_menuButton) { + // If it's not a child view button (i.e., it's our overlay button), remove it + if (!_isChildViewButton && _menuButton.superview == self) { + [_menuButton removeFromSuperview]; + } + // Clear the menu to prevent any lingering references + _menuButton.menu = nil; + _menuButton = nil; + } + _isChildViewButton = NO; +} + +- (void)dealloc +{ + [self cleanupMenuButton]; + [self restoreUserInteractionForDisabledViews]; +} + - (void)updateProps:(Props::Shared const &)props oldProps:(Props::Shared const &)oldProps { const auto &oldViewProps = *std::static_pointer_cast(_props);