First, here's a newer version with some improvements which make it work better on iOS 13, and adds a callback which is called when the popover is presented:
def adaptivePresentationStyleForPresentationController_(_self, _cmd,
controller):
#### force the popover presentation style to stay as-is
return -1 # UIModalPresentationNone
def popoverDelegateCompletion(delegate):
if delegate._popview is not None and delegate._bgcolor is not None:
delegate._popview.background_color = delegate._bgcolor
if delegate._completion is not None:
delegate._completion()
delegate._popview = None
delegate._presented_block = None
def popoverPresentationControllerDidDismissPopover_(_self, _cmd, controller):
import objc_util
delegate = objc_util.ObjCInstance(_self)
popoverDelegateCompletion(delegate)
PopoverPresentationDelegate = objc_util.create_objc_class(
"PopoverPresentationDelegate",
methods=[adaptivePresentationStyleForPresentationController_,
popoverPresentationControllerDidDismissPopover_],
protocols=["UIPopoverPresentationControllerDelegate"])
def popover(sourceview, popview, presented=None, dismissed=None):
"""Create and display a popover modal view containing the given
content view, with a little arrow pointing at the given source view.
'presented' is called when the popover is first presented.
'dismissed' will be called when the popover is dismissed."""
def _closer(vc, delegate):
#### return a function object which will dismiss the popover
#### and restore the popview bg color, as well as call
#### any completion function
def _f():
vc.dismissViewControllerAnimated_completion_(True,None)
popoverDelegateCompletion(delegate)
return _f
#### find the view controller which is presenting the source view
#### this is what the popover arrow will point at
sourcevc = ViewHierarchy.getUIViewController(sourceview)
#### create a new modal view controller and set its presentation
#### style to the popover style, and size it to the content
UIViewController = objc_util.ObjCClass("UIViewController")
vc = UIViewController.new().autorelease()
vc.modalPresentationStyle = 7 # UIModalPresentationPopover
vc.preferredContentSize = objc_util.CGSize(popview.width, popview.height)
#### As of iOS 13+ popover presentation bounds include the little arrow
#### as part of the content area.
#### Since our popup content is designed to be displayed only in the
#### rectangular portion of the popover view, we constrain it to
#### the "safe area", which excludes the arrow.
safeView = objc_util.UIView.new().autorelease()
safeView.addSubview_(popview.objc_instance)
safeView.backgroundColor = objc_util.UIColor.clearColor()
vc.view = safeView
popview.objc_instance.translatesAutoresizingMaskIntoConstraints = False
guide = safeView.safeAreaLayoutGuide()
anchor = popview.objc_instance.leadingAnchor()
anchor.constraintEqualToAnchor_(guide.leadingAnchor()).active = True
anchor = popview.objc_instance.trailingAnchor()
anchor.constraintEqualToAnchor_(guide.trailingAnchor()).active = True
anchor = popview.objc_instance.topAnchor()
anchor.constraintEqualToAnchor_(guide.topAnchor()).active = True
anchor = popview.objc_instance.bottomAnchor()
anchor.constraintEqualToAnchor_(guide.bottomAnchor()).active = True
#### retrieve the popover presentation controller for the new modal
#### view controller, bail if that fails for some reason
popover = vc.popoverPresentationController()
if popover is None: return None
#### create and assign the popover presentation delegate
delegate = PopoverPresentationDelegate.new().autorelease()
delegate._completion = dismissed
delegate._popview = popview
delegate._bgcolor = None
popover.delegate = delegate
#### if the popview has a translucent background, make the
#### presenting view translucent instead, and the popview transparent,
#### save the original color to restore on dismissal.
color = popview.background_color
if color[3] < 1.0:
popview.background_color = "clear"
delegate._bgcolor = color
popover.backgroundColor = objc_util.UIColor.colorWithRed_green_blue_alpha_(*color)
popover.sourceView = sourceview.objc_instance
popover.sourceRect = objc_util.CGRect(objc_util.CGPoint(0,0),objc_util.CGSize(
sourceview.width, sourceview.height))
#### if a presentation function is provided, it is converted to an
#### objective-c block to be called when the popover is displayed
objc_f = None
if presented is not None:
objc_f = objc_util.ObjCBlock(presented, None, [objc_util.c_void_p])
delegate._presented_block = objc_f
#### present the view controller containing the popover content
#### via the popover presentation controller, using the
#### popover delegate we created, and return the close
#### function to allow caller to close the popover from code
sourcevc.presentViewController_animated_completion_(vc, True, objc_f)
return _closer(vc, delegate)
it's simple enough to use. When you wish to display a view as a popover, just call like so:
def popover(sender):
popview = ui.View()
popview.width = 200
popview.height = 100
popview.add_subview(ui.Label(text="Hello popover!", flex="WH")
popover(sender, popview)
v = ui.View()
v.width = 300
v.height = 300
b = ui.Button()
b.title = "Show Popover"
b.action = showPopover
b.frame = (0,0,100,40)
b.center = v.center
v.add_subview(b)
v.present("sheet")