3

I've just stumbled across this issue and was wondering if anyone could help explain the problem.

When in use on an iPad, my view has a nested UINavigationController that is displayed within a popover. I've been converting this code to Swift recently and attempted to use lazy initialisation as follows:

lazy var secondaryNavigationController: UINavigationController = {
    let navController = UINavigationController(rootViewController: menuView)
    navController.modalPresentationStyle = .popover
    navController.popoverPresentationController?.barButtonItem = menuButton
    return navController
}()

Note that menuView and menuButton are also lazily initialised

I then tried to present the view as follows:

@objc private func openMenu() {
    if UIDevice.current.userInterfaceIdiom == .pad {
        present(secondaryNavigationController, animated: true, completion: nil)
    } else {
        navigationController?.pushViewController(menuView, animated: true)
    }
}

The first time I open the menu everything works okay, the menu displays as expected. However if I dismiss the menu by tapping another area of the screen and then try to reopen it for a second time, I get the following crash:

UIPopoverPresentationController () should have a non-nil sourceView or barButtonItem set before the presentation occurs.

This crash goes away if I make the following change to the openMenu() function:

@objc private func openMenu() {
    if UIDevice.current.userInterfaceIdiom == .pad {
        secondaryNavigationController.popoverPresentationController?.barButtonItem = menuButton // hurrah!
        present(secondaryNavigationController, animated: true, completion: nil)
    } else {
        navigationController?.pushViewController(menuView, animated: true)
    }
}

So is the fix above necessary? Am I missing anything obvious that would allow it to be set within the lazy initialisation block?


(Crash Log from example project)

2018-06-29 21:13:09.836061+0100 PopoverBugExample[90568:1465443] * Terminating app due to uncaught exception 'NSGenericException', reason: 'UIPopoverPresentationController () should have a non-nil sourceView or barButtonItem set before the presentation occurs.' * First throw call stack: ( 0 CoreFoundation 0x0000000110c4b7f6 exceptionPreprocess + 294 1 libobjc.A.dylib
0x000000010f2a3035 objc_exception_throw + 48 2 UIKitCore
0x0000000113323a0e -[UIPopoverPresentationController presentationTransitionWillBegin] + 3168 3 UIKitCore
0x000000011332d568 __71-[UIPresentationController _initViewHierarchyForPresentationSuperview:]_block_invoke + 2495 4 UIKitCore 0x000000011332ab5c __56-[UIPresentationController runTransitionForCurrentState]_block_invoke + 468 5 UIKitCore
0x0000000112fd8965 _runAfterCACommitDeferredBlocks + 318 6
UIKitCore 0x0000000112fc77d3 _cleanUpAfterCAFlushAndRunDeferredBlocks + 397 7 UIKitCore 0x0000000112ff7131 _afterCACommitHandler + 141 8 CoreFoundation
0x0000000110baee97 __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION
+ 23 9 CoreFoundation 0x0000000110ba92ce __CFRunLoopDoObservers + 430 10 CoreFoundation 0x0000000110ba9971 __CFRunLoopRun + 1553 11 CoreFoundation
0x0000000110ba9021 CFRunLoopRunSpecific + 625 12 GraphicsServices
0x000000011825c16e GSEventRunModal + 62 13 UIKitCore
0x0000000112fcd3ff UIApplicationMain + 140 14 PopoverBugExample
0x000000010e97c857 main + 71 15 libdyld.dylib
0x00000001120dce61 start + 1 16 ???
0x0000000000000001 0x0 + 1 ) libc++abi.dylib: terminating with uncaught exception of type NSException

Matt Carr
  • 413
  • 5
  • 15
  • I suspect that the issue is that `menuButton` isn't owned by `secondaryNavigationController`, and when the popup is dismissed it is deallocated. What happens if you don't make `menuButton` `lazy`? – Grimxn Jun 28 '18 at 09:08
  • Please share your crash logs here – Anita Nagori Jun 28 '18 at 10:21
  • @Anita see edit for crash logs :) – Matt Carr Jun 29 '18 at 20:51
  • @Grimxn - What alternative to lazy init did you have in mind? I gave it a try by just declaring it as a standard var and initialising in viewDidLoad, but unfortunately the behaviour was the same (crash on the second attempt to load). Had a quick read of the docs and I'm starting to suspect that the popoverPresentationController instance is the issue. I think a new instance is created each time, but with the lazy init approach the barbutton is only set for the first instance. The `UIPopoverPresentationControllerDelegate` could be worth a try. – Matt Carr Jun 29 '18 at 20:52
  • I think you've sussed it out when you say "a new instance is created each time, but with the lazy init approach the barbutton is only set for the first instance" - but you've also fixed it! My comment was a guess (hence not an answer), so I suggest you answer your own question... – Grimxn Jun 30 '18 at 22:17

0 Answers0