how to break out of loop using UI button
I am new to Pythonista.
I need help with my script. It is very simple.
I created a button in Pythonista UI tool.
I run infinite loop and want to break outthis loop by pushing this button.
The code is below:
import ui import time running= True def button_tapped(sender): '@type sender: ui.Button' print('Done!') running = False #ui.close_all() ui.load_view('done_button').present('sheet') while True: if running == False: print('out of here') break print('running') time.sleep(2)
The script runs fine, prints "running" message.
When I push button, I see "Done" message but the loop never breaks.
I never see "out of here" message.
Can you help?
global runningat the top of
button_tapped()because you want to write to a global variable from inside a function.
That did the trick !
Thanks a lot !
It is great that you have broken out of the loop. Note that selecting
Analyze (Pyflakes)under the wrench menu would have pointed you to the offending line but the error message (
'running' assigned to but never used) is probably too cryptic to help much.
@leos , there are so many ways to do what you want. I appreciate you are new to Pythonista. But using a global is the last way you should consider. It's a very bad idea. On top of that you are jumping over some key concepts about using the ui module.
Below is just one way to do what you wanted. I could have made it more compact, but I didn't in the hope it was more easy to understand.
Hope it helps
import ui import time #running= True def button_tapped(sender): '@type sender: ui.Button' print('Done!') sender.enabled = False # setting the enabled attr to false #running = False #ui.close_all() # broken this call into 2 parts. i am not sure the reasin, but if you need the a a reference # to the view back, it has some problems. v = ui.load_view('done_button') v.present('sheet') while True: #if running == False: commented out # here, we are looking at the uiButton.enabled attr to decide if its been clicked # your script was also not working correctly if you closed your window. # so we are checking the ui.Views attr on_screen. if the view is closed v.on_screen # would be False so we should also break out f the while loop. if not v['button1'].enabled or not v.on_screen: print('out of here') break print('running') time.sleep(2)
@leos , sorry. I got a little carried away and did not really see what you were trying to do here. I think all you wanted to really do is close the view from the button. The while loop is what you thought you had to do. I think that's what's going on.
All you needed to do to get a reference back to the view was to say
v = sender.superview. Every ui object has a superview view you can access. So the below does what I think you were trying to achieve. It takes a little getting using to when you first start with ui, but it's been very well built. It's worth putting a little reading into it. Esp ui.View as all controls are basically ui.Views with some additional attrs and methods.
import ui def button_tapped(sender): '@type sender: ui.Button' print('Done!') sender.superview.close() ui.load_view('done_button').present('sheet')
@leos, just wanted to post this for you as well. The below example is using a custom ui.View class to do what you did in the .pyui file. The ui designer is great especially when views are complicated. But for simple views, a custom ui.View class is very nice and fast. Just wanted you to see it from another way.
import ui class MyClass(ui.View): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.make_view() def make_view(self): btn = ui.Button(frame=(10, 10, 80, 32), bg_color = 'white', border_width=.5, corner_radius=3, action=self.button_hit) btn.title = 'Close' self.add_subview(btn) def button_hit(self, sender): self.close() if __name__ == '__main__': v = MyClass(frame = (0, 0, 300, 400), bg_color='teal', name='Test Form') v.present(style='sheet')
Thank you for your help.
I am still learning about ui module.
I run your example and get Attribute error message "NoneType has no attribute' enabled' "
Where is this attribute set?
if not v['button1'].enabled or not v.on_screen:
@leos i guess you are talking about this line above. replace button1 with the name you have named your button in the ui Designer, then it should be fine.
no, that did not help.
I changed 'button1' to the name I use ('done_button'), but I get the same message.
"NoneType has no attribute' enabled' "
@leos , can you pls post your code so i can see
Thanks for you help - I figured out what was wrong . I did not name my button properly in UI designer.
Now your code example runs fine.
I have another question though.
Lets say my code loads one view with a button which does some action.
As a result of this action, the code loads another view with 2nd button which does a different action
Should i close the 1st view before loading 2nd view? What is the command for closing view?
If I do ui.close_all() , Pythonista crashes out
@leos, you cant close your view before another view is presented on the screen. I don't want to say to much about that as it may have changed or I am a little wrong.
But below I put an example that hopefully helps you. I have used Custom views instead of pyui files from the designer so it's easier to explain. But it should work equally with loaded views from pyui files.
But I think what I have done is a pretty normal case. Notice after the second view is presented I have put v.wait_modal(). This makes the view act like a dialog. I have put print statements around the opening and closing so you can see that your code for that view is basically suspended until the new view is closed.
Views also have a hidden attr, you could also use that if you didn't want to see the first view.
Anyway, I hope the below helps.
import ui def make_btn(title, action=None): btn = ui.Button(frame=(20, 20, 80, 32), border_width=.5, bg_color='white', corner_radius=6, action=action ) btn.title = title return btn class MyClass(ui.View): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.make_view() def make_view(self): btn = make_btn('Close', self.close_frm) self.add_subview(btn) btn1 = make_btn('Open', self.show_new_form) btn1.x = btn.width + 50 self.add_subview(btn1) def close_frm(self, sender): self.close() def show_new_form(self, sender): v = MyClass2(frame=self.frame, bg_color='purple') v.present(style='sheet') print('The view has been opened') v.wait_modal() # your code will wait here, until the view closes print('The view has been closed') class MyClass2(ui.View): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.make_view() def make_view(self): btn = make_btn('Close', self.close_frm) self.add_subview(btn) def close_frm(self, sender): self.close() if __name__ == '__main__': f = (0, 0, 300, 400) v = MyClass(frame = f, bg_color='white') v.present(style='sheet')
I'm finding that using dialogs functions triggers wait_modal() before the view has been closed.
I changed the code in your last example to add a button which triggers dialogs.get_duration() and swapped the print statements with dialogs.hud_alert() so that I could see when wait_modal() is being triggered. Your code works as expected, even if it calls a dialog.
This simple example shows wait_modal() being triggered by a dialog opening, before the view closes. I'm struggling to understand why this happens in my example but not yours.
import ui, dialogs def getstat(sender): dialogs.duration_dialog() v = ui.View(background_color='white') btn = ui.Button(title='Dialog', action=getstat, frame=(50,50,50,50)) v.add_subview(btn) v.present() v.wait_modal() dialogs.hud_alert('v closed')
@kastore , sorry for the delay to get back to you. I did see your post but forgot to get back to it because I dont check this area as much.
But sorry, I cant be much help. Your code posted seems to work fine on my iPad Pro. I am still on the latestet Pythonista beta and iOS 11.2.1
Are you on the beta or on the new release from the App Store? If you changed did it fix your problem? If it didn't what is your device? I have some other devices I could try