Forum Archive

Custom ui.View Instantiation Pattern

iAmMortos

This is a pattern I've been using to instantiate custom Views that have accompanying pyui files with dynamic data. Just looking for some feedback or any constructive criticism to improve it or maybe just share the pattern if it can help someone else.

Let's say I have a custom ui.View class called MyView. The pyui file that represents this View would be called my_view.pyui, and would set Custom Class to MyView in the main view properties. This view would contain a single Label component named lbl. I want to be able to instantiate this view with a constructor-like method that affects the contents of the View when it gets shown.

The following pattern has made this possible in my_view.py:

# my_view.py
import ui

class MyView(ui.View):
  def did_load(self):
    self.my_lbl = self['lbl']

  def init(self, text):
    '''
    since the ui.load_view() handles instantiating the actual View object, we can't access the true __init__
    method. So we'll call this one as part of our custom static load_view method.'''
    self.my_lbl.text = text

  @staticmethod
  def load_view(text):
    '''
    I particularly like this simple shortcut as it allows me to create instances of a custom class somewhere
    else without having to remember the name of its file. Much cleaner.
    This becomes our makeshift "constructor."'''
    v = ui.load_view()
    v.init(text)
    return v


if __name__ == '__main__':
  # load MyView and initialize the view's label with text "Hello World!", then show the view.
  v = MyView.load_view("Hello World!")
  v.present()

That wysiwyg UI editor in Pythonista is so stinkin' handy, and this pattern seems like a nice compromise between using the UI editor, and being able to create dynamic, flexible, and reusable views in a pythonic way.

shinyformica

Good stuff, nice and simple.

I'll take the opportunity to also point to the brilliant bit of python magic that @JonB came up with to make it possible to instantiate a pyui file directly as a ui.View subclass without deviating from standard class instantiation syntax.

Here's the derivation of it which I use regularly:

class PyUiView(ui.View):
    """
    Wrapper class for a custom ui.View

    If a pyui file is specified when instantiated then this view
    loads the pyui view and makes itself the root view. Otherwise
    acts as a normal custom ui.View subclass.

    If you subclass this for your custom view, the custom_class
    of your pyui root view needs to be set to the custom view's class.
    """

    def _WrapInstance(self):
        #### Wrap this view instance in an object which will
        #### return itself when "instantiated". This allows
        #### this same object to be produced when loaded via
        #### pythonista's pyui loading process.
        class _Wrapper(self.__class__):
            def __new__(cls):
                return self
        return _Wrapper

    def __init__(self, *args, **kws):
        """Custom pyui views may be created with no options, in which
        case they are identical to regular ui.View objects, or they
        may be created with the following keyword options:
            pyui: a path to a .pyui file to load into the view.
            custom: the name of the custom_class of the root view in the pyui file.
            bindings: bindings to place in the local namespace
                        when the pyui file is loaded into the view.
                        Use this to make available modules and custom view classes
                        which are referenced by the pyui.
        """
        pyuifile = kws.pop("pyui",None)
        custom = kws.pop("custom", self.__class__.__name__)
        bindings = kws.pop("bindings",{})

        super(PyUiView, self).__init__(*args, **kws)

        if pyuifile:
            #### bindings include the current global namespace,
            #### and also two items needed to make sure this view
            #### instance is used as the root of the view hierarchy:
            ####    - either the name of this view class, or the given
            ####        custom class name, mapped to the
            ####        wrapper object that returns this instance on
            ####        instantiation.
            ####    - the name "self" mapped to this instance
            #### the bindings ensure that when the pyui custom classname
            #### matching this class is instanced, this same object
            #### is returned
            bindings.update(globals())
            bindings[custom] = self._WrapInstance()
            bindings["self"] = self
            ui.load_view(pyuifile, bindings)

this allows for dynamic instantiation either by subclassing the base PyUiView class and naming the new subclass for the custom_class of the pyui root view:

class MyView(PyUiView): pass
v = MyView(pyui="test_customview.pyui")
v.present("sheet")

or by instantiating a PyUiView class with the 'custom' parameter set to the pyui root view custom_class:

v = PyUiView(pyui="test_customview.pyui", custom="MyView")
v.present("sheet")
iAmMortos

Oooooh, maaaagic.
Thank you! This is exactly the kind of improvement I've been looking for!