When implemented the "Open Recent..." menu item under "File" menu, I connected the menu of "Open Recent"'s delegate to "AppDelegate" class. However, the delegate functions never called.
I tried to connect the NSMenuDelegate
with the main menu, the result was the same.
I looked this issue up in stackoverflow site. There are many questions on this. Someone says that "Menu delegates are not used that often, so Apple hasn't made them too easy to set up in Interface Builder. Instead, do this in awakeFromNib:".
I don't accept the theory. But the answer does imply that setting NSMenuDelegate
in code works.
Then I looked up on how to implementing "Open Recent..." menu. I found this post, Respond to Open Recent clicks in NSMenu. I was glad that I could use NSDocumentController
to get the feature of "Open Recent...".
But the routine approach is not suit for my app.
For apps like Pages or Numbers, the files they open varies every time on names. However, for Xliff Tool, the files it opens are exported by Xcode, and the names are fixed. So when using the default approach of NSDocumentController
, the recent files shown may be the same.
As you can see in above picture, there are two files with the same name. In fact they are in different paths. I need to show the files in full paths instead of just filenames.
So the question is back again. I have to make functions to conform to NSMenuDelegate
.
What the magic Apple does to make the "Open Recent..." menu working?
Apple must have implemented its own class that conforms to NSMenuDelegate
protocol. Since in Interface Builder I could not find any, I would debug it on AppDelegate
's applicationDidFinishLaunching(_:)
function.
I connect a @IBOutlet of menu of "Open Recent..." to "AppDelegate", and found that when app runs, the openRecentMenu has been set a NSMenuDelegate
called NSDocumentControllerSubMenuDelegate
.
After another digging, I found that NSDocumentControllerSubMenuDelegate
is a hidden class that should not be used by third-party developers.
Answer
The answer of NSMenuDelegate
set in Interface Builder not working is that for mechanism of magic like "Open Recent...", Apple resets the NSMenuDelegate
of all menus under an app's main menu.
Anyone who wants to use a NSMenuDelegate
, should set it in code.
My Own Solution
After set the NSMenuDelegate
, the rest is easy.
extension AppDelegate:NSMenuDelegate {
func menuNeedsUpdate(_ menu: NSMenu) {
let clearMenuMenuItem = menu.items.last!
let urls = NSDocumentController.shared.recentDocumentURLs
let menuItems = urls.map {
NSMenuItem(title: $0.path, action: #selector(openFile(_:)), keyEquivalent: "")
}
menu.items = [
menuItems,
[NSMenuItem.separator(), clearMenuMenuItem]
].flatMap({$0})
}
}
Others
The differences between @ojbc and @IBAction through an interesting bug/feature of Interface Builder.