Fatal Exception UIPopoverPresentationController that sourceView is nil after setting sourceView

We are seeing crashes only on iOS 14 (and its sub-releases) that we are unable to reproduce locally with the following stack trace:

Fatal Exception: NSGenericException
UIPopoverPresentationController (<UIPopoverPresentationController: 0x112513b50>) should have a non-nil sourceView or barButtonItem set before the presentation occurs.

Last Exception Backtrace:
0   CoreFoundation                	0x1a36eb9d8 __exceptionPreprocess + 216 (NSException.m:199)
1   libobjc.A.dylib               	0x1b7a6eb54 objc_exception_throw + 56 (objc-exception.mm:565)
2   UIKitCore                     	0x1a577eba4 -[UIPopoverPresentationController presentationTransitionWillBegin] + 3908 (UIPopoverPresentationController.m:1326)
3   UIKitCore                     	0x1a578a1fc __80-[UIPresentationController _initViewHierarchyForPresentationSuperview:inWindow:]_block_invoke + 2392 (UIPresentationController.m:1574)
4   UIKitCore                     	0x1a5787cd8 __56-[UIPresentationController runTransitionForCurrentState]_block_invoke.466 + 532 (UIPresentationController.m:1079)
5   UIKitCore                     	0x1a63fff08 -[_UIAfterCACommitBlock run] + 64 (_UIAfterCACommitQueue.m:122)
6   UIKitCore                     	0x1a5f65984 _runAfterCACommitDeferredBlocks + 296 (UIApplication.m:2920)
7   UIKitCore                     	0x1a5f54eb4 _cleanUpAfterCAFlushAndRunDeferredBlocks + 200 (UIApplication.m:2898)
8   UIKitCore                     	0x1a5f86484 _afterCACommitHandler + 76 (UIApplication.m:2954)
9   CoreFoundation                	0x1a366a87c __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__ + 32 (CFRunLoop.c:1799)
10  CoreFoundation                	0x1a3664f50 __CFRunLoopDoObservers + 604 (CFRunLoop.c:1912)
11  CoreFoundation                	0x1a3665498 __CFRunLoopRun + 960 (CFRunLoop.c:2953)
12  CoreFoundation                	0x1a3664ba0 CFRunLoopRunSpecific + 572 (CFRunLoop.c:3242)
13  GraphicsServices              	0x1ba3ca598 GSEventRunModal + 160 (GSEvent.c:2259)
14  UIKitCore                     	0x1a5f562f4 -[UIApplication _run] + 1052 (UIApplication.m:3253)
15  UIKitCore                     	0x1a5f5b874 UIApplicationMain + 164 (UIApplication.m:4707)
16  libswiftUIKit.dylib           	0x1b6de2b54 UIApplicationMain(_:_:_:_:) + 100 (UIKit.swift:528)
17  IXLMath                       	0x1049da44c main + 58444 (<compiler-generated>:0)
18  libdyld.dylib                 	0x1a3343568 start + 4

We only set modalPresentationStyle = .popover with this method in our UIViewController extension

  @discardableResult
 func setUpPopoverPresentation(sourceView: UIView, sourceRect: CGRect)
 -> UIPopoverPresentationController {
  modalPresentationStyle = .popover
  popoverPresentationController!.sourceView = sourceView
  popoverPresentationController!.sourceRect = sourceRect
  return popoverPresentationController!
 }

We sometimes pass a force unwrapped optional, e.g., @IBOutlet weak var borderLabel: UILabel!, as sourceView, but we don't expect this value to be nil and believe we would see other issues if it were nil.

Are there any requirements for the sourceView or sourceRect we use for the popoverPresentationController besides being non-nil? Can sourceView be a force unwrapped optional as long as it is not nil? Can sourceRect have a 0 height and 0 width?

Is there potentially an iOS 14 bug related to this?

Could bad memory be manifesting itself with this exception?

Does anyone have other ideas about the cause of this issue or suggestions for fixing it?

We have already looked at Apple documentation for UIPopoverPresentationController and popoverPresentationController and have not been able to find answers.

Thank you!

