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.
Custom ui.View Instantiation Pattern
-
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 calledMyView
. Thepyui
file that represents thisView
would be calledmy_view.pyui
, and would set Custom Class toMyView
in the main view properties. This view would contain a singleLabel
component namedlbl
. I want to be able to instantiate this view with a constructor-like method that affects the contents of theView
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.
-
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")
-
Oooooh, maaaagic.
Thank you! This is exactly the kind of improvement I've been looking for!