I'm trying to piece together how I should reason about multiple windows, activating menus and items.
For instance, I have an email command. If a document is open, it emails just that document. If the collection view holding that document is open, it generates an attachment representing the collection. (this happens with buttons right now but this really feels like a menu item) How do I tell which window is active (document/collection) in order to have the right menu item available.
If the user is adding a document I don't want the "new" command open, I only want one editing view. I saw sample code to include or remove commands but not disable them. I feel like there's a whole conceptual layer I want to understand with the interplay with scenes but don't know where to look for documentation. I searched here but there's hardly any threads on this.
TIA
My menu code is all in Objective-C too (old app still going). I've been using UIMenuBuilder
since iOS 14 in support of macOS via Mac Catalyst. My proposed solution also works now on the iPad in iOS 26.
Your case is slightly more complicated than mine. I support multiple scenes but all of the scenes are the same class with the same root view controller class.
First, design your overall app menu taking into account the needs of general app-level menu items and items specific to each of your scenes' root view controllers. In the end you will have common menu items used by all scenes, you will have items specific to each scene, and you will have items that work even if there are no open scenes.
Once you know all of the possible menu items, you will use UIMenuBuilder
in the app delegate to build the entire possible menu structure.
Then there will be validation code in the app delegate and each of your possible root view controllers. Each root view controller will only validate the items it can handle. For those it can't it calls super
. And the app delegate handles what it can and for those it can't it disables them.
With that general approach, if Scene A is in focus, then it validates its own specific menu item and punts to the app delegate for the rest. The app delegate will handle a few and disable the rest. This means that while Scene A is in focus, any menu items specific to Scene B will be disabled (in the app delegate by default).
Here's some example skeleton code:
In AppDelegate.m:
- (void)buildMenuWithBuilder:(id<UIMenuBuilder>)builder {
[super buildMenuWithBuilder:builder];
if (builder.system != UIMenuSystem.mainSystem) {
return;
}
// Remove unwanted standard File menu:
[builder removeMenuForIdentifier:UIMenuOpenRecent];
[builder removeMenuForIdentifier:UIMenuOpen];
[builder removeMenuForIdentifier:UIMenuDocument];
// Add some new File menu items
UICommand *item1 = [UIKeyCommand commandWithTitle:@"Item 1" image:[UIImage systemImageNamed:@"some.symbol"] action:@selector(item1MenuAction:) input:@"I" modifierFlags:UIKeyModifierShift | UIKeyModifierCommand propertyList:nil];
UICommand *item2 = [UIKeyCommand commandWithTitle:@"Item 2" image:[UIImage systemImageNamed:@"other.symbol"] action:@selector(item2MenuAction:) input:@"J" modifierFlags:UIKeyModifierShift | UIKeyModifierCommand propertyList:nil];
UIMenu *extraItems = [UIMenu menuWithTitle:@"" image: nil identifier:nil options:UIMenuOptionsDisplayInline children:@[ item1, item2 ]]; // NO_I18N
[builder insertSiblingMenu:extraItems afterMenuForIdentifier:UIMenuClose];
// Build everything else as needed
}
- (void)validateCommand:(UICommand *)command {
// Handle App level items here
if (
command.action == @selector(someMenuAction:) ||
command.action == @selector(otherMenuAction:) ||
NO) {
command.attributes &= ~UIMenuElementAttributesDisabled; // Always on
} else if (
command.action == @selector(anotherMenuAction:) ||
NO) {
if (someCondition) {
command.attributes &= ~UIMenuElementAttributesDisabled;
} else {
command.attributes |= UIMenuElementAttributesDisabled;
}
} else {
[super validateCommand:command];
}
}
Now add a validateCommand:
to each of your root view controller classes. It should validate each of its own commands as needed. Call super
for all other commands.
Tedious but straight forward.