I am looking for an effective way to re-position a popover using the new uipopoverpresentationcontroller. I have succesfully presented the popover, and now I want to move it
I'm posting this because I don't have enough points to vote or comment. :) @turbs's answer worked for me perfectly. It should be the accepted answer.
Setting *rect to the rect you need in the delegate method:
(void)popoverPresentationController:(UIPopoverPresentationController *)popoverPresentationController
willRepositionPopoverToRect:(inout CGRect *)rect
inView:(inout UIView **)view
Here is an example for how to recenter the popover:
- (void)popoverPresentationController:(UIPopoverPresentationController *)popoverPresentationController willRepositionPopoverToRect:(inout CGRect *)rect inView:(inout UIView **)view {
*rect = CGRectMake((CGRectGetWidth((*view).bounds)-2)*0.5f,(CGRectGetHeight((*view).bounds)-2)*0.5f, 2, 2);
I have also used this method to ensure that the popover moved to the correct location after moving by setting the *rect and the *view to the original sourceRect and sourceView.
As an additional note, I don't believe that this method is called when the popover's source is set using a bar button item.
I used the same method as mentioned in another answer by @Rowan_Jones, however I didn't want the popover's size to actually change. Even by fractions of a point. I realized that you can set the preferredContentSize
multiple times back to back, but visually it's size will only change to match the last value.
[vc.popoverPresentationController setSourceRect:newSourceRect];
CGSize finalDesiredSize = CGSizeMake(320, 480);
CGSize tempSize = CGSizeMake(finalDesiredSize.width, finalDesiredSize.height + 1);
[vc setPreferredContentSize:tempSize];
[vc setPreferredContentSize:finalDesiredSize];
So even if finalDesiredSize
is the same as your initial preferredContentSize
this will cause the popover to be updated, even though it's size doesn't actually change.
I had luck using containerView?.setNeedsLayout()
and containerView?.layoutIfNeeded()
after changing the sourceRect
of the popoverPresentationController
, like so:
func movePopoverTo(_ newRect: CGRect) {
let popover = self.presentedViewController as? MyPopoverViewController {
popover.popoverPresentationController?.sourceRect = newRect
popover.popoverPresentationController?.containerView?.setNeedsLayout()
popover.popoverPresentationController?.containerView?.layoutIfNeeded()
}
}
And even to have a popover follow a tableView cell without having to change anything:
class MyTableViewController: UITableViewController {
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "MyPopoverSegue" {
guard let controller = segue.destination as? MyPopoverViewController else { fatalError("Expected destination controller to be a 'MyPopoverViewController'!") }
guard let popoverPresentationController = controller.popoverPresentationController else { fatalError("No popoverPresentationController!") }
guard let rowIndexPath = sender as? IndexPath else { fatalError("Expected sender to be an 'IndexPath'!") }
guard myData.count > rowIndexPath.row else { fatalError("Index (\(rowIndexPath.row)) Out Of Bounds for array (count: \(myData.count))!") }
if self.presentedViewController is MyPopoverViewController {
self.presentedViewController?.dismiss(animated: false)
}
popoverPresentationController.sourceView = self.tableView
popoverPresentationController.sourceRect = self.tableView.rectForRow(at: rowIndexPath)
popoverPresentationController.passthroughViews = [self.tableView]
controller.configure(myData[rowIndexPath.row])
}
super.prepare(for: segue, sender: sender)
}
}
// MARK: - UIScrollViewDelegate
extension MyTableViewController {
override func scrollViewDidScroll(_ scrollView: UIScrollView) {
if let popover = self.presentedViewController as? MyPopoverViewController {
popover.popoverPresentationController?.containerView?.setNeedsLayout()
popover.popoverPresentationController?.containerView?.layoutIfNeeded()
}
}
}
iOS 12.3
[vc.popoverPresentationController setSourceRect:newSourceRect]; [vc.popoverPresentationController.containerView setNeedsLayout];
Unfortunately this hacky workaround is the only solution I've found:
[vc.popoverPresentationController setSourceRect:newSourceRect];
[vc setPreferredContentSize:CGRectInset(vc.view.frame, -0.01, 0.0).size];
This temporarily changes the content size of the presented view, causing the popover and arrow to be repositioned. The temporary change in size is not visible.
It seems this is a problem Apple need to fix - changing the sourceView
or sourceRect
properties of UIPopoverPresentationController
does nothing when it's already presenting a popover (without this workaround).
Hope this works for you too!