To enable Copy and Paste in my Cocoa app, I added two new menu items (copy and paste) to the menu and dragged the selector from each item to the first responder (copy and paste). However, two extra items show up below the Copy and Paste menu items: 'Start Dictation' and 'Special Characters' .
I haven't been able to figure out why they show up or how I remove them.
Optimally, I don't even want the copy and paste menu items to be visible. I just want the user of my app to be able to paste stuff (i.e. from an email, text doc etc) into a text field on one of the forms in my app.
Of the three approaches already mentioned, setting UserDefaults
is probably the cleanest, though, as noted, it relies on undocumented UserDefaults
entries.
The approach of removing the items by the index position (ie, the last position) in the menu relies on the assumption that they will always be at the end of the menu. That's probably a safe assumption, but Apple could do something else the future.
Relying on the menu item names is also problematic for localization reasons.
It's better to use the selector, as shown for Objective-C, but it won't work quite so simply in Swift, particularly for the "Start Dictation..." menu item. The method you need the selector for is startDictation(_:)
, but unlike in Objective-C you can't just type it like that. You need to specify an @objc
type to which it belongs. So just search Apple's documentation, right? Good luck. It's undocumented, and the method isn't exposed in Swift.
My approach to solving that problem is to add a stub for that method in my AppDelegate
, and then use that to get the selector. You really just need some type Swift can get its teeth into to form the selector. But when the selector is tossed over the wall to the Obj-C side of Cocoa, that type just disappears. All that matters is the method's name, how many parameters, their order, and names. In this case, like most action methods, it takes one parameter (for the sender):
@objc public func startDictation(_: Any) { }
The AppDelegate
is handy for a quick and dirty implementation, but for real use I prefer to create a class that inherits for NSObject
with private
initializers specifically for stub methods like that. That way you ensure that they never pollute the responder chain. Basically make a non-instantiable bag of do-nothing methods you can use to make selectors.
Now we need a way to find the corresponding menu item, so I make an extension on NSMenu
public extension NSMenu
{
func lastMenuItem(where condition: (NSMenuItem) -> Bool) -> NSMenuItem?
{
for item in items.reversed()
{
if let submenu = item.submenu
{
if let foundItem = submenu.lastMenuItem(where: condition) {
return foundItem
}
}
else if condition(item) { return item }
}
return nil
}
}
I choose to search the menu in reverse, under the assumption that if I decided to add my own items at some future date, they will most likely be before the items I'm removing. Then in AppDelegate
:
func removeUnwantedAutomaticMenus()
{
let unwantedActions: [Selector] =
[
#selector(AppDelegate.startDictation(_:)),
#selector(NSApplication.orderFrontCharacterPalette(_:)),
]
for action in unwantedActions {
NSApp.mainMenu?.lastMenuItem { $0.action == action }?.isHidden = true
}
}
As you can see, I choose to hide the item rather than remove them, but you could certainly remove them instead. All that's left is just to call removeUnwantedAutomaticMenus()
in AppDelegate.applicationDidFinishLaunching
.
If you create your menus programmatically, you can make this more robust by using the tag
property in NSMenuItem
to mark items you added, then check for that tag to make sure you don't remove/hide those instead of the automatically added ones.
Another approach is to subclass NSMenu
, overriding its addItem
and insertItem
methods to check the NSMenuItem
's tag before adding them. Simply don't add/insert any NSMenuItem
s with an incorrect tag, which Apple's automatically inserted items won't have. If you use Storyboards for your menus, it's kind of pain, because you have to make sure the class for each menu is set to your custom class, and the tag for each menu item is set correctly. If you create your menus programmatically it's much easier to ensure everything is set correctly.
Quickest way to fix this is to set the title to "Edit " (with an extra space at the end).
In the interface builder select the Edit menu:
Then from the properties inspector, add an extra space to the title.
As mentioned in Mac OS X Internals: A Systems Approach and Qt Mac (Re)move "Special Characters..." action in Edit menu, you can do something like this in main() before you load the nib (but it is not supported API):
[[NSUserDefaults standardUserDefaults] setBool:YES forKey:@"NSDisabledDictationMenuItem"];
[[NSUserDefaults standardUserDefaults] setBool:YES forKey:@"NSDisabledCharacterPaletteMenuItem"];
Solution for Swift 4 using Storyboards
Add the following code to your AppDelegate
:
func applicationWillFinishLaunching(_ notification: Notification) {
UserDefaults.standard.set(true, forKey: "NSDisabledDictationMenuItem")
UserDefaults.standard.set(true, forKey: "NSDisabledCharacterPaletteMenuItem")
}
The applicationWillFinishLaunching
function is called early in the life-cycle of your application, before the menu is initialized. No need to manually hack the menu items.
Here is the code I am using in my application to remove these automagically added entries to the Edit menu:
- (void) applicationDidFinishLaunching: (NSNotification*)aNotification
{
NSMenu* edit = [[[[NSApplication sharedApplication] mainMenu] itemWithTitle: @"Edit"] submenu];
if ([[edit itemAtIndex: [edit numberOfItems] - 1] action] == NSSelectorFromString(@"orderFrontCharacterPalette:"))
[edit removeItemAtIndex: [edit numberOfItems] - 1];
if ([[edit itemAtIndex: [edit numberOfItems] - 1] action] == NSSelectorFromString(@"startDictation:"))
[edit removeItemAtIndex: [edit numberOfItems] - 1];
if ([[edit itemAtIndex: [edit numberOfItems] - 1] isSeparatorItem])
[edit removeItemAtIndex: [edit numberOfItems] - 1];
}
NOTE: This code needs to go in applicationDidFinishLaunching:
or later, if you place it in applicationWillFinishLaunching:
the entries won't yet be added to the Edit
menu.
Also note, I am using NSSelectorFromString
as using @selector
causes "unknown selector" warnings. (Even with the warning the code does work, but I prefer to have no warnings in my code, so opted to use NSSelectorFromString
to avoid them.)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With