The interaction's view (or an ancestor) must have an associated view controller for presentation to work.

Still struggling with menus in iOS 13/14.... I tried to add a menu to the navigation bar.... and I am also getting the same error as the context menus...

"The interaction's view (or an ancestor) must have an associated view controller for presentation to work."

The UIWindow has a rootViewController...the stand-alone Navigation bar is a subview of the UIWindow... and I add the menu to the navigation bar with:

UIBarButtonItem *options = [[UIBarButtonItem alloc] initWithImage:[UIImage systemImageNamed:@"list.bullet"] menu:[windowmenu menu]];

The entire application is generated programmatically, not using Interface Builder so maybe I am not hooking things up correctly, or need to add something I am missing.

Do I need to connect the UIViewController to the UINavigationBar, UITableView etc somehow?
  • Think I may be seeing what is happening... after reading an article about the UIResponder chain, I wrote a recursive function to traverse the UIResponder chain up... according to the Apple documentation on this, https://developer.apple.com/documentation/uikit/touches_presses_and_gestures/using_responders_and_the_responder_chain_to_handle_events?language=objc the UIViewController should be in the chain... but traversing it via nextResponder the UIViewController does not show up in the chain. I assume this is because I programmatically created the DWWindow, DWViewController and all the subviews. I suspect that the UIMenu code traverses the responder chain and does not find the UIViewController and produces this error. My question now is... how do I correctly add the UIViewController to the UIResponder chain?

  • Maybe that isn't the problem after all, I added nextResponder overrides to DWView and DWWindow to include DWViewController in the chain... and I still get the error... here is the console output with my UIResponder chain dump from the UITableView context menu:

    ` === Starting responder chain dump ===

    <DWContainer: 0x103839400; baseClass = UITableView; frame = (6 404; 1002 200); clipsToBounds = YES; tag = 100; gestureRecognizers = <NSArray: 0x2824214a0>; layer = <CALayer: 0x282a67140>; contentOffset: {0, 0}; contentSize: {1002, 176}; adjustedContentInset: {0, 0, 0, 0}; dataSource: <DWContainer: 0x103839400; baseClass = UITableView; frame = (6 404; 1002 200); clipsToBounds = YES; tag = 100; gestureRecognizers = <NSArray: 0x2824214a0>; layer = <CALayer: 0x282a67140>; contentOffset: {0, 0}; contentSize: {1002, 176}; adjustedContentInset: {0, 0, 0, 0}; dataSource: <DWContainer: 0x103839400>>>

    <DWBox: 0x102e24f10; frame = (0 0; 1014 634); layer = <CALayer: 0x282a165e0>>

    <DWNotebookPage: 0x102e31290; frame = (5 45; 1014 634); layer = <CALayer: 0x282a16380>>

    <DWNotebook: 0x102d0bee0; frame = (5 5; 1014 674); tag = 1; layer = <CALayer: 0x282a73260>>

    <DWBox: 0x102e0ba00; frame = (0 0; 1024 684); layer = <CALayer: 0x282a6cd40>>

    <DWView: 0x102d0fe10; frame = (0 60; 1024 684); layer = <CALayer: 0x282a70dc0>>

    <DWViewController: 0x102d101a0>

    <DWWindow: 0x102d0ed30; baseClass = UIWindow; frame = (0 0; 1024 768); autoresize = W+H; gestureRecognizers = <NSArray: 0x282405470>; layer = <UIWindowLayer: 0x282a70b60>>

    <UIWindowScene: 0x102e078b0; scene = <FBSScene: 0x280454780; identifier: sceneID:org.dbsoft.dwindows.dwtest-default>; persistentIdentifier = ADFA76E7-BFE5-4733-B8DD-5B3BEAAEE88D; activationState = UISceneActivationStateForegroundActive; settingsCanvas = <UIWindowScene: 0x102e078b0>; windows = (     "<UIWindow: 0x102e095d0; frame = (0 0; 1024 768); hidden = YES; autoresize = W+H; gestureRecognizers = <NSArray: 0x282401560>; layer = <UIWindowLayer: 0x282a795c0>>",     "<UITextEffectsWindow: 0x102e0d8f0; frame = (0 0; 1024 768); opaque = NO; autoresize = W+H; layer = <UIWindowLayer: 0x282a7c340>>",     "<DWWindow: 0x102d0ed30; baseClass = UIWindow; frame = (0 0; 1024 768); autoresize = W+H; gestureRecognizers = <NSArray: 0x282405470>; layer = <UIWindowLayer: 0x282a70b60>>" )>

    <UIApplication: 0x102d05850>

    <DWAppDel: 0x282858570>

    === Responder chain dump complete ===

    [Assert] Failed to find a presenting view controller for view (<DWContainer: 0x103839400; baseClass = UITableView; frame = (6 404; 1002 200); clipsToBounds = YES; tag = 100; gestureRecognizers = <NSArray: 0x2824214a0>; layer = <CALayer: 0x282a67140>; contentOffset: {0, 0}; contentSize: {1002, 176}; adjustedContentInset: {0, 0, 0, 0}; dataSource: <DWContainer: 0x103839400; baseClass = UITableView; frame = (6 404; 1002 200); clipsToBounds = YES; tag = 100; gestureRecognizers = <NSArray: 0x2824214a0>; layer = <CALayer: 0x282a67140>; contentOffset: {0, 0}; contentSize: {1002, 176}; adjustedContentInset: {0, 0, 0, 0}; dataSource: <DWContainer: 0x103839400>>>) in window (<DWWindow: 0x102d0ed30; baseClass = UIWindow; frame = (0 0; 1024 768); autoresize = W+H; gestureRecognizers = <NSArray: 0x282405470>; layer = <UIWindowLayer: 0x282a70b60>>). The interaction's view (or an ancestor) must have an associated view controller for presentation to work.`

  • I really need an Apple engineer who knows this code to look and tell where it is looking for the View Controller, because as far as I can tell there are view controllers to handle this presentation despite the error's protest.

