From 879984f9aba5493e2a9f3aeb9fa67e3b391fd2d1 Mon Sep 17 00:00:00 2001 From: Andrei Salavei Date: Thu, 3 Sep 2026 13:33:20 +0200 Subject: [PATCH] Refactor CMPEditMenuView.isEditMenuShown flag --- .../CMPUIKitObjcUtils/CMPEditMenuView.m | 122 +++++++++++++----- 1 file changed, 91 insertions(+), 31 deletions(-) diff --git a/compose/ui/ui-uikit/src/iosMain/objc/CMPUIKitUtils/CMPUIKitObjcUtils/CMPEditMenuView.m b/compose/ui/ui-uikit/src/iosMain/objc/CMPUIKitUtils/CMPUIKitObjcUtils/CMPEditMenuView.m index f36f865ca1971..9f51e7c89b78c 100644 --- a/compose/ui/ui-uikit/src/iosMain/objc/CMPUIKitUtils/CMPUIKitObjcUtils/CMPEditMenuView.m +++ b/compose/ui/ui-uikit/src/iosMain/objc/CMPUIKitUtils/CMPUIKitObjcUtils/CMPEditMenuView.m @@ -59,6 +59,13 @@ - (void)hideAllMenusSkipping:(CMPEditMenuView *)skipEditMenuView { @end +typedef enum : NSUInteger { + CMPEditMenuStateHidden = 0, + CMPEditMenuStatePresenting, + CMPEditMenuStatePresented, + CMPEditMenuStateHiding, +} CMPEditMenuState; + @interface CMPEditMenuView() @property (weak, nonatomic, nullable) UIView *rootView; @@ -82,7 +89,7 @@ @interface CMPEditMenuView() @property (strong, nonatomic, nullable) dispatch_block_t presentInteractionBlock; @property (assign, nonatomic) CGRect targetRect; -@property (assign, nonatomic) BOOL isEditMenuShown; +@property (assign, nonatomic) CMPEditMenuState editMenuState; @property (readwrite) UIEditMenuInteraction* editInteraction API_AVAILABLE(ios(16.0)); @@ -123,21 +130,56 @@ - (void)showEditMenuAtRect:(CGRect)targetRect if (@available(iOS 16, *)) { [[CMPEditMenuViewRegister shared] hideAllMenusSkipping:self]; - if (self.editInteraction == nil || contextMenuItemsChanged || !self.isEditMenuShown) { - BOOL isFirstMenuPresentation = self.presentInteractionBlock == nil; - [self cancelPresentEditMenuInteraction]; - NSTimeInterval delay = isFirstMenuPresentation ? 0 : [self editMenuDelay]; - [self schedulePresentEditMenuInteractionWithDelay:delay]; - } else if (positionChanged) { - [self.editInteraction updateVisibleMenuPositionAnimated:NO]; + + switch (self.editMenuState) { + case CMPEditMenuStateHidden: + case CMPEditMenuStateHiding: + [self cancelPresentEditMenuInteraction]; + [self schedulePresentEditMenuInteractionWithDelay:[self editMenuDelay]]; + self.editMenuState = CMPEditMenuStatePresenting; + break; + + case CMPEditMenuStatePresenting: + if (contextMenuItemsChanged) { + [self cancelPresentEditMenuInteraction]; + [self schedulePresentEditMenuInteractionWithDelay:[self editMenuDelay]]; + } else if (positionChanged) { + if (self.presentInteractionBlock == nil) { + // View appearance already started - jsut set the new locaiton. + [self.editInteraction updateVisibleMenuPositionAnimated:NO]; + } else { + [self cancelPresentEditMenuInteraction]; + [self schedulePresentEditMenuInteractionWithDelay:[self editMenuDelay]]; + } + } + break; + + case CMPEditMenuStatePresented: + if (contextMenuItemsChanged) { + [self cancelPresentEditMenuInteraction]; + [self schedulePresentEditMenuInteractionWithDelay:0]; + self.editMenuState = CMPEditMenuStatePresenting; + } else if (positionChanged) { + [self.editInteraction updateVisibleMenuPositionAnimated:NO]; + self.editMenuState = CMPEditMenuStatePresented; + } + break; } } else { - self.isEditMenuShown = YES; if (contextMenuItemsChanged || positionChanged) { [self hideEditMenu]; [self scheduleShowMenuController]; } + self.editMenuState = CMPEditMenuStatePresenting; + } +} + +- (void)setEditMenuState:(CMPEditMenuState)editMenuState { + if (_editMenuState == editMenuState) { + return; } + + _editMenuState = editMenuState; } - (void)updateAvailableSystemActions:(void (^)(void))copyBlock @@ -152,6 +194,15 @@ - (void)updateAvailableSystemActions:(void (^)(void))copyBlock self.systemSelectAllBlock = selectAllBlock; } +- (BOOL)isEditMenuShown { + if (@available(iOS 16, *)) { + return _editMenuState == CMPEditMenuStatePresenting || _editMenuState == CMPEditMenuStatePresented; + } else { + return _editMenuState == CMPEditMenuStatePresenting || + (_editMenuState == CMPEditMenuStatePresented && [UIMenuController sharedMenuController].menuVisible); + } +} + - (void)didMoveToWindow { [super didMoveToWindow]; @@ -172,6 +223,7 @@ - (void)scheduleShowMenuController { UIMenuController *controller = [UIMenuController sharedMenuController]; controller.menuItems = [self makeCustomMenuItems]; [self becomeFirstResponder]; + self.editMenuState = CMPEditMenuStatePresented; [controller showMenuFromView:self rect:self.targetRect]; self.showContextMenuBlock = nil; @@ -249,23 +301,16 @@ - (void)setEditInteraction:(UIEditMenuInteraction *)editInteraction API_AVAILABL _editInteraction = editInteraction; } -- (void)presentEditMenuInteraction API_AVAILABLE(ios(16.0)) { - NSAssert(self.editInteraction != nil, @"Edit Interaction must be initialized"); - - UIEditMenuConfiguration *config = [UIEditMenuConfiguration configurationWithIdentifier:nil - sourcePoint:self.targetRect.origin]; - [self.editInteraction presentEditMenuWithConfiguration:config]; -} - - (void)schedulePresentEditMenuInteractionWithDelay:(NSTimeInterval)delay API_AVAILABLE(ios(16.0)) { __weak __auto_type weak_self = self; self.presentInteractionBlock = dispatch_block_create(0 ,^{ __auto_type self = weak_self; - if (self.editInteraction == nil) { - self.editInteraction = [[UIEditMenuInteraction alloc] initWithDelegate:self]; - [self addInteraction:self.editInteraction]; - } - [self presentEditMenuInteraction]; + self.presentInteractionBlock = nil; + self.editInteraction = [[UIEditMenuInteraction alloc] initWithDelegate:self]; + [self addInteraction:self.editInteraction]; + UIEditMenuConfiguration *config = [UIEditMenuConfiguration configurationWithIdentifier:nil + sourcePoint:self.targetRect.origin]; + [self.editInteraction presentEditMenuWithConfiguration:config]; }); dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delay * NSEC_PER_SEC)), dispatch_get_main_queue(), @@ -286,15 +331,25 @@ - (BOOL)canBecomeFirstResponder { - (void)hideEditMenu { if (@available(iOS 16, *)) { [self cancelPresentEditMenuInteraction]; - - if (self.editInteraction != nil) { - [self.editInteraction dismissMenu]; - [self removeInteraction:self.editInteraction]; - self.editInteraction = nil; + switch (self.editMenuState) { + case CMPEditMenuStateHidden: + case CMPEditMenuStateHiding: + break; + + case CMPEditMenuStatePresenting: + case CMPEditMenuStatePresented: + self.editMenuState = CMPEditMenuStateHiding; + + if (self.editInteraction != nil) { + UIEditMenuInteraction *interaction = self.editInteraction; + [interaction dismissMenu]; + self.editInteraction = nil; + [self removeInteraction:interaction]; + } } } else { - self.isEditMenuShown = NO; [self cancelShowMenuController]; + self.editMenuState = CMPEditMenuStateHidden; [[UIMenuController sharedMenuController] hideMenu]; } @@ -447,11 +502,16 @@ - (CGRect)editMenuInteraction:(UIEditMenuInteraction *)interaction - (void)editMenuInteraction:(UIEditMenuInteraction *)interaction willDismissMenuForConfiguration:(UIEditMenuConfiguration *)configuration animator:(id)animator API_AVAILABLE(ios(16.0)) { + if (self.editInteraction != interaction) { + return; + } + self.editInteraction = nil; + self.editMenuState = CMPEditMenuStateHiding; __weak __auto_type weak_self = self; [animator addCompletion:^{ __auto_type self = weak_self; - if (self.editInteraction == interaction) { - self.isEditMenuShown = NO; + if (self.editInteraction == nil) { + self.editMenuState = CMPEditMenuStateHidden; } }]; } @@ -463,7 +523,7 @@ - (void)editMenuInteraction:(UIEditMenuInteraction *)interaction [animator addCompletion:^{ __auto_type self = weak_self; if (self.editInteraction == interaction) { - self.isEditMenuShown = YES; + self.editMenuState = CMPEditMenuStatePresented; } }]; }