Welcome!
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.
Traceback using Gestures module
-
Wanted to add simple swipe-gesture recognition to a custom View I was writing, so I thought I'd try out the utterly fantastic Gestures module by @mikael, which seemed like it had exactly what I needed...
...and it worked! ...the first time I run my code. The second time, and any time thereafter, until I force-quit Pythonista and restart, it gives the following traceback when detecting my gesture:
Traceback (most recent call last): File "_ctypes/callbacks.c", line 315, in 'calling callback function' File "/private/var/mobile/Library/Mobile Documents/iCloud~com~omz-software~Pythonista3/Documents.../Gestures.py", line 154, in gestureRecognizer_shouldRequireFailureOfGestureRecognizer_ return self.objc_should_require_failure(self.fail_other, gr, other_gr) File "/private/var/mobile/Library/Mobile Documents/iCloud~com~omz-software~Pythonista3/Documents.../Gestures.py", line 157, in objc_should_require_failure_default return simplify(func, gr, other_gr) File "/private/var/mobile/Library/Mobile Documents/iCloud~com~omz-software~Pythonista3/Documents/.../Gestures.py", line 133, in simplify gr_o = ObjCInstance(gr) TypeError: 'NoneType' object is not callable
My project is python2.7, and Gestures is really python3 code, though the only incompatibility I found was this import of a python3-only module which isn't even used in the Gestures code:
from types import SimpleNamespace
So I commented that line out and it worked fine...until this issue. To reiterate: I can run my code once, load the pyui, my custom View shows up, and reacts to the swipe as expected. Then I exit my script, and run it again, and the second time around it gives this traceback...it seemed like it was holding a reference to the gesture recognizer instance from one run to the next, so I tried both:
class MyView(ui.View): def did_load(self): import Gestures Gestures.Gestures(retain_global_reference=True).add_swipe(self, self._handleSwipe, direction=[Gestures.Gestures.LEFT, Gestures.Gestures.RIGHT])
and also:
class MyView(ui.View): def did_load(self): import Gestures self._gr = Gestures.Gestures(retain_global_reference=False) self._gr.add_swipe(self, self._handleSwipe, direction=[Gestures.Gestures.LEFT, Gestures.Gestures.RIGHT])
Thinking maybe it was the "retain_global_reference" which was causing this...but either way I get the same result.
Anyone ever see this before? Any ideas? -
I should mention that in my feeble attempts to debug what was going on, I put a bit of debug printout in the Gestures.py module just before where the traceback was occurring, to see what the "gr" and "other_gr" values in the simplify() function actually are each time this happens:
def simplify(func, gr, other_gr): print "in simplify:",gr,other_gr gr_o = ObjCInstance(gr) other_gr_o = ObjCInstance(other_gr)
And I noticed that "gr" (which I assume is the address of the gesture recognizer to be looked up as an ObjC instance) is always changing in value on each run, while the "other_gr" value is pretty much constant (always one of two values in my repeated tests). So this implies that the "gr" value is referencing a gesture recognizer object which is getting blown away somehow?
-
Sorry...bad form to reply repeatedly to my own post, but...I just realized that the issue here is actually not quite what I thought: the actual ObjCInstance class definition seems to be disappearing between runs of the script. As in, on the first run this tiny bit of debug printout added to the Gestures module simplify() function:
def simplify(func, gr, other_gr): print "in simplify:",ObjCInstance gr_o = ObjCInstance(gr) other_gr_o = ObjCInstance(other_gr) if (gr_o.view() != other_gr_o.view()): return False gr_name = gr_o._get_objc_classname() other_gr_name = other_gr_o._get_objc_classname() return func(gr_name, other_gr_name)
produces:
in simplify: <class 'objc_util.ObjCInstance'>
but on the very next run of the script, when it tracebacks, that same debug printout produces:
in simplify: None
how on earth is that happening? -
To clarify, is your view still on screen when you run your code again? I.e you are presenting in a panel?
A subtlety of the pythonista environment is that globals are cleared every time you run from the editor. Yet any presented UI views still live on, so any functions they reference are not etc gc'd. But, any modules or globals that those functions depend on are cleared!
The solution is to make sure that functions used as callbacks import everything they need in the def itself.
-
My view is presented fullscreen with title bar, and the script runs until the "X" is hit in the top-left, which effectively ends the run (will_close gets called, and I do some shutdown there). So on the next run, the view is not onscreen, it is created and then presented again via:
editor.present_themed(view, style='full_screen', hide_title_bar=False, hide_close_button=False)
So, to be clear, are you saying that even when the view is "closed" via the "X", it still lives as long as Pythonista is open? I can see how that might cause me trouble. But I don't understand how the ObjCInstance class itself, which is defined in objc_util would suddenly be "None"...but there is probably subtlety going on there, if the global names and modules are blown away, but the View still holds a reference to something.
I should also mention that I'm using your nice "wrapped view" method of creating a custom view which loads a pyui into itself:
def _WrapInstance(self): class _Wrapper(self.__class__): def __new__(cls): return self return _Wrapper ... def __init__(self, *args, **kws): pyui = kws.pop("pyui") _bindings = globals().copy() _bindings[self.__class__.__name__] = self._WrapInstance() _bindings["self"] = self ui.load_view(pyui, _bindings) ui.View.__init__(self, *args, **kws)
So perhaps there is a bad interaction of some kind going on there? I'll investigate more.
-
other_gr in this case is probably the pythonista app recognizer, so wouldn't change. gr would change every time you run, since globals are cleared.
The concerns I have probably are unfounded if you present full screen, since you have to close before globals get cleared. Also, the fact that ObjCInstance is still defined (you didn't get NameError) but just set to None is strange.
Do you have a minimal example that reproduces this?
-
I will try to make one, since this is non-obvious behavior. I wouldn't be surprised if it's happening somewhere in the way I manipulate things. Though I never touch any of the Objc_util or Gestures modules in any way beyond importing them.
-
What do your imports look like? Are you only installing a gesture recognizer on your custom view, or do you attach it to the pythonista main window?
You might try putting Gestures.py in site-packages, to keep it from getting cleared. Shouldn't be necessary but might help narrow things down
-
Only installed on my custom view. In the did_load() method, since I don't need it anywhere else except on this control.
Let me see if a simple example still does it, if it doesn't then I'll know it's in my code somewhere. -
@JonB, here's a very simple example which exhibits the behavior. Of note: I'm running in python2.7, so I have a local copy of the Gestures.py module which has this import line commented out:
#from types import SimpleNamespace
since that's a python3 module. Other than that, and the debug printout in the simplify() function:
def simplify(func, gr, other_gr): print "in simplify:",ObjCInstance,gr,other_gr gr_o = ObjCInstance(gr) other_gr_o = ObjCInstance(other_gr) if (gr_o.view() != other_gr_o.view()): return False gr_name = gr_o._get_objc_classname() other_gr_name = other_gr_o._get_objc_classname() return func(gr_name, other_gr_name)
there's no changes to the module at all, but it is in the same directory as the following code, so it can be imported locally:
import ui class MyView(ui.View): def __init__(self, *args, **kws): ui.View.__init__(self, *args, **kws) self._color = (1,0,0,1) import Gestures self._gr = Gestures.Gestures(retain_global_reference=False) self._gr.add_swipe(self, self._handleSwipe, direction=[Gestures.Gestures.LEFT, Gestures.Gestures.RIGHT]) def draw(self): ui.set_color(self._color) ui.fill_rect(0, 0, self.width, self.height) def _handleSwipe(self, data): print "swiped!" print data self._color = (0,1,0,1) self.set_needs_display() def run(): v = MyView() v.present('full_screen') if __name__ == '__main__': run()
To reproduce, I just run once, swipe to have it do its thing, hit the "X" to exit the script, then immediately run again and try to swipe...the swipe happens, but I get this traceback:
Traceback (most recent call last): File "_ctypes/callbacks.c", line 315, in 'calling callback function' File "/private/var/mobile/Library/Mobile Documents/iCloud~com~omz-software~Pythonista3/Documents/Gestures.py", line 155, in gestureRecognizer_shouldRequireFailureOfGestureRecognizer_ return self.objc_should_require_failure(self.fail_other, gr, other_gr) File "/private/var/mobile/Library/Mobile Documents/iCloud~com~omz-software~Pythonista3/Documents/Gestures.py", line 158, in objc_should_require_failure_default return simplify(func, gr, other_gr) File "/private/var/mobile/Library/Mobile Documents/iCloud~com~omz-software~Pythonista3/Documents/Gestures.py", line 134, in simplify gr_o = ObjCInstance(gr) TypeError: 'NoneType' object is not callable
-
@JonB So, modifying the Gestures.py module ever so slightly allowed it to work without a traceback, based off what you said about having to import modules in the place where they're used, I just went ahead and changed that "simplify()" function to:
def simplify(func, gr, other_gr): import objc_util print "in simplify..." print "objc_util:",objc_util print "ObjCInstance:",objc_util.ObjCInstance print "gr,gr_other:",gr,other_gr gr_o = objc_util.ObjCInstance(gr) other_gr_o = objc_util.ObjCInstance(other_gr) if (gr_o.view() != other_gr_o.view()): return False gr_name = gr_o._get_objc_classname() other_gr_name = other_gr_o._get_objc_classname() return func(gr_name, other_gr_name)
and voila, it suddenly works fine. I'm still not sure how exactly the ObjCInstance is getting set to None, and by whom, but by importing objc_util and using the definition contained within, I'm ok. Must be some consequence of callbacks from objc and the pythonista framework's way of loading and unloading objects when scripts are run.
That example above still stands as a way to see this oddness in action.
-
@shinyformica, for reference, I tested your code on both 2.7 and 3.6, restarting several times, and unfortunately I cannot reproduce the issue. This is on iPhone X.
Also, I am curious why you set
retain_global_reference
toFalse
? -
@mikael that is really odd...on my iPad Air it happens every time I run/end/run-again. On my iPhone 7, it does not.
Setting "retain_global_reference" to True or False appears not to make a difference, I guess since I'm holding a reference to the Gestures instance in my View instance.I would love to know if @JonB or anyone else can reproduce this? Seems strange it would only be my iPad that sees the issue.
Regardless, simply importing objc_util inside simplify() fixed it for me, so I'm ok with a tiny change like that. The module is fantastic @mikael.
-
@mikael @JonB, or anyone else, instead of starting a new thread...thought I'd add this here, since it might be related to the previous issue.
Another Gestures thing I've just had come up: I have multiple custom Views which each handle gestures. This has been working fine, up till now, when I needed one of these views to allow simultaneously handling two gestures. It seems like the first View to create a Gestures instance is the one that has
recognize_simultaneously, fail, and fail_other
functions called, no matter if each view independently creates its own Gestures instance.Here's a moderately simple example:
import ui import Gestures class GestureViewA(ui.View): def __init__(self, *args, **kws): ui.View.__init__(self, *args, **kws) self._color = (1,0,0,1) self.gestures = Gestures_test.Gestures() self.gestures.recognize_simultaneously = self._recognizeSimultaneously self.gestures.add_swipe(self, self._handleSwipe, direction=[Gestures_test.Gestures.LEFT, Gestures_test.Gestures.RIGHT]) def draw(self): ui.set_color(self._color) ui.fill_rect(0, 0, self.width, self.height) def _recognizeSimultaneously(self, gr, other_gr): print "view A: recognize simultaneously:",gr,other_gr return False def _handleSwipe(self, data): print "swipe view A!" self._color = (0,1,0,1) self.set_needs_display() class GestureViewB(ui.View): def __init__(self, *args, **kws): ui.View.__init__(self, *args, **kws) self._color = (0,0,1,1) self.gestures = Gestures_test.Gestures() self.gestures.recognize_simultaneously = self._recognizeSimultaneously self.gestures.add_swipe(self, self._handleSwipe, direction=[Gestures_test.Gestures.LEFT, Gestures_test.Gestures.RIGHT]) self.gestures.add_pan(self, self._handlePan) def draw(self): ui.set_color(self._color) ui.fill_rect(0, 0, self.width, self.height) def _recognizeSimultaneously(self, gr, other_gr): print "view B: recognize simultaneously:",gr,other_gr return False def _handleSwipe(self, data): print "swipe view B!" self._color = (1,0,1,1) self.set_needs_display() def _handlePan(self, data): print "pan view B!" self._color = (1,1,1,1) self.set_needs_display() mainView = ui.View() viewA = GestureViewA() viewA.x = 0 viewA.y = 0 viewA.width = mainView.width viewA.height = 100 viewA.flex = "WHB" mainView.add_subview(viewA) viewB = GestureViewB() viewB.x = 0 viewB.y = mainView.height/2 viewB.width = mainView.width viewB.height = 100 viewB.flex = "WHT" mainView.add_subview(viewB) mainView.present('full_screen')
What I think should happen in the above code:
- When viewA is swiped its _handleSwipe() is called, and there's no need for its _recognizeSimultaneously() to be called, since there's only one gesture registered.
- When viewB is either swiped or panned, I would expect one of its _handleSwipe() or _handlePan() to be called, and also to see its _recognizeSimultaneously() called, since there's two gestures registered.
What I see when I run it is that viewA is having its _recognizeSimultaneously called for viewB's gestures. Which doesn't seem right.
-
Also, I've tried having GestureViewB have a function replace:
Gestures.objc_should_recognize_simultaneously
with its own function to see if it was something happening in simplify(), but same result, only the method on GestureViewA gets called. This makes me believe that it's actually when the ObjC class is created that the first object to do so "wins" and has its methods registered:
try: PythonistaGestureDelegate = ObjCClass('PythonistaGestureDelegate') except: PythonistaGestureDelegate = create_objc_class('PythonistaGestureDelegate', superclass=NSObject, methods=[ #gestureRecognizer_shouldReceiveTouch_, gestureRecognizer_shouldRecognizeSimultaneouslyWithGestureRecognizer_, gestureRecognizer_shouldRequireFailureOfGestureRecognizer_, gestureRecognizer_shouldBeRequiredToFailByGestureRecognizer_], classmethods=[], protocols=['UIGestureRecognizerDelegate'], debug=True) self._delegate = PythonistaGestureDelegate.new()
so even though I'm getting a new instance of the PythonistaGestureDelegate objective-C object, the underlying functions registered with the original definition are pointing at my new instance's methods, or are being called with the first instance as "self", or something to that effect.
-
And...doing it again...(I should really test my theories before posting)...if I simply force a new ObjC class definition to be created for the gesture delegate for each different view class, by modifying Gestures.py slightly so it takes a "name" in its init():
try: PythonistaGestureDelegate = ObjCClass('PythonistaGestureDelegate_{0}'.format(name)) except: PythonistaGestureDelegate = create_objc_class('PythonistaGestureDelegate_{0}'.format(name),
this works, in that it now does what I expect and calls the methods defined on the specific view class which the Gestures object was created in. But my understanding of what's happening on the Objective-C side of things is not clear here.
-
@shinyformica, I think the issue is related to the way I have used closure to access
self
in the ObjC delegate functions. Maybe we could find another way to connect to the Python instance. -
@mikael yeah, that's definitely the reason, in some fashion. The Objective-C side of things is definitely holding tightly to the Gestures object instance that "self" refers to at the time the delegate ObjCClass is defined. I had to modify it even further to get it to work from one run of the script to another (without killing and restarting Pythonista in between) because it was still holding the reference to the first instance of my custom View class that created the ObjCClass from one run to the next, and thus passing the wrong gesture recognizer instances to the simultaneous gesture and fail/fail_other calls.
Not knowing much about how this works under the hood, I don't know how to prevent it, but I'll look into it a bit more...perhaps it would be possible to hold or pass the reference to the Gestures object as data, and use some generic way to call the appropriate method on that object, instead of holding the method instance.
-
@shinyformica, latest version on Github now uses a class-level lookup to find the right instance when calling delegate functions. Could you please give it a try?
-
@mikael just got to try this this morning. Unfortunately, while it seems like it would work, I once again encounter the strange issue of module-level things becoming undefined when the script is run a second time, from way back at the start of this thread. In this case it is the actual Gestures class which is suddenly None when the code tries to call Gestures.get_self(cls, key).
Again, this only happens the second time the script runs, and every time thereafter. First time through, things work as expected. I actually managed a solution of my own, which isn't ideal: I append the id of the instance of the Gestures object to the name of the delegate class being created by the call to objc_util.create_objc_class() so each Gestures instance gets a unique delegate class and instance of that class, which will be sure to have self as a reference to that particular Gestures instance. This appears to solve the problem, though it means I'll have a new ObjC class definition for each Gesture instance, which in my case means one for each control that gets instantiated...and I don't know the performance or memory implications of that.
I really wish I knew why I was having that strange "undefinition" issue, since your solution only creates one class, and registers the instances of the Gesture object, it seems much more efficient. I'll see if I can figure out a way to do something similar to your way of finding a reference to the Gestures instance without holding an explicit self reference, without triggering my other problem.