Accepted Reply

Ok, going to answer my own question. The problem was I was creating the UIWindow manually but I was not using the view returned by UIViewController's "view" property, and then hiding the UITransitionView since it was blocking the rest of the content, therefore the content, including the UINavigationBar and the UITableView that had menus attached to them were not direct descendants of the UIViewController, thus when the system traversed the hierarchy up, it did not find a UIViewController to present the menu, even though there was one in a different branch of the hierarchy of the UIWindow.

The fix is once the UIWindow is created with the UIViewController in the rootViewController, we query it's "view" property, which will trigger the creation of the UITransitionView hierarchy and since we aren't using nibs it will create a UIView taking up the entire content area. There we can attach the UINavigationBar and any other content, which will not be obstructed and be descendants of the UIViewController so it can be found and avoiding the error in the title of this question.

Replies

The entire application is generated programmatically

So you'd better show the code…
Well the test program has a lot of code... not sure what the best way to paste the code here... so for now https ://hg.dbsoft.org/dwindows/file/tip/ios/dw.m ... if you remove the space from the URL... you can see the source code.

Most of it is in dw_window_new around line 7996 and DWViewController line 1462...

DWMenu, DWMenuItem and DWWindow classes are also important.

When I click the Navigation bar button I get the error in the title of my post.
Since posting URLs seems to be discouraged, I'm posting the seemingly relevant sections here... if you need to see more complete the URL in the last post to see the full source.

