UIDeferredMenuElement With Uncached Provider Not Working on Mac Catalyst. Uncached provider block never called and menu displays as "Loading"

I'm trying to create a dynamic menu on Mac Catalyst. Using a UIBarButtonitem like so to make a "pull down" button:

    UIDeferredMenuElement *deferredmenuElement;
    deferredmenuElement = [UIDeferredMenuElement elementWithUncachedProvider:^(void (^ _Nonnull completion)(NSArray<UIMenuElement *> * _Nonnull))
    {
        UIAction *actionOne = [UIAction actionWithTitle:@"Action One" image:nil identifier:nil handler:^(__kindof UIAction * _Nonnull action) {
            NSLog(@"action one fired.");
        }];
        
        UIAction *actionTwo = [UIAction actionWithTitle:@"Action Two" image:nil identifier:nil handler:^(__kindof UIAction * _Nonnull action) {
            NSLog(@"action two fired.");
        }];
        
        UIAction *actionThree = [UIAction actionWithTitle:@"Action Three" image:nil identifier:nil handler:^(__kindof UIAction * _Nonnull action) {
            NSLog(@"action three fired.");
        }];
        
        completion(@[actionOne,actionTwo,actionThree]);
    }];
    
    UIMenu *wrappedMenu = [UIMenu menuWithChildren:@[deferredmenuElement]];
    
    UIBarButtonItem *uiBarButtonItem = [[UIBarButtonItem alloc]initWithTitle:nil
                                                                        menu:wrappedMenu];
    
    uiBarButtonItem.image = [UIImage systemImageNamed:@"rectangle.and.pencil.and.ellipsis"];
    self.navigationItem.rightBarButtonItems = @[uiBarButtonItem];

The button appears in the toolbar but when I click it to expose the menu I get a menu with on element in it that says "Loading...". The the uncached provider block is never called.

Running Ventura 13.2.1 and Xcode 14.2.

Replies

I filed FB12062113

Haven't seen this on Ventura. On Monterey the whole deferred thing didn't work at all but have used it on most every Ventura version without issue. Though I guess I don't have any menus attached to button bar items. I wonder if not implemented there.

Still not fixed in Ventura 13.3. Can you show your code so I can see what's different from mine? I tried using it with a UIButton instead of UIBarButtonItem and then adding it to my NSToolbar with NSUIViewToolbarItem but still not working. The menu just shows a "Loading..." item and the uncached provider block is never called.

Still broken on Ventura 13.5.2.

Also it doesn't work when creating a pull down UIButton. The following button just show "Loading..." and the provider block is never called:

  UIDeferredMenuElement *deferredmenuElement;
    deferredmenuElement = [UIDeferredMenuElement elementWithUncachedProvider:^(void (^ _Nonnull completion)(NSArray<UIMenuElement *> * _Nonnull))
    {
        UIAction *actionOne = [UIAction actionWithTitle:@"Action One" 
                                                  image:nil
                                             identifier:@"fake.action.test"
                                                handler:^(__kindof UIAction * _Nonnull action) {
            NSLog(@"action one fired.");
        }];
        
        
        completion(@[actionOne]);
    }];
    
    UIMenu *someMenu = [UIMenu menuWithChildren:@[deferredmenuElement]];
    UIButton *pullDownButton = [UIButton buttonWithType:UIButtonTypeSystem];
    [pullDownButton setTitle:@"Pull Down Button" forState:UIControlStateNormal];
    [pullDownButton sizeToFit];
    self.button = pullDownButton;
    self.button.menu = someMenu;
    self.button.showsMenuAsPrimaryAction = YES;
    [self.view addSubview:pullDownButton];
    //Position the button...

On Mac Catalyst I did get the UIDeferredMenuElement elementWithUncachedProvider: block to be called only when creating a menu for a UIContextMenuConfiguration directly but if you're actually loading data async to build the menu you can't use dispatch_async_get_main_queue to call the completion block on the main thread.

 UIDeferredMenuElement *deferredMenuElement = [UIDeferredMenuElement elementWithUncachedProvider:^(void (^ _Nonnull completion)(NSArray<UIMenuElement *> * _Nonnull)) {
     //Load whatever async. 
       dispatch_queue_t queue = dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0);
        dispatch_async(queue, ^{

              //You can't do this. The menu will never update because of the run loop mode.
              dispatch_async(dispatch_get_main_queue(), ^{ 
                      //Build UIMenu with data loaded and call the completion block on the main queue.
                      UIMenu *menu = //...make it
                      completion(@[menu])
                   });
    
               //Instead you have to pass the block to a method like this, then invoke the block.
               [self performSelectorOnMainThread:@selector(builtMenuBlock:)
                                   withObject:completion
                                waitUntilDone:NO
                                        modes:@[NSRunLoopCommonModes]];
}];


  • Seems on Sonoma using -performSelectorOnMainThread:withObject:waitUntilDone:modes: is not necessary. When testing on Ventura yesterday that was the only way I could get the menu to update.

Add a Comment

Still broken on macOS Sonoma.

Still broken in 14.4.1. Quality software by Apple. Top notch.