Forum Archive

Any UI module tutorials out there?

briarfox

I'm trying to figure out the ui module and was wondering if anyone had any tutorials or examples. I would have liked to have been able to use the editor and view the output code, but that does not appear to be possible. I'm needing to make dynamic views so the editor wont work. Some examples on subclassing view would be most helpful.

Thanks

[deleted]

@briarfox, I was curious about the UI Editor files too... but it appears they are in text format. You can view the .pyui file using the tool you posted to the 'Unsupported File Type' thread.

Did you see this post? http://omz-forums.appspot.com/pythonista/post/4613099080384512 ... it has an example of building a dynamic interface in a subclassed view.

briarfox

@Tony Thanks, I hadn't thought of that. Also example helps. Thank you.

Omega0

To view the text version of the .pyui files you can also use editor.open_file('filename.pyui').

ccc

Brumm created a great Scene tutorial so maybe we can convince him to something similar for the UI module. ;-)

His slick hexviewer embodies some of the best practices that I have been trying to follow:

  • ui.View is the only class in ui module that can be subclassed so take advantage of it...
    • Every time you create a ui.View, consider making it a custom subclass of ui.View.
    • It will give you the ability to build more complex yet comprehensible apps.
  • Each ui element type that gets added as a subview to your ui.View should have its own make_xxx() method.
    • Consider what method parameters will maximize the reusability of your make_xxx() method.
    • Near the beginning of the make_xxx() method, there should be a local_variable = ui.xxx() type of call.
    • The make_xxx() method should not assume that the ui element will be a member variable of your ui.View.
    • The make_xxx() method should only focus on the xxx element and should not mention other ui elements.
    • ~~Near the end of the make_xxx() method, the ui element should be attached as a subview via self.add_subview(xxx) or similar.~~
    • make_xxx() should return the xxx object created so your ui.View can make it a member variable if it wishes to.
  • Consider making your ui.View the target of all .actions of its subviews.
  • Consider making your ui.View the delegate of its subviews.

Try to keep the complexity of view logic inside the class and keep everything else (reading files, networks, complex formatting) outside the class so that you can improve reusability and complexity hiding (separation of concerns). The Model-View-Controller programming paradigm comes to mind.

The ideas around make_xxx() methods are to promote reusability and to reduce the potential issues that I see arising from very long code blocks of ui element setup code especially in scripts that create their ui in Python code, as opposed to in the GUI Editor. I have seen 35 lines of code in one sequence setting multiple subviews all interspersed. These long blocks are difficult for the reader to follow and it will be hard to find interaction bugs in them. By using the make_xxx() method approach outlined above, we will get more code reuse from project to project with fewer hard to find interaction bugs.

ccc

Subclassing ui.View also seems to work for views created from .pyui files:

class MySpecialView(ui.View):
    def __init__(self):
        self = ui.load_view()  # strange syntax but it seems to work
        self.present()

EDIT: See the MyView example below for better syntax.

omz

@ccc You can use ui.View subclasses in the UI editor by setting the Custom View Class attribute in the inspector (works for the root view and "Custom View" widgets).

ccc

@omz thanks... That is a much better way to go.

@briarfox, check out:

import json, pprint
with open('MySpecialView.pyui') as in_file:
    pprint.pprint(json.load(in_file))
briarfox

Thank for the info @ccc it is most helpful.

brumm

@ccc: At the moment I'm writing a ui SFTPclient. Sorry for the delay...

@briarfox: If you're looking for sth. special please let me know. Just write me what your layout should look like and what should be dynamic and in which way.

briarfox

@brumm Thank you for the offer, but I think I have the general idea. I'm going to play around a bit and I'll get back to you if I get stuck.

brumm

@ccc: Thank you so much for optimizing my code. My current project sftp-client contains thousands of globals. Next weekend I will study your pull request.

briarfox

@omz I seem to be doing something from when setting the Custom View Class in the editor. My class does not seem to inherit the editor view.. Could I get an example?

ccc
# coding: utf-8

import ui

class MyView(ui.View):
    def __init__(self):
        self.present()

    def did_load(self):
        self['button1'].title = 'It works ;-)'

the_view = ui.load_view()  # strange syntax for creating an instance of MyView but it works
brumm

Okay I started a tutorial. But you have to be patient because I'm starting with the very basics (and my time is limited - so the tutorial is slow-growing).
ui-tutorial

briarfox

@brumm Thank you.

ccc

