10

In my app I have 3 UIPopOvers. They appear when user taps buttons in the toolbar. I need to make the popovers appear in the correct place when user rotates iPad if the popover is already opened (like the -willAnimateRotationToInterfaceOrientation:).

How can I do it?

Thanks in advance!

Knodel
  • 4,359
  • 8
  • 42
  • 66

9 Answers9

11

In iOS 7.0 and later, it can be done by implementing following method available in UIPopoverControllerDelegate:

(void)popoverController:(UIPopoverController *)popoverController willRepositionPopoverToRect:(inout CGRect *)rect inView:(inout UIView **)view

For popovers that were presented using the presentPopoverFromRect method, the popover controller calls this method when the interface orientation changes.

Nitin Gupta
  • 189
  • 2
  • 5
6

The only solution I've found so far is just closing the popover when the device is rotated/

Knodel
  • 4,359
  • 8
  • 42
  • 66
  • Sadly, i can't get anything to trigger my view controller's willRotate method, where I would normally dismiss the popover. I would have figured that would get called no matter what. – Greg Combs Jul 04 '10 at 21:27
  • I also think about this solution I can't get it done. :( – Alyssa Reyes Oct 02 '15 at 12:10
  • This would work in some instances, but for me this is not a viable solution due to the fact that dismissing and presenting the view releases the view and viewController from memory. This makes it impossible to maintain the state during orientation change. For example, let's say the user selects some stuff in the popover, then rotates the screen. They would have to re-do any work they did in the popover again. – Zach Feb 04 '20 at 17:09
4

Here is a code fragment from one of my projects. Basically, if the popover is showing, you present the popover again in the method didRotateFromInterfaceOrientation:, which is sent to the view controller after the user interface rotation has taken place. (The willRotate... and willAnimateRotation... methods are called before the rotation has taken place, so it is the wrong place for the presentPopover... method call.)

- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation
{
  // if the popover is showing, adjust its position after the re-orientation by presenting it again:
  if (self.myPopoverController != nil)  // if the popover is showing (replace with your own test if you wish)
  {
    [self.myPopoverController presentPopoverFromRect:attachmentRect
                                              inView:myView
                            permittedArrowDirections:UIPopoverArrowDirectionUp
                                            animated:YES];
  }    
}

In the above, self.myPopoverController is a property of my view controller where I store a reference to the popover when it is created. When I dismiss and discard the popover under normal circumstances, I take care to set this property to nil, so I can check it for 'non-nil'ness to decide whether or not the popover is being shown.

Note, however, that you don't need to dismiss the popover before the rotation takes place. Just present the same popover again. (This is where keeping a reference to the popover comes in handy.)

In your case, where the popover emanates from a toolbar button, you would use something like the following instead:

- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation
{
  // if the popover is showing, adjust its position after the re-orientation by presenting it again:
  if (self.myPopoverController != nil)  // if the popover is showing (replace with your own test if you wish)
  {
    [self.myPopoverController presentPopoverFromBarButtonItem:barButtonItem
                                     permittedArrowDirections:UIPopoverArrowDirectionAny
                                                     animated:YES];
  }    
}
inwit
  • 935
  • 6
  • 11
3

If you simply use the presentPopoverFromBarButtonItem method to present your popover then the popover will automatically move to the correct position for the new button position when the device is rotated.

sdsykes
  • 1,256
  • 12
  • 15
2

I've run into this same issue a couple of times. I typically just make a method to show the popover centered like this:

- (void) showPopoverForSize:(CGSize) size center:(CGPoint) center {
   CGFloat width = size.width;
    CGFloat height = size.height;
    CGFloat x = center.x - width / 2;
    CGFloat y = center.y - height / 2;
    CGRect frame = CGRectMake(x, y, width, height);
    popover.popoverContentSize = frame.size;

    [popover presentPopoverFromRect:frame inView:self.view permittedArrowDirections:0 animated:YES];
}

Then on didRotate I do:

- (void) didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation {
    [super didRotateFromInterfaceOrientation:fromInterfaceOrientation];
    if (popover.isPopoverVisible)
        [self showPopoverForSize:popover.popoverContentSize center:self.view.center];
}

This will put the popover in the center for any orientation.

James Jones
  • 1,486
  • 1
  • 12
  • 22
2

Are you calling presentPopoverFromBarButtonItem or FromRect? Are you making any changes to the BarButtonItem on rotate?

Apple's documentation specifically states you need to manage position on rotation for FromRect or if you modify the bar button item. See fourth paragraph in http://developer.apple.com/library/ios/#documentation/uikit/reference/UIPopoverController_class/Reference/Reference.html

Dan Bennett
  • 1,450
  • 1
  • 15
  • 17
1

At the beginning of the change of orientation dismiss the popover, and after the change of orientation is completed it again and it changes present its position on the screen:

- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration {
            [_popover dismissPopoverAnimated:YES];
        }
    }

    - (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation
    {
        if (_popover) {
            [_popover presentPopoverFromRect:frameRect
                                                      inView:self.view
                                    permittedArrowDirections:UIPopoverArrowDirectionUp
                                                    animated:YES];
        }    
    }
0

Performing popover function:

func presentPopover() {
    self.popoverFlag = true
    //Presenting PopOver code goes here
    // ...
}

Dismissing presented popover on changing orientation:

override func viewWillTransitionToSize(size: CGSize, withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator) {

        if self.isKindOfClass(ViewController) && self.popoverFlag{
            guard self.presentedViewController != nil else { return }
            dispatch_async(dispatch_get_main_queue()) {
                self.presentedViewController!.dismissViewControllerAnimated(true, completion: nil)
            }
        }
    }

Presenting popover again:

func popoverPresentationController(popoverPresentationController: UIPopoverPresentationController, willRepositionPopoverToRect rect: UnsafeMutablePointer<CGRect>, inView view: AutoreleasingUnsafeMutablePointer<UIView?>) {
    self.presentPopover()
}
Vahid
  • 3,352
  • 2
  • 34
  • 42
0

You should use UIPopoverPresentationControllerDelegate method:

func popoverPresentationController(_ popoverPresentationController: UIPopoverPresentationController, willRepositionPopoverTo rect: UnsafeMutablePointer<CGRect>, in view: AutoreleasingUnsafeMutablePointer<UIView>)

and update the rect value.

See @Hugo Alonso's answer here

Bionicle
  • 85
  • 9