Could you show the code where you call setUpPopoverPresentation ?

Does it crash on iPhone, on iPad ?

We only see the crashes on iPad, but we have a lot more functionality on iPad than iPhone, so it's possible the culprit flow is not available on iPhone.

We call setUpPopoverPresentation in several different places. Here are snippets of how it is called:

// BorderedButton is a subclass of UIButton private let button = BorderedButton(   normal: ColorPalette.LiveMessage.messageButtonBorderNormal,   highlighted: ColorPalette.LiveMessage.messageButtonBorderHighlighted) ... let popoverPresentationController = messagePopoverViewController.setUpPopoverPresentation(    sourceView: button, sourceRect: button.bounds) // within a method for creating dropdown popovers with table views - sourceView is of type UIView    let popoverPresentationController = dropdownTable.setUpPopoverPresentation(    sourceView: sourceView, sourceRect: sourceView.bounds.inset(.init(all: -5)))   @IBOutlet weak var borderLabel: UILabel! //from scene in storyboard ...    let anchorX = borderLabel.frame.width / 2    let anchorY = borderLabel.frame.height    let popoverPresentationController = tableViewController.setUpPopoverPresentation(     sourceView: borderLabel, sourceRect: CGRect(x: anchorX, y: anchorY, width: 0, height: 0))         popoverPresentationController.permittedArrowDirections = .up    present(tableViewController, animated: true, completion: nil) guard let senderView = sender as? UIView else {    return   }       let sourceRect =    CGRect(x: senderView.bounds.midX, y: senderView.bounds.maxY, width: 0, height: 0)   let popoverPresentationController = optionsViewController.setUpPopoverPresentation(    sourceView: senderView, sourceRect: sourceRect)   popoverPresentationController.permittedArrowDirections = .up   settingsContactViewController.present(optionsViewController, animated: true, completion: nil) // sourceView passed in is a UIButton func setPopoverPresentationInfo(sourceView: UIView, sourceRect: CGRect) {   let popoverPresentationController =    setUpPopoverPresentation(sourceView: sourceView, sourceRect: sourceRect)   popoverPresentationController.permittedArrowDirections = UIPopoverArrowDirection(rawValue: 0)   popoverPresentationController.delegate = self   popoverPresentationController.popoverBackgroundViewClass = KeyboardPopoverBackgroundView.self   view.setNeedsLayout()  }

(here is hopefully an easier to read response to @Claude31)

It only crashes on iPad, but we have a lot more functionality on iPad than iPhone, so it's possible the culprit flow is not available on iPhone.

We call setUpPopoverPresentation in several different places. Here are snippets of how it is called:

// BorderedButton is a subclass of UIButton
private let button = BorderedButton(
  normal: ColorPalette.LiveMessage.messageButtonBorderNormal,
  highlighted: ColorPalette.LiveMessage.messageButtonBorderHighlighted)
...
let popoverPresentationController = messagePopoverViewController.setUpPopoverPresentation(
   sourceView: button, sourceRect: button.bounds)
// within a method for creating dropdown popovers with table views - sourceView is of type UIView
   let popoverPresentationController = dropdownTable.setUpPopoverPresentation(
   sourceView: sourceView, sourceRect: sourceView.bounds.inset(.init(all: -5)))
  @IBOutlet weak var borderLabel: UILabel! //from scene in storyboard
...
   let anchorX = borderLabel.frame.width / 2
   let anchorY = borderLabel.frame.height
   let popoverPresentationController = tableViewController.setUpPopoverPresentation(
    sourceView: borderLabel, sourceRect: CGRect(x: anchorX, y: anchorY, width: 0, height: 0))
    
   popoverPresentationController.permittedArrowDirections = .up
   present(tableViewController, animated: true, completion: nil)

  guard let senderView = sender as? UIView else {
   return
  }
   
  let sourceRect =
   CGRect(x: senderView.bounds.midX, y: senderView.bounds.maxY, width: 0, height: 0)
  let popoverPresentationController = optionsViewController.setUpPopoverPresentation(
   sourceView: senderView, sourceRect: sourceRect)

  popoverPresentationController.permittedArrowDirections = .up
  settingsContactViewController.present(optionsViewController, animated: true, completion: nil)
