buildMenu is called in AppDelegate but not UIViewController
Asked Answered
D

2

9

I'm attempting to create a custom menu for each view in my app, however it appears buildMenu is not being called in View Controllers. Here's an example:

In my AppDelegate, this code is used, which works 100% as expected.

override func buildMenu(with builder: UIMenuBuilder) {

    print("Updating menu from AppDelegate")

    super.buildMenu(with: builder)

    let command = UIKeyCommand(
        input: "W",
        modifierFlags: [.command],
        action: #selector(self.helloWorld(_:))
    )
    command.title = "Hello"

    builder.insertChild(UIMenu(
        __title: "World",
        image: nil,
        identifier: UIMenu.Identifier(rawValue: "com.hw.hello"),
        options: [],
        children: [command]
    ), atEndOfMenu: .file)
}

@objc private func helloWorld(_ sender: AppDelegate) {

    print("Hello world")
}

However I need to change the options available in the menu depending on where the user is in the app, so I tried doing this in a UIViewController:

override func viewDidAppear(_ animated:Bool){
  // Tried all of these to see if any work
    UIMenuSystem.main.setNeedsRebuild()
    UIMenuSystem.context.setNeedsRebuild()
    UIMenuSystem.main.setNeedsRevalidate()
    UIMenuSystem.context.setNeedsRevalidate() 
}

and again..

// This is never called
override func buildMenu(with builder: UIMenuBuilder) {

    print("Updating menu in View Controller")
}

but the buildMenu in the UIViewController is never called :(

Any ideas if this is intended behavior or if there are any workarounds?

Diffidence answered 27/7, 2019 at 23:13 Comment(1)
Not sure what you're trying to do with rebuilding the menu, but you can use the method validateCommand: inside a view controller to update the menu options on the fly (i.e. disable/enable a command or change its title).Pensioner
U
17

For main menus, the system only consults UIApplication and UIApplicationDelegate, since main menus can exist without any window and hence without any UIViewController hierarchy. That's why your override on UIViewController doesn't get called for main menus.

For context menus, the system does consult the full responder chain starting at the view.

If you need to update main menu commands depending on their context:

  • You could leave buildMenu(with:) in UIApplicationDelegate, arrange for delegate to figure out when and what changed and call UIMenuSystem.main.setNeedsRebuild() when it does change, or
  • You could define a private method buildMyMenu(with:) in your UIViewController subclasses, and arrange for buildMenu(with:) in UIApplicationDelegate to call it, or
  • You could build a static menu in buildMenu, and rely on your overrides of canPerformAction(_:withSender:) and validate(_:) to enable or disable or even hide particular commands e.g. by updating the attributes property in your validate(_:) override.
Uranus answered 6/8, 2019 at 20:25 Comment(1)
Do you have any thoughts on this: forums.developer.apple.com/thread/124710 ?Longfellow
I
1

This is the intended behavior. Quoting from docs:

Because menus can exist with no window or view hierarchy, the system only consults UIApplication and UIApplicationDelegate to build the app’s menu bar.

The same docs page explains how you can adjust the menu commands from view controllers, and there is a great sample project too, so make sure to check it.

Ichor answered 9/8, 2020 at 19:44 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.