How to get iOS popover view to position correctly within the bubble

67 Views Asked by At

I've written code to present a popover from a UICollectionView cell if the cell is selected but is unavailable for whatever reason. It works fine, except the content is never correctly positioned within the popup bubble. Note from the two examples shown, that the content is always shifted toward the caret by about half the thickness of the caret.

enter image description here enter image description here

It's clear that the size is being computed properly, but it just seems like the popover controller is simply calculating the position of the popupView incorrectly. Since the direction of shift is out of my control, I can't just shift everything over by a few pixels as a workaround.

The storyboard simply has a popupView with constraints to center it horizontally and vertically within the scene view:

enter image description here

And here's my code... Note I've turned on the border around the view just to accentuate the shift.

// Inside collectionView:didSelectItemAtIndexPath:

    UIStoryboard* mainStoryboard = [UIStoryboard storyboardWithName:@"Main" bundle:nil];
    MenuUnavailableViewController* muc = [mainStoryboard instantiateViewControllerWithIdentifier:@"MenuOptionUnavailable"];
    muc.modalPresentationStyle = UIModalPresentationPopover;

    [self presentViewController:muc animated:NO completion:nil];

    MenuCollectionViewCell* menuCell = (MenuCollectionViewCell*) [menuCollection cellForItemAtIndexPath:indexPath];
    muc.popoverPresentationController.sourceView = menuCollection;
    muc.popoverPresentationController.sourceRect = menuCell.frame;
    muc.popoverPresentationController.permittedArrowDirections = UIPopoverArrowDirectionAny;
@implementation MenuUnavailableViewController

- (void) viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:animated];
   
    [self.view layoutSubviews];
    self.preferredContentSize = self.popupView.bounds.size;
    self.popupView.layer.borderColor = [UIColor blackColor].CGColor;
    self.popupView.layer.borderWidth = 2;
    self.popupView.layer.cornerRadius = 12;
}

I've been doing popovers successfully using this formula for quite a while, and then one day some iOS or Xcode update silently broke the positioning (not sure, but I think it was around iOS 13 or 14). Anyone have an idea what I'm doing wrong? I've been looking for a complete example implementing popovers under a recent iOS version, but can't find one anywhere.

1

There are 1 best solutions below

0
DonMag On

Not sure what might have changed, or when... and, maybe you were "getting lucky" with your layouts so you didn't notice it... but...

We want to set the .preferredContentSize based on the size of the controller's view - not your popupView subview.

Also, we want to make sure the content is constrained to the safe-area.

So, if I setup a MenuUnavailableViewController like this:

enter image description here

With "Unavailable" and "Reason:" labels Content Hugging and Compression Resistance Priorities all set to Required ...

  • Unavailable Lbl label will determine the Width
  • "Reason Lbl` label will determine the Height

In the calling controller, set string properties for the labels.

Then, MenuUnavailableViewController class looks like this:

@implementation MenuUnavailableViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    self.unavailableLbl.text = _unavailableString;
    self.reasonLbl.text = _reasonString;
    
    self.preferredContentSize = [self.view systemLayoutSizeFittingSize:UILayoutFittingCompressedSize];
}

@end

No need to do anything in viewWillAppear.

Here's how it looks (cycling through 3 sets of strings):

enter image description here

enter image description here

enter image description here

and if we clear the colors and border:

enter image description here

enter image description here

enter image description here

and, just to show it works for different "arrow" directions:

enter image description here

enter image description here

enter image description here

enter image description here