Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

buildMenu is called in AppDelegate but not UIViewController

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?

like image 491
Dave Avatar asked Jul 27 '19 23:07

Dave


2 Answers

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.
like image 129
Glen Low Avatar answered Sep 18 '22 15:09

Glen Low


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.

like image 41
Hejazi Avatar answered Sep 20 '22 15:09

Hejazi