Code Block
@implementation DWViewController : UIViewController {}
-(void)viewWillLayoutSubviews
{
    DWWindow *window = (DWWindow *)[[self view] window];
    NSArray *array = [window subviews];
    CGRect frame = [window frame];
    DWView *view = nil;
    UINavigationBar *nav = nil;
    NSInteger sbheight = [[[window windowScene] statusBarManager] statusBarFrame].size.height;
    for(id obj in array)
    {
        if([obj isMemberOfClass:[DWView class]])
            view = obj;
        else if([obj isMemberOfClass:[UINavigationBar class]])
            nav = obj;
        /* Hide the UITransitionView which is blocking the screen...
         * This is probably not the correct solution, but it solves the
         * problem for the moment.  Figure out what to do with this view.
         */
        else
            [obj setHidden:YES];
    }
    /* Adjust the frame to account for the status bar and navigation bar if it exists */
    if(nav)
    {
        CGRect navrect = [nav frame];
        navrect.size.width = frame.size.width;
        navrect.origin.x = 0;
        navrect.origin.y = sbheight;
        sbheight += navrect.size.height;
        [nav setFrame:navrect];
        if (@available(iOS 14.0, *)) {
            DWMenu *windowmenu = [window menu];
            UINavigationItem *item = [[nav items] firstObject];
            if(windowmenu && !item.rightBarButtonItem)
            {
                UIBarButtonItem *options = [[UIBarButtonItem alloc] initWithImage:[UIImage systemImageNamed:@"list.bullet"]
                                                                                 menu:[windowmenu menu]];
                item.rightBarButtonItem = options;
            }
        } else {
        }
    }
    frame.size.height -= sbheight;
    /* Account for the special area on iPhone X and iPad Pro
     */
    frame.size.height -= 24;
    frame.origin.y += sbheight;
    [view setFrame:frame];
    [view windowResized:frame.size];
}
@end
DW_FUNCTION_DEFINITION(dw_window_new, HWND, DW_UNUSED(HWND hwndOwner), const char *title, DW_UNUSED(ULONG flStyle))
DW_FUNCTION_ADD_PARAM3(hwndOwner, title, flStyle)
DW_FUNCTION_RETURN(dw_window_new, HWND)
DW_FUNCTION_RESTORE_PARAM3(DW_UNUSED(hwndOwner), HWND, title, char *, DW_UNUSED(flStyle), ULONG)
{
    DW_FUNCTION_INIT;
    CGRect screenrect = [[UIScreen mainScreen] bounds];
    DWWindow *window = [[DWWindow alloc] initWithFrame:screenrect];
    DWView *view = [[DWView alloc] init];
    UIUserInterfaceStyle style = [[DWObj hiddenWindow] overrideUserInterfaceStyle];
    [window setWindowLevel:UIWindowLevelNormal];
    [window setRootViewController:[[DWViewController alloc] init]];
    [window addSubview:view];
    [window setBackgroundColor:[UIColor systemBackgroundColor]];
    /* TODO: Handle style flags... if we can? There is no visible frame */
    if(@available(iOS 13.0, *)) {
        NSString *nstitle = [NSString stringWithUTF8String:title];
        [window setLargeContentTitle:nstitle];
        if(flStyle & DW_FCF_TITLEBAR)
        {
            NSInteger sbheight = [[[window windowScene] statusBarManager] statusBarFrame].size.height;
            UINavigationBar* navbar = [[UINavigationBar alloc] initWithFrame:CGRectMake(0, sbheight, screenrect.size.width, 40)];
            UINavigationItem* navItem = [[UINavigationItem alloc] initWithTitle:nstitle];
            [navbar setItems:@[navItem]];
            [window addSubview:navbar];
        }
    }
    /* Copy the overrideUserInterfaceStyle property from the hiddenWindow */
    if(style != UIUserInterfaceStyleUnspecified)
        [window setOverrideUserInterfaceStyle:style];
    DW_FUNCTION_RETURN_THIS(window);
}


Ok, going to answer my own question. The problem was I was creating the UIWindow manually but I was not using the view returned by UIViewController's "view" property, and then hiding the UITransitionView since it was blocking the rest of the content, therefore the content, including the UINavigationBar and the UITableView that had menus attached to them were not direct descendants of the UIViewController, thus when the system traversed the hierarchy up, it did not find a UIViewController to present the menu, even though there was one in a different branch of the hierarchy of the UIWindow.

The fix is once the UIWindow is created with the UIViewController in the rootViewController, we query it's "view" property, which will trigger the creation of the UITransitionView hierarchy and since we aren't using nibs it will create a UIView taking up the entire content area. There we can attach the UINavigationBar and any other content, which will not be obstructed and be descendants of the UIViewController so it can be found and avoiding the error in the title of this question.