Skip to content

Instantly share code, notes, and snippets.

@ktiays
Last active September 17, 2023 14:10
Show Gist options
  • Save ktiays/d9e30be3251322c79cb7e66b353a7a55 to your computer and use it in GitHub Desktop.
Save ktiays/d9e30be3251322c79cb7e66b353a7a55 to your computer and use it in GitHub Desktop.
A utility that can show UIContextMenu at any position programmatically in iOS 17.
//
// Created by ktiays on 2023/9/16.
// Copyright (c) 2023 ktiays. All rights reserved.
//
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
/// A utility class that can show `UIContextMenu` at any position programmatically.
@interface MenuTrigger : NSObject
/// The attached view that shows context menu.
///
/// You have to add it to your view as a subview.
/// You can adjust the frame or update constraints according to your needs.
@property (nonatomic, readonly) UIView *targetView;
/// Shows context menu by simulating clicking on target view.
- (void)showMenu:(UIMenu *)menu;
@end
NS_ASSUME_NONNULL_END
//
// Created by ktiays on 2023/9/16.
// Copyright (c) 2023 ktiays. All rights reserved.
//
#import "MenuTrigger.h"
@interface _UIClickPresentationInteraction : NSObject
@property (nonatomic, nullable) NSArray *allDrivers;
- (void)clickDriver:(id)driver shouldBegin:(void (^)(void))beginHandler;
- (void)clickDriver:(id)arg1 didUpdateHighlightProgress:(CGFloat)progress;
- (void)clickDriver:(id)arg1 didPerformEvent:(NSUInteger)event;
@end
@interface UIContextMenuInteraction ()
@property (nonatomic, nullable) _UIClickPresentationInteraction *presentationInteraction;
@end
@implementation MenuTrigger {
UIButton *_menuButton;
}
- (void)showMenu:(UIMenu *)menu {
if (!menu || !_menuButton.superview) {
return;
}
_menuButton.menu = menu;
_menuButton.showsMenuAsPrimaryAction = YES;
__auto_type contextMenuInteraction = [_menuButton contextMenuInteraction];
if (![contextMenuInteraction isKindOfClass:NSClassFromString(@"_UIVariableGestureContextMenuInteraction")]) {
return;
}
_UIClickPresentationInteraction *presentationInteraction = contextMenuInteraction.presentationInteraction;
NSArray *drivers = presentationInteraction.allDrivers;
id touchDownClickInteractionDriver = nil;
for (id driver in drivers) {
if ([driver isKindOfClass:NSClassFromString(@"_UITouchDownClickInteractionDriver")]) {
touchDownClickInteractionDriver = driver;
}
}
if (!touchDownClickInteractionDriver) {
return;
}
__weak __typeof(touchDownClickInteractionDriver) weakDriver = touchDownClickInteractionDriver;
__weak __typeof(presentationInteraction) interaction = presentationInteraction;
[presentationInteraction clickDriver:touchDownClickInteractionDriver shouldBegin:^{
__strong __typeof(weakDriver) strongDriver = weakDriver;
__strong __typeof(interaction) strongInteraction = interaction;
if (!weakDriver || !interaction) {
return;
}
[strongInteraction clickDriver:strongDriver didUpdateHighlightProgress:1];
for (NSUInteger i = 0; i <= 3; ++i) {
[strongInteraction clickDriver:strongDriver didPerformEvent:i];
}
}];
}
- (UIView *)targetView {
if (!_menuButton) {
_menuButton = [[UIButton alloc] init];
_menuButton.userInteractionEnabled = NO;
}
return _menuButton;
}
@end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment