I recently figured out a way to remove the "Show Fonts" and "Show Colors" menus from the context menu of a UITextView. This only works on iOS 16.0/Mac Catalyst 16.0 or later.
The following is my Objective-C code. Should be easy enough to translate to Swift.
I started by adding new method to UIMenu:
Category declaration:
@interface UIMenu (Additions)
- (UIMenu *)menuByRemovingChildWithAction:(SEL)action;
@end
Category implementation:
@implementation UIMenu (Additions)
- (UIMenu *)menuByRemovingChildWithAction:(SEL)action {
NSArray<UIMenuElement *> *children = self.children;
for (NSInteger i = 0; i < children.count; i++) {
UIMenuElement *element = children[i];
if ([element isKindOfClass:[UICommand class]]) {
UICommand *cmd = (UICommand *)element;
if (cmd.action == action) {
NSMutableArray *newChildren = [children mutableCopy];
[newChildren removeObjectAtIndex:i];
if (newChildren.count == 0) {
return nil;
} else {
return [self menuByReplacingChildren:newChildren];
}
}
} else if ([element isKindOfClass:[UIMenu class]]) {
UIMenu *menu = (UIMenu *)element;
UIMenu *newMenu = [menu menuByRemovingChildWithAction:action];
if (newMenu == nil) {
NSMutableArray *newChildren = [children mutableCopy];
[newChildren removeObjectAtIndex:i];
return [self menuByReplacingChildren:newChildren];
} else if (newMenu != menu) {
NSMutableArray *newChildren = [children mutableCopy];
newChildren[i] = newMenu;
return [self menuByReplacingChildren:newChildren];
}
}
}
return self;
}
@end
This recursively goes through a menu hierarchy and removes any menu item that is a UICommand with the given action.
With that in place you need to implement the iOS 16.0 UITextViewDelegate method:
- (UIMenu *)textView:(UITextView *)textView editMenuForTextInRange:(NSRange)range suggestedActions:(NSArray<UIMenuElement *> *)suggestedActions API_AVAILABLE(ios(16.0)) {
UIMenu *menu = [UIMenu menuWithChildren:suggestedActions];
menu = [menu menuByRemovingChildWithAction:NSSelectorFromString(@"toggleFontPanel:")];
menu = [menu menuByRemovingChildWithAction:NSSelectorFromString(@"orderFrontColorPanel:")];
return menu;
}
That's it. All other attempts failed. I was able to disable the "Show Fonts" menu by overriding canPerformAction:sender: in the App Delegate. But for some reason it did nothing for the "Show Colors" menu.
- (BOOL)canPerformAction:(SEL)action withSender:(id)sender {
if (action == NSSelectorFromString(@"toggleFontPanel:") || action == NSSelectorFromString(@"orderFrontColorPanel:")) {
return NO;
}
BOOL res = [super canPerformAction:action withSender:sender];
return res;
}