简体   繁体   中英

Menu items grayed out using split view controller - Catalyst

I'm working to bring my iPad app to the Mac using Catalyst. My app uses a split view controller. The master view controller has two rows that can be tapped that let a user take a photo or select a photo from the camera roll. I am trying to add two menu items with keyboard shortcuts for each of these actions.

After adding the menu items following the steps in a WWDC video, the menu items are grayed out when the app first launches. Below is a screenshot showing the app first launched and Take Photo and Select From Camera Roll are both grayed out. 变灰的菜单项

However, if I toggle/tap any item in the master view controller, the menu items become enabled and work as intended. Once I select a menu item, it goes back to being grayed out and I need to toggle/tap an item in the master view controller to make them enabled again. 启用的菜单项

I can't figure out why the items are being grayed out. I'm thinking it may be related to the split view controller but have not been able to figure anything out.

The code I'm using to add the menu items is pretty simple. I added @IBAction to the methods that take a photo and select from the camera roll. I added a Main Menu to my Storyboard file with two new Inline Menu items and connected each to to the methods.

UPDATE: I mixed it up. This is only valid for a macOS but not for a Catalyst app.!!.

For Catalyst the best way is to use a menu builder and related functionality.

------ Ignore this if its about Catalyst -------

Did you try switch off "Auto Enables Items" for the menu. If this will not help or you want to control activation I would follow the documentation from Apple here or try with something like below in your view controller.

 override func validate(_ command: UICommand) {

      switch command.action {
      case #selector(doSomething):

          command.title = "Change the title"
          command.attributes = [.disabled]
            // command.attributes = []

        default:
        break
      }
    }

在此处输入图像描述

I'm not entirely happy with the approach, but this is how I've been working around the issue...

The master view controller's viewDidAppear() includes tableView.becomeFirstResponder()

PLUS, the detail controller overrides next to point to the master view controller:

override var next: UIResponder? { return masterViewController }

PLUS, the details view controller's viewDidAppear() must also include:

someSuitableSubview.becomeFirstResponder()

Now my mac Catalyst menus behave mostly as desired!

Is there some better strategy for keeping both of these views in the responder chain, so long as they are being displayed (and their window is front-most)?

I've encountered a perhaps related problem in my Catalyst app.

I'm using menu the menu-builder method, but I find that the app will request a menu for one view controller but try to validate it against another view controller. I presume that this is because I have two view controllers presiding over parts of the same screen.

The workaround I used is to handle queries for one view controller's menu items in the other view controller. For example, let's say you have controllers sharing the screen, called

  • MainVC
  • PlayViewController

The app will call the menu builder for MainVC, but you want to have some hotkeys that call PlaybackVC methods. So you populate the Playback menu with an item that calls PlayViewController.nextClip(), for example.

@available(iOS 13.0, *)
class func playbackMenu() -> UIMenu
{
let prevCmd =
    UIKeyCommand(title: "Previous clip",
                 image: nil,
                 action: #selector(PlayViewController.cmdPrevClip(_:)),
                 input: UIKeyCommand.inputLeftArrow,
                 modifierFlags: .control,
                 propertyList: nil)

let nextCmd =
    UIKeyCommand(title: "Next clip",
                 image: nil,
                 action: #selector(PlayViewController.cmdNextClip(_:)),
                 input: UIKeyCommand.inputRightArrow,
                 modifierFlags: .control,
                 propertyList: nil)

let loopCmd =
    UIKeyCommand(title: "Loop",
                 image: nil,
                 action: #selector(PlayViewController.cmdToggleLoopClip(_:)),
                 input: "L",
                 modifierFlags: .control,
                 propertyList: nil)
let theMenu =
    UIMenu(title: "Playback",
           image: nil,
           identifier: UIMenu.Identifier("com.atomos.AtomRemoteMenus.playbackMenu"),
           options: .destructive,
           children: [prevCmd, nextCmd, loopCmd])

return theMenu
}

Later, when the OS asks MainVC to validate items in the Playback menu, MainVC's canPerformAction() method needs to handle it like so:

else if action == #selector(PlayViewController.cmdNextClip(_:))
{
    if m_playController != nil
    {
        return m_playController!.canPerformAction(#selector(PlayViewController.cmdNextClip(_:)), withSender: sender)
    }
    else
    {
        return false
    }
}

So yes I have a redundant canPerformAction method in PlayViewController because I don't know if it might someday be called.

You might think hey, I'll just make stub methods in MainVC and put calls to those in the Playback menu, and in turn call the PlayViewController's methods. Nope, because each menu item's command targets what it thinks the active view controller is at any given point... which wasn't MainVC in my case.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM