35

I'm using a storyboard segue that presents a view controller as popover. The seque has a custom UIView as its anchor. On pre-iOS9 the popover would correctly point to the centre-bottom of the custom UIView (presented below the UIView). On iOS9 it points to the top-left corner of the UIView.

I did try to trace all selector calls to the custom UIView to find out if there is anything I may need to implement in my custom UIView to provide the 'hotspot' for the popover but couldn't find anything

Any ideas..? Thanks

Thanks to @Igor Camilo his reply - in case it's useful to some, this is how I fixed this in my code:

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {

     UIPopoverPresentationController* possiblePopOver = segue.destinationViewController.popoverPresentationController;
     if (possiblePopOver != nil) {
         //
         // iOS9 -- ensure correct sourceRect
         //
         possiblePopOver.sourceRect = possiblePopOver.sourceView.bounds;
     }
    ...
 }

Example: 'Short' button triggers a popover, the popover points to the top-left corner of 'Sort' control

Resulting popover

Segue settings

Nikos
  • 787
  • 1
  • 8
  • 19
  • You can't use `UIPopoverArrowDirection` constant? Everything else is deprecated. – noobsmcgoobs Sep 19 '15 at 09:59
  • Hi, the problem is not the direction -- that's defined correctly in the storyboard segue. The issue is the where the arrow points to. – Nikos Sep 19 '15 at 10:49
  • So it's pointing to the origin and there's no way to adjust? Does the arrow go over the UIView or is it off of it? – noobsmcgoobs Sep 19 '15 at 10:53
  • Just added an example gif in my question above - you can see how the popover points to the top-left corner of the Sort button. – Nikos Sep 19 '15 at 11:08
  • Are you using UIPopoverPresentationController or UIPopoverController? PresentationController has a a sourceRect and sourceView properties that should allow you to adjust the anchor point. – noobsmcgoobs Sep 19 '15 at 11:40
  • I'm using a storyboard segue which presents a popover on iPad. See second image with the settings. This works just fine on iOS8. I suspect something changed in iOS9 and obtaining a sourceRect from the anchor must rely on a missing selector/property/whatever on my custom UIView (the Sort Button is a custom UIControl). – Nikos Sep 19 '15 at 12:13

9 Answers9

35

I had the exact same problem. I just resolved it by setting sourceRect in prepareForSegue:

override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
    switch segue.identifier {
    case "Popover Identifier"?:
        if #available(iOS 9.0, *) {
            segue.destinationViewController?.popoverPresentationController?.sourceRect = anchorView.bounds
        }
    default:
        break
    }
}
Igor Camilo
  • 1,200
  • 13
  • 16
  • 1
    where did you get the `anchorView` ? – Buntylm Sep 28 '15 at 07:49
  • I updated Igor Camilo's answer using Objective-C. This should answer your question, @BuntyMadan – zath Sep 28 '15 at 10:51
  • @BuntyMadan In my case, there was only one popover in my controller, so I just got the anchor view from my IBOutlets, but I believe you can grab it from `sender` or `popoverPresentationController?.sourceView`. I haven't tested it though. – Igor Camilo Sep 28 '15 at 20:00
  • 3
    I think this should be anchorView.bounds, rather than anchorView.frame. See http://stackoverflow.com/a/30067716/220287 – stephent May 03 '17 at 16:52
18

Had the same issue but my app has a multitude of pop-over so I created a centralized function for the fix (but still had to use it on every view controller that had pop overs).

// Fix for IOS 9 pop-over arrow anchor bug
// ---------------------------------------
// - IOS9 points pop-over arrows on the top left corner of the anchor view
// - It seems that the popover controller's sourceRect is not being set
//   so, if it is empty  CGRect(0,0,0,0), we simply set it to the source view's bounds
//   which produces the same result as the IOS8 behaviour.
// - This method is to be called in the prepareForSegue method override of all
//   view controllers that use a PopOver segue
//
//   example use:
//
//          override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) 
//          {
//             fixIOS9PopOverAnchor(segue)   
//          }
//      
extension UIViewController
{
   func fixIOS9PopOverAnchor(segue:UIStoryboardSegue?)
   {
      guard #available(iOS 9.0, *) else { return }          
      if let popOver = segue?.destinationViewController.popoverPresentationController,
         let anchor  = popOver.sourceView
         where popOver.sourceRect == CGRect()      
            && segue!.sourceViewController === self 
      { popOver.sourceRect = anchor.bounds }   
   }       
}
Alain T.
  • 40,517
  • 4
  • 31
  • 51