I am getting the following warning when I try in the UI Editor to set an button's action attribute to a method of its root view: Warning: Couldn't bind action 'self.button_tapped' of 'button1'

I have tried in the UI Editor to set the button's action attribute to: button_tapped, self.button_tapped, and self.superview.button_tapped with no success. If I uncomment the commented line below then I can set the action in Python code. I just can not figure the syntax for setting it in the UI Editor.

import ui

class MyView(ui.View):
    def __init__(self):
        self.present()

    def did_load(self):
        self['button1'].title = 'It works ;-)'
#        self['button1'].action = self.button_tapped

    def button_tapped(self, sender):
        sender.title = 'Got tapped'

the_view = ui.load_view()
briarfox

@ccc I have the same issue ended up setting it outside of the editor.

I'm not real fond of the way you call a custom view that inherits from the editor the by ui.load_view(). I'd rather create an instance of the class by view = Custom_View() How can I pass parameters to the init of my Custom view class? It seems like your previous idea of loading the view in the init of the class is better. What am I missing?

omz

@ccc An action that is set from the UI editor can either be a globally visible function, or a method of the class from which you're loading the view (i.e. whatever self evaluates to when you call load_view). In the latter scenario, you'd typically have some kind of controller object that loads a view and handles its actions.

[deleted]

@ccc I'm trying to implement the best practices you set out. I have some queries please...

1 init() with no parameters and no final pass to View.init() is because all params are optional, right? And we know that View does nothing there other than initialise the options?

7/8 I find the act of adding a subview already makes it a member variable?