// sourceView passed in is a UIButton
func setPopoverPresentationInfo(sourceView: UIView, sourceRect: CGRect) {
  let popoverPresentationController =
   setUpPopoverPresentation(sourceView: sourceView, sourceRect: sourceRect)
  popoverPresentationController.permittedArrowDirections = UIPopoverArrowDirection(rawValue: 0)
  popoverPresentationController.delegate = self
  popoverPresentationController.popoverBackgroundViewClass = KeyboardPopoverBackgroundView.self
  view.setNeedsLayout()
 }

Thanks for the more readable code. Does the crash occur in all cases ?

I do not see where setPopoverPresentationInfo() is called. But setUpPopoverPresentation() seems to be called instead. In some cases you set a sourceRect with zero size. Why ?

As you present small chunks of code, it is very hard to see if something is missing or wrong. For instance, in this code

  let popoverPresentationController = dropdownTable.setUpPopoverPresentation(
   sourceView: sourceView, sourceRect: sourceView.bounds.inset(.init(all: -5)))

what are sourceView and sourceRect ?

(reply to @Claude31 - not an answer but submitted as answer so code blocks are styled properly)

The crash does not happen in all cases the code shared is executed. I’m not sure what the cases are that cause this crash because we cannot reproduce it. Based on logs, it seems like a lot of the crashes are happening with this code

  @IBOutlet weak var borderLabel: UILabel! //from scene in storyboard
...
   let anchorX = borderLabel.frame.width / 2
   let anchorY = borderLabel.frame.height
   let popoverPresentationController = tableViewController.setUpPopoverPresentation(
    sourceView: borderLabel, sourceRect: CGRect(x: anchorX, y: anchorY, width: 0, height: 0))
    
   popoverPresentationController.permittedArrowDirections = .up
   present(tableViewController, animated: true, completion: nil)

I did not show where setPopoverPresentationInfo() is called. I just included the function definition because it calls setUpPopoverPresentation(), and I shared all the places where setUpPopoverPresentation() is called. I think we use sourceRect with zero size as a default value. We don't get a crash every time we use a zero size because some places always use a zero size, and we can’t reproduce the crash in those places.

What are the implications of setting a sourceRect with zero size? I don’t see much explaining how the size of sourceRect is used in https://developer.apple.com/documentation/uikit/uipopoverpresentationcontroller/1622324-sourcerect. Could you explain exactly how sourceRect is used? How does the size of the sourceRect affect the popover or sourceView? What is the difference between using the sourceView.bounds and using zero (or any other value) for the sourceRect size?

Unfortunately, I can’t share our entire project, and these methods are called in different places. I know it may be difficult to see what all values are in the code snippets, but I’m hoping the issue may be apparent from what I shared. In the code you referenced:

  let popoverPresentationController = dropdownTable.setUpPopoverPresentation(
   sourceView: sourceView, sourceRect: sourceView.bounds.inset(.init(all: -5)))

sourceRect is the second parameter name for setUpPopoverPresentation which is given sourceView.bounds.inset(.init(all: -5)). In the same code snippet, sourceView is another (the first) parameter name, which is passed sourceView as the parameter. The passed in sourceView is a UIButton of custom type with a normal attributedTitle set, sometimes a normal image set, sizeToFit() called on it, and other variables set for it as well. This (setUpPopoverPresentation) is called with the button as the sourceView parameter when a user taps on said button. Please see my original question for the setUpPopoverPresentation definition.

If I can provide more information that could help (besides our entire project :) ), please let me know! Thank you!

Maybe sourceRect of zero size causes the problem:

   let popoverPresentationController = tableViewController.setUpPopoverPresentation( sourceView: borderLabel, sourceRect: CGRect(x: anchorX, y: anchorY, width: 0, height: 0))

Try and give non null width and height.

I was trying to figure out flush and run and saw this , it appears that it is not cut n dry .

Fatal Exception UIPopoverPresentationController that sourceView is nil after setting sourceView
 
 
Q