8

Here's an example of Igor Camilo's snippet in Objective-C.

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
    // If the sender is a UIView, we might have to correct the sourceRect of
    // a potential popover being presented due to an iOS 9 bug. See:
    // https://openradar.appspot.com/22095792 and http://stackoverflow.com/a/32698841/368674
    if ([sender isKindOfClass:UIView.class]) {
        // Fetch the destination view controller
        UIViewController *destinationViewController = [segue destinationViewController];

        // If there is indeed a UIPopoverPresentationController involved
        if ([destinationViewController respondsToSelector:@selector(popoverPresentationController)]) {
            // Fetch the popover presentation controller
            UIPopoverPresentationController *popoverPresentationController =
            destinationViewController.popoverPresentationController;

            // Set the correct sourceRect given the sender's bounds
            popoverPresentationController.sourceRect = ((UIView *)sender).bounds;
        }
    }
}
zath
  • 1,044
  • 8
  • 13
  • unfortunately this solution is not suitable for tvOS, because there is no class `UIPopoverPresentationController`. – Duck Nov 23 '15 at 11:42
7

Here's my solution, inside prepareForSegue:

segue.destinationViewController.popoverPresentationController?.sourceRect = CGRectMake(anchorView.frame.size.width/2, anchorView.frame.size.height, 0, 0)

This will move the pointer to the bottom middle of the anchor view

s0m3chill
  • 111
  • 7
1

I also encountered this problem but now it works when I added this to my PrepareForSegue function. Given that my Segue ID contains string Popover

if ([[segue identifier] containsString:@"Popover"]) {
    [segue destinationViewController].popoverPresentationController.sourceRect = self.navigationItem.titleView.bounds;
}
0

If you reference your popover UIViewController in your main UIViewController you can adjust the sourceRect property to offset the popover. For example, given popover popoverVC you can do something like so:

float xOffset = 10.0;
float yOffset = 5.0;
popoverVC.popoverPresentationController.sourceRect = CGMakeRect(xOffset, yOffset, 0.0, 0.0);
Chris Stillwell
  • 10,266
  • 10
  • 67
  • 77
0

Try to set width and height anchor of your source rect (UIView or UIBarButtonItem ) and set it to active .Set it when you are initialising your UIView or UIBarButtonItem.

UIBarButtonItem

 [[youruibarbuttonitem.customView.widthAnchor constraintEqualToConstant:youruibarbuttonitem.customView.bounds.size.width] setActive:YES];
 [[youruibarbuttonitem.customView.heightAnchor constraintEqualToConstant:youruibarbuttonitem.customView.bounds.size.height] setActive:YES];

UIView

[[uiview.widthAnchor constraintEqualToConstant:uiview.bounds.size.width] setActive:YES];
 [[uiview.heightAnchor constraintEqualToConstant:uiview.bounds.size.height] setActive:YES];
Tanvir Singh
  • 131
  • 1
  • 2
0

A bit of a more updated answer for Swift 3! Pardon all the casting

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        if segue.identifier == "popSegue" {
            let popoverViewController = segue.destination
            popoverViewController.popoverPresentationController?.delegate = self
            segue.destination.popoverPresentationController?.sourceRect = ((sender as? UIButton)?.bounds)!
        }
    }
Matt Bart
  • 809
  • 1
  • 7
  • 26
0

just to update to an actual example with working code for any UIView

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
    switch segue.identifier {
    case "PopoverSegueId"?:
        if #available(iOS 9.0, *) {
            segue.destination.popoverPresentationController?.sourceRect = (segue.destination.popoverPresentationController?.sourceView?.bounds)!
        }
    default:
        break
    }

}