UIPopOver and orientation change
-
26-09-2019 - |
Question
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!
Solution 2
The only solution I've found so far is just closing the popover when the device is rotated/
OTHER TIPS
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.
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];
}
}
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.
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
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.
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];
}
}
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()
}
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