NSPopover detached window loses contentViewController

Originator:mayoff
Number:rdar://50144082 Date Originated:2019-04-23
Status:open Resolved:
Product:macOS + SDK Product Version:10.14.5 Beta (18F118d)
Classification:Serious Bug Reproducible:Always
 
Dear AppKitterinos,

You probably know that, to show an NSPopover, we must set its contentViewController property to an NSViewController.

And you probably know that, to detach a popover into a custom window, we must implement detachableWindowForPopover: in the popover’s delegate, and the detached window cannot share the popover’s contentViewController.

So the logical approach is to create an NSViewController subclass to manage the popover’s content and the detached window’s content, and create separate instances of this subclass for the popover and for the detached window.

The problem arises when we create an NSWindow, set its contentViewController to a new instance, and then return the NSWindow from detachableWindowForPopover:. AppKit temporarily replaces the new window’s contentView. It restores the window’s contentView but does not restore the window’s contentViewController. The original contentViewController of the new window is lost.

I will attach a test project. To reproduce the problem in the test project:

1. Launch the test app.
2. Click the “Show Popover” button.
3. Drag the popover to detach it.
4. As soon as we begin the drag, we see a message in the main window showing the description of the newly created ContentViewController.
5. When we release the drag, the detached window shows a message put there by the detached window’s ContentViewController when it was destroyed.

That ContentViewController should not have been destroyed!

Apparently bugreport.apple.com now limits the description field to 3000 characters, so I will continue this report in comments…

Checking the console output, we can see what happened: our detachableWindowForPopover set the window’s contentViewController, which also set the window’s contentView:

    2019-04-23 16:00:54.127720-0500 popover-bug[3575:200709] detachableWindowForPopover: initialized <MyPanel: 0x600003e06c00> with <ContentViewController: 0x600002c27a00> and <NSView: 0x600003306080>

Then AppKit replaced the new window’s contentView by sending it setContentView:, which set the new window’s contentViewController to nil:

    2019-04-23 16:00:54.148743-0500 popover-bug[3575:200709] setContentView: replaced <ContentViewController: 0x600002c27a00> with (null); replaced <NSView: 0x600003306080> with <NSView: 0x600003309cc0>
    2019-04-23 16:00:54.149825-0500 popover-bug[3575:200709] (
    	0   popover-bug                         0x0000000100001648 -[MyPanel setContentView:] + 328
    	1   AppKit                              0x00007fff45fafe8b __28-[NSPopover _dragWithEvent:]_block_invoke + 149
    	2   AppKit                              0x00007fff45552c44 NSPerformVisuallyAtomicChange + 132
    	3   AppKit                              0x00007fff45faf4f3 -[NSPopover _dragWithEvent:] + 1909
    	4   AppKit                              0x00007fff4574b313 forwardMethod + 211
    	5   AppKit                              0x00007fff4574b313 forwardMethod + 211
    	6   AppKit                              0x00007fff4574b313 forwardMethod + 211
    	7   AppKit                              0x00007fff4574b313 forwardMethod + 211
    	8   AppKit                              0x00007fff4574fc17 -[NSWindow(NSEventRouting) _handleMouseDownEvent:isDelayedEvent:] + 5724
    	9   AppKit                              0x00007fff45686482 -[NSWindow(NSEventRouting) _reallySendEvent:isDelayedEvent:] + 2295
    	10  AppKit                              0x00007fff45685943 -[NSWindow(NSEventRouting) sendEvent:] + 478
    	11  AppKit                              0x00007fff4552511f -[NSApplication(NSEvent) sendEvent:] + 331
    	12  AppKit                              0x00007fff45513944 -[NSApplication run] + 755
    	13  AppKit                              0x00007fff45502e4c NSApplicationMain + 777
    	14  popover-bug                         0x0000000100000f52 main + 34
    	15  libdyld.dylib                       0x00007fff73d663d5 start + 1
    )

Later, AppKit restored the new window's contentView, but it did not restore the contentViewController:

    2019-04-23 16:00:54.154460-0500 popover-bug[3575:200709] setContentView: replaced (null) with (null); replaced <NSView: 0x600003309cc0> with <NSView: 0x600003306080>
    2019-04-23 16:00:54.155426-0500 popover-bug[3575:200709] (
    	0   popover-bug                         0x0000000100001648 -[MyPanel setContentView:] + 328
    	1   AppKit                              0x00007fff45faff44 __28-[NSPopover _dragWithEvent:]_block_invoke + 334
    	2   AppKit                              0x00007fff45552c44 NSPerformVisuallyAtomicChange + 132
    	3   AppKit                              0x00007fff45faf4f3 -[NSPopover _dragWithEvent:] + 1909
    	4   AppKit                              0x00007fff4574b313 forwardMethod + 211
    	5   AppKit                              0x00007fff4574b313 forwardMethod + 211
    	6   AppKit                              0x00007fff4574b313 forwardMethod + 211
    	7   AppKit                              0x00007fff4574b313 forwardMethod + 211
    	8   AppKit                              0x00007fff4574fc17 -[NSWindow(NSEventRouting) _handleMouseDownEvent:isDelayedEvent:] + 5724
    	9   AppKit                              0x00007fff45686482 -[NSWindow(NSEventRouting) _reallySendEvent:isDelayedEvent:] + 2295
    	10  AppKit                              0x00007fff45685943 -[NSWindow(NSEventRouting) sendEvent:] + 478
    	11  AppKit                              0x00007fff4552511f -[NSApplication(NSEvent) sendEvent:] + 331
    	12  AppKit                              0x00007fff45513944 -[NSApplication run] + 755
    	13  AppKit                              0x00007fff45502e4c NSApplicationMain + 777
    	14  popover-bug                         0x0000000100000f52 main + 34
    	15  libdyld.dylib                       0x00007fff73d663d5 start + 1
    )

I am experimenting with a workaround for this bug. The workaround is, in ContentViewController's viewWillAppear method, to set self.view.window.contentViewController = self (if it's not already self). This works in the test program, but it seems *very* fishy to change the window's contentViewController in viewWillAppear. You can enable this workaround in the test program by turning on the “Enable Workaround” checkbox.

Please restore the detached window's contentViewController so we don't have to test fishy workarounds.

Thanks,
Rob

Comments


Please note: Reports posted here will not necessarily be seen by Apple. All problems should be submitted at bugreport.apple.com before they are posted here. Please only post information for Radars that you have filed yourself, and please do not include Apple confidential information in your posts. Thank you!