9/10 when making View the target/delegate of its subviews what style do you recommend to identify which sender/delegator of possibly many of the same class? (the neatest I've found is below)

Before making View the target/delegate:

    class wvDelegate (object):
        def webview_did_finish_load(self, webview): 
            if id(webview) == id(webview.superview.wv1):
                console.hud_alert('1')
            elif id(webview) == id(webview.superview.wv2):
                console.hud_alert('2')
            elif id(webview) == id(webview.superview.wv3):
                console.hud_alert('3')

Or... after:

    def webview_did_finish_load(self, webview): 
        if id(webview) == id(self.wv1):
            console.hud_alert('1')
        elif id(webview) == id(self.wv2):
            console.hud_alert('2')
        elif id(webview) == id(self.wv3):
            console.hud_alert('3')

Or... put more simply:

    def webview_did_finish_load(self, webview): 
        if webview is self.wv1:
            console.hud_alert('1')
        elif webview is self.wv2:
            console.hud_alert('2')
        elif webview is self.wv3:
            console.hud_alert('3')
Best practices:
1 ui.View is the only class in ui module that can be subclassed so take advantage of it. Every time you create a ui.View, consider making it a custom subclass of ui.View. It will give you the ability to build more complex yet comprehensible apps.
2 Each ui element that gets added as a subview to your ui.View should have its own make_xxx() method.
3 Consider what method parameters will maximize the reusability of your make_xxx() method.
4 Near the beginning of the make_xxx() method, there should be a local_variable = ui.xxx() type of call.
5 The make_xxx() method should not assume that the ui element will be a member variable of your ui.View.
6 The make_xxx() method should only focus on the xxx element and should not mention other ui elements.
7 Near the end of the make_xxx() method, the ui element should be attached as a subview via self.add_subview(xxx) or similar.
8 make_xxx() should return the xxx object created so your ui.View can make it a member variable if it wishes to.
9 Consider making your ui.View the target of all .actions of its subviews.
10 Consider making your ui.View the delegate of its subviews.
11 Try to keep the complexity of view logic inside the class and keep everything else (reading files, networks, complex formatting) outside the class
ccc

1 I am unclear what your text around this means. When I subclass View, my __init__() only takes self as a parameter and the method never calls View.__init__() and it usually only contains one method call to self.present(). The real work will happen in did_load().

7/8 I see a difference between self['save button'] and self.save_button but perhaps it is not super important. However, what I am interested to have is a single function/method that I can call to create the save, load, and delete buttons. I would lose that reusability if the function/method directly attached the newly created button to self.

9/10 My recommendation was NOT to create a separate class wvDelegate (object). Instead, so far, I have seen it best to make MySuperCoolView or MySuperCoolController (the 'self' at View creation time) be the delegate. I would advocate using sender.name instead of id(sender) in your examples. Then you can do things with self[webview.name] or... names go well with dicts:

def webview_did_finish_load(self, webview): 
        webview_names = {'Apple' : 1, 'Google' : 2, 'IBM' : 3}
        alert_id = webview_names.get(webview.name, 0)
        if alert_id:
            console.hud_alert(str(alert_id))
        else:
            panic()
[deleted]

@ccc Thanks.

Sorry about '1'... I meant the init we use for the subclass of View, skips the init of View itself, I think. I can't see View's init... but presumably we know it's safe to skip it?

ccc

Yes. Read the comments under __init__() in the code block at http://omz-software.com/pythonista/docs/ios/ui.html#building-custom-views "You don't have to call super.".

[deleted]

In case it helps anyone following the best practices... there's currently a subtle difference between View and a sub class of View in terms of how they run between the main UI thread and the main interpreter thread... as the little test below shows (swap the comment line for the one above):

import ui
import console

class TestView (ui.View):
    pass

v = ui.View()
#v = TestView()
v.present('popover')
console.alert('Test', button1 = 'OK', hide_cancel_button = True)
[deleted]

@ccc Regarding best practice 2... is there a case for the make_xxx() method to be _make_xxx() per the convention in The Python Tutorial... 8.6?

ccc

See the notes on mixing console.alert with the ui module at http://omz-software.com/pythonista/docs/ios/ui.html#about-actions-and-delegates

Your _make_xxx() idea makes sense to me.

[deleted]

@ccc Thanks and yes, but the point here is that the mere act of subclassing View causes it's thread behaviour to change. (If you run the test with View it will complete, but with TestView it will freeze)

I mention it because it can cause problems to surface when converting to the best practices.

For a case in point... see the Keychain Master Password thread... "This only happens when subclassing ui.View. When making a "standalone" view there's no lock up:" which @omz is investigating for a fix.

The in background function decorator doesn't work as advertised here in the current version I think.

ccc

The other KEY difference is that custom Views can not set self-based actions in the UI editor. Instead, the actions must be set in Python code. See the set_actions() method in the AreYouEnabledView.py example in the ui-tutorial.

omz

@tony This looks like a bug to me, thanks!

briarfox

Tutorial Suggestions

  • What is the best practice to handle multiple views? I'm guessing a controller class that handles the ui.View changes.
  • What is the best practice on passing arguments to a custom views init? It appears that if you use the editor sub class that this is not possible.
brumm

Looking for this?

https://github.com/humberry/ui-tutorial/blob/master/MultipleViews.py

briarfox

@brumm I'm referring to multiple views as a bunch of separate ui.Views made in the editor. I was wondering that the best practice is to handle and load them as the user moved from one view to another.

btw Thank you for the tutorials, they cleared up a lot of my confusion.

Also had a question in regards to displaying on iPhone vs iPad. Would it be a good practice to have two .pyui files, one fore each device? If so do you know of any way to get the ios device being used?

Here is a little function I use to set the style of all my ui.view 's. It can take a dict that has the style elements for each view in the form of:

default_style = {}
default_style['Main'] = {'background_color': 'white'}
default_style['Button'] = {'background_color': 'FFCAB0',
                   'border_color': 'FF9F70',
                   'border_width': 5,
                   'tint_color': 'FFFFFF',
                   'font': ('arial',14),
                   }
#Continue with Lable Tableview etc.

def set_style(view, style=default_style):
    for key, value in style['Main'].items():
        setattr(view, key, value)
    for v in view.subviews:
        try:
            for key, value in style[type(view[v.name]).__name__].items():
                setattr(view[v.name], key, value)
        except:
            print 'Missing style for %s' % type(view[v.name]).__name__

I call the function in did_load(self) to style all the ui.view 's the same. Much easier then using the editor.

brumm

It's possible to load one view over another (pop-over2), but I don't know if this is a good style whether if there are performance issues.
You may get the screen size via the scene modul, but my attempts always ends up with a crash...

ccc

Can't you get the screen dimensions with the UI module?

import ui
the_view = ui.View()
the_view.present()
print(the_view.width, the_view.height)
#print(the_view.bounds,
#      the_view.frame)
the_view.close()
brumm

You're totally right, thank you.

briarfox

@brumm Yeah I've been playing with different ways to load multiple ui.Views. I've tried using a main view and loading others as a subview. I've tried a view controller that loads and manages the views. I'm just not sure what the preferable method is.

I'm also unsure what the best way to create a custom view. Is it by using the Custom View Class in the editor(gain access to did_load etc.) or by making a class inherit from ui.view?