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.
Crashing pythonista on pushing view
-
Now I just want to say I don't think this is a bug in pythonista.
I have a much larger "app" I'm working on in pythonista but for the problem I'm having I created a basic example so it isn't very nice code.
With that out of the way here is my example.
import ui import time class source (object): def __init__(self): self.selectcb = None def tableview_number_of_rows(self, tv, s): return 1 def tableview_cell_for_row(self, tv, s, r): cell = ui.TableViewCell() cell.text_label.text = 'DoubleTap' return cell def tableview_did_select(self, tv, s, r): self.selectcb() time.sleep(1) view = ui.TableView() s = source() nav = ui.NavigationView(view) w,h = ui.get_screen_size() v2 = ui.View(frame=(0,0,w,h)) @ui.in_background def cb(): time.sleep(1) nav.push_view(v2) time.sleep(1) s.selectcb = cb view.data_source = s view.delegate = s nav.present()
My problem is when I double tab the row it calls the call back to add the view to the navigation controller. Not sure this the best way to do it. I have put the sleeps in as in the other script I'm working on I do some processing before I push the view on, so I am simulating that with sleeps.
So if I double tap on the row and wait like 5 seconds after the view is pushed it crashes pythonista.
Anyway I know what the error is as I have the fault handler and exception catching script on so it produces this.
Fatal Python error: Aborted
Thread 0x000000016e247000 (most recent call first):
Objective-C exception details:
UIViewControllerHierarchyInconsistency: A view can only be associated with at most one view controller at a time! View <SUITableView_PY3: 0x1371a3000; baseClass = UITableView; frame = (0 64; 414 672); clipsToBounds = YES; gestureRecognizers = <NSArray: 0x138aac4e0>; layer = <CALayer: 0x13b35a520>; contentOffset: {0, 0}; contentSize: {414, 336}> is associated with <SUINavigationViewContentViewController_PY3: 0x13b4c31f0>. Clear this association before associating this view with <SUINavigationViewContentViewController_PY3: 0x1393bedf0>.
I have left the stack trace out. Now so I can see the issue but I don't know how to get around this from occurring. It appears to only happen when I have decorated the function to push the view on the navigation view in a ui.in_background
Any thoughts are appreciated.
-
I should mention, my intention isn't for the row to be double tapped but as there could be a pause before the view gets pushed, the user might tap the row again.
-
time.sleep
isn't really compatible withui
. Instead, useui.delay
?? -
@ccc The reason I had the sleeps in was to pause the code. In the other code where I had the problem it doesn't use them, so haven't really tried to change that example.
What I did do though in my other code I needed console.alert in those methods which required me to have ui.inbackground otherwise it locked up (from memory anyway it was a while since I wrote that bit). Anyway my work around that seems to be working is to write another function that just shows the alert and decorate that with ui.in_background and remove it from the function that does the pushing of the view and call the alert function.
-
You might try protecting the push with a
if v2.navigation_view: ...
What is happening is you are selecting the row twice by double tapping (select is a single tap action). This calls your cb() twice in quick succession.
You might also use on_main_thread for the call to push_view, and wrap the call in a try/except.
-
By the way, it is very confusing if the user presses a button, then some long time later the view changes. If you have lots of heavy computation to populate the view, instead of waiting until it is ready, try to push an empty view first, fill it with a ui.ActivityIndicator, THEN call a long background computation to fill it in with content. This prevents the user from mashing rows in the tableview trying to get something to happen.
Or, if you are not sure whether you will push the view until doing the long computation, "disarm" the table as soon as the user selects a row, and "rearm" it when computation is done. This would be done in the tableview_did_select, assuming your source has a new armed attribute.
def tableview_did_select(self, tv, s, r): if self.armed: self.armed=False self.selectcb() time.sleep(1)
then, in cb(), you would rearm the view.data_source after the long computation.
-
Thanks @JonB I will add the activity indicator on click and the nav check on the view, just to make sure it doesn't occur again.