This is the community forum for my apps Pythonista and Editorial.
For individual support questions, you can also send an email. If you have a very short question or just want to say hello — I'm @olemoritz on Twitter.
Close and re-present a ui.View?
I have a small "options" UI which I want to display as a popover window when the user taps a certain place in the main interface.
It's working the first time I tap, the popover shows up in the place specified by popover_location in the call to view.present(). But after dismissing it the first time, trying to show it again produces this traceback:
Traceback (most recent call last): File "/private/var/mobile/Library/Mobile Documents.../Gestures.py", line 443, in _general_action action(data) File "/private/var/mobile/Library/Mobile Documents.../controls.py", line 1578, in _handleSingleTap picker.present(style="popover", animated=False, popover_location=(x,y)) ValueError: View is already being presented or animation is in progress
I removed everything from the ui.View subclass which defines the popover so it is an empty shell:
class Popover(ui.View): def __init__(self, func, *args, **kws): views.PyUiView.__init__(self, *args, **kws) print "popover initialized" def will_close(self): print "popover closing"
and I still get the traceback the second time I try to call present() from the code which handles displaying the view, or any time thereafter.
Is calling present() on the same view more than once not allowed (I don't see anything indicating that in the docs)? I'm not explicitly calling view.close(), but I thought that once the popover was dismissed, that was automatic, which is why I do see will_close() being called. Even if I attach a callback which is called from will_close() and tries calling view.close() on the popover from the outside, it still doesn't prevent the traceback.
Here's a bit of simple code which emulates what I'm doing in my more complicated code:
import ui class Popover(ui.View): def __init__(self, *args, **kws): ui.View.__init__(self, *args, **kws) self.label = ui.Label() self.label.text = "Popover" self.add_subview(self.label) def layout(self): self.label.x = 0 self.label.y = 0 self.label.width = self.width self.label.height = self.height def will_close(self): print "popover closing" class MyView(ui.View): def __init__(self, *args, **kws): ui.View.__init__(self, *args, **kws) self.popover = None self.button = ui.Button() self.button.title = "Show Popover" self.button.action = self.showPopover self.add_subview(self.button) def layout(self): self.button.x = 0 self.button.y = 0 self.button.width = self.width self.button.height = self.height def showPopover(self, sender): print "show popover" if self.popover is None: self.popover = Popover() self.popover.present(style="popover", popover_location=self.button.center) #self.popover.close() def run(): v = MyView() v.present('full_screen') if __name__ == '__main__': run()
First time I hit the "Show Popover" button, it shows. Second time, I get the traceback.
JonB last edited by JonB
I think there may be a bug in popover, I remember something like that..
An alternative is to use a "shield view", which is a semi transparent view added as a subviews of the root view that covers the entire screen, then your settings is a subview of the shield. See for example
This concept can be extended to detect touches on the shield, to dismiss it when user taps outside the settings view.
Another option is to use my overlay class, which is somewhat more flexible than popover.https://github.com/jsbain/viewbrowser/blob/master/overlay.py
JonB last edited by
It is possible that calling close on_main_thread might do the job, after some delay.
@JonB well that bug report definitely looks like it's exactly what I'm seeing. Unfortunately the comment thread isn't making much sense to me:
In case someone else does stumble over that odd behaviour and this comment: The trick is to open the new instance first and then close the old one when dealing with an instance of a popover view. It is a bit counterintuitive, but it works for me. I just tested this in 311014 and it works if you wait reasonable amount of time. Closing it in favour of #450 and #392. When these two issues will be done, you will know when exactly you can present it again.
How would one "open the new instance first, and then close the old one"? I don't really have control over when the popover is dismissed, and I don't want a new one to show until some time later, when the user taps the right place again. And the second comment...what is a "reasonable amount of time"? I've waited 30 seconds to a minute after closing the popover, and it still tracebacks.
Calling close on_main_thread seems like a good bet...do you mean that I wait some small amount of time after the view is closed, and then call close again on_main_thread?
mikael last edited by mikael
I wonder whether you could just hide the popover and then show it again while repositioning the anchor: https://developer.apple.com/documentation/uikit/uipopoverpresentationcontroller/1622324-sourcerect?language=objc
cvp last edited by cvp
@shinyformica try this, it is the way omz wrote dialog: using wait_modal and resetting the view just after.
import ui from objc_util import * class Popover(ui.View): def __init__(self, *args, **kws): ui.View.__init__(self, *args, **kws) self.width = 200 self.height = 50 self.label = ui.Label() self.label.text = "test" self.add_subview(self.label) self.label.x = 0 self.label.y = 0 self.label.width = self.width self.label.height = self.height def will_close(self): print("popover closing") class MyView(ui.View): def __init__(self, *args, **kws): ui.View.__init__(self, *args, **kws) self.popover = None self.button = ui.ButtonItem() self.button.title = "Show Popover" self.button.action = self.showPopover self.right_button_items = (self.button,) def showPopover(self, sender): print("show popover") if self.popover is None: self.popover = Popover() self.popover.present(style="popover", popover_location=(self.width-100,50), hide_title_bar=True) self.popover.wait_modal() self.popover = None def run(): v = MyView() v.present('full_screen') if __name__ == '__main__': run()```