UI proxies for subclassing
I'm Convinced That
uiWas Deliberately Designed to be Unhackable: the Rant. 😛
I. ProxyTypes is cool (and old)
Today I started using
ProxyTypesto "subclass" the uninheritable
ui.Viewclasses  (e.g.
Slider), as @mikael showed a while ago. These classes delegate literally everything to the wrapped view.
Take the following example:
>>> from ui2.subclassing.proxies import ObjectProxy >>> a = ui.View() >>> b = ObjectProxy(a) >>> b.background_color (0.0, 0.0, 0.0, 0.0) >>> b <_ui.View object at 0x10cbbf048> >>> isinstance(b, ui.View) True
ObjectProxymakes it nearly impossible to distinguish
a. The only way they can be told apart is:
>>> type(b) <class 'ui2.subclassing.proxies.ObjectProxy'>
These classes can't be added as subviews:
>>> ui.View().add_subview(b) Traceback (most recent call last): File "<string>", line 1, in <module> TypeError: Expected a View
To me, it looks like
issubclass(type(b), ui.View). Now, there's no reason why
uicouldn't have used the simpler
isinstancemethod unless it's deliberately trying to prevent my sort of trickery. The thing is, I've read the source of
ProxyTypes(I rewrote most of it yesterday) and it forwards everything, far more than should be necessary. I'm assuming that the
_uilibrary does most of its stuff off of the
_objc_pointer, which is totally forwarded and should be covered. For god's sake, even
ObjCInstanceis forwarded through the
>>> ObjCInstance(b) <b'SUIView_PY3': <SUIView_PY3: 0x15a1342a0; frame = (0 0; 100 100); clipsToBounds = YES; layer = <CALayer: 0x159085120>>>
In short, the
ObjectProxyis more than robust enough, this should work. Unless @omz is using some kind of weird C type-checking I don't know about, he's going out of his way to prevent this.
III. Almost solutions
These are sorted by my perceived chance of their success.
- Create a new
ObjectProxysubclass which uses multiple inheritance to also inherit from
ui.View. The idea of this is that the class wouldn't actually act as a view itself because of the use of
__getattribute__(VERY different from
__getattr__.) This is "almost" because conflicts with
__slots__mean that I can't create an
ObjectProxysubclass which uses multiple inheritance to also inherit from
ui.View. I'll see if I can take
ProxyTypes, since I think they're just for performance.
- Try to write a proxy with a metaclass overriding
__subclasscheck__so that my
issubclass(type(b), ui.View)check passes. This idea is "almost" because I haven't tried it yet. I've never used metaclasses before, so I may be totally wrong about what they can do. The way I see it, there's also a good chance that
__slots__will make my iPad catch on fire when I try this.
My question is: why? Am I getting something wrong? Why not allow this convoluted method of subclassing the uninheritable types?
Lastly, I'd love to hear any other ideas for overcoming this besides what I've proposed.
 I rewrote
ProxyTypesfor PEP8 compliance and Python 3 support (As of tomorrow, it will not have been updated in 10 years 😱) The new version is in
ui2and there's a good chance I got the licensing completely wrong.
- Create a new
Please, I really doubt that omz is actively trying to stop you from subclassing certain classes. Most likely these things are just a consqeucene of how Python's C API works. First of all, subclassing needs to be explicitly enabled on the C side. When defining a Python class in C, you basically create a big
PyTypeObject(which is a
struct) containing all info about the class. One of the
tp_flags, which contains various flags (duh). For example the one from
PyFloat_Typelooks like this:
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */
And for comparison, the one from
Py_TPFLAGS_DEFAULT, /* tp_flags */
Note how the one for
Py_TPFLAGS_BASETYPEwhile the one for
slicedoes not. And if you try it in Python, you'll see that you can subclass
float, but for
slicePython will tell you that it's "not an acceptable base type".
And why not just enable subclassing by default? Well, the C implementation needs to make a special case for when an instance of a subclass is created, rather than an instance of the class itself. In
float_newthis looks like this:
if (type != &PyFloat_Type) return float_subtype_new(type, args, kwds); /* Wimp out */
In this case the subclass handling is really simple, but that is because
floaton the C side is mostly a struct containing a
double. (And if you check
slice_new, there is no speical case for subclasses there.)
As for the subclass checking, the equivalent of
isinstanceis implemented for each type by macros like these:
#define PyFloat_Check(op) PyObject_TypeCheck(op, &PyFloat_Type) #define PyFloat_CheckExact(op) (Py_TYPE(op) == &PyFloat_Type)
PyObject_TypeCheckis also a macro, defined as:
#define PyObject_TypeCheck(ob, tp) \ (Py_TYPE(ob) == (tp) || PyType_IsSubtype(Py_TYPE(ob), (tp)))
tp in type(ob).__mro__, which obviously does not call any of
And even if
ui's functions accepted things that didn't actually subclass
ui.View, it would probably crash the app. The reason is that on the C side, the pointer to the underlying
UIViewor controller or whatever is actually a struct field, and
_objc_ptris likely a read-only descriptor returning that field's value. So no,
_objc_ptris not sufficient, that's only a convenience attribute for
@dgelessus Thanks. Does this mean that creating a new class inheriting from both
Viewwould have unintended side-effects? I guess I'll just have to try.
The third thing I can try is having
view, but this is probably impractical and fragile.
No, inheriting from
ui.Viewshouldn't cause any issues, though it probably won't do what you want either. When you do this and then add an instance of that subclass to another view, it will add a blank view (the proxy object itself, which inherits from
ui.View) and not the wrapped view.
The most reliable way to handle this might be to implement your wrappers as simple
ui.Viewsubclasses that contain the wrapped view as their only subview, with the appropriate flexing etc. set. That way the wrappers could be used anywhere a normal
Hmm, you're right. It doesn't work :( I really don't like your approach, because it's easily inspectable and easy for users to break. I think I'm going to try overriding
add_subview. @mikael has been using
add_subview(view.__subject__), I'm going to try to override
add_subviewto try this automatically.
Some progress: I've got a wrapper that automatically adds the wrapped as a subview.
The way Python works in this respect is really cool, imo.
>>> a = ViewProxy(ui.Slider()) >>> a <_ui.Slider object at 0x10e642410> >>> a.value 0.0 >>> a.subviews () >>> object.__getattribute__(a, "subviews") (<_ui.Slider object at 0x10e642410>,)
The reason I'm now willing to use a subview is that when doing it with a proxy it's almost completely transparent.
Does this let you subclass slider, as was your original goal?
Success, I've got a wrapper for subclassing. It displays correctly in view heirarchies.
I could be lazy and make the wrapper view have a frame like
(0, 0, 3000, 3000), since that's larger than any iOS device. I'll try an
__getattribute__, the attributes of the container are effectively invisible. The only way is
object.__getattribute__as displayed above. So nobody will mess with my container.
It displays correctly :D
This working approach is similar to Approach 1 listed above, but my assumption that it wouldn't act as a view was incorrect. It acts as a view, but its attributes are inaccessible. I simply add an instance of the wrapped class as a subview.
It seems to me for a ViewProxy there are some attributes which should not be proxied: namely anything that is tied up and out to ObjC geometry, or a relating views to each other. None of the following should be proxied, i think, but handled by the container : name, frame, x, y, center, bounds, flex, hidden, superview, left_button_items, right_button_items, navigation_view, bring_to_front(), close(), present(), send_to_back(), wait_modal(), action or delegate. I am not sure about alpha (not sure if a None bg color lets subviews see through to the underlying ui). add_subview should also be on this list as well, since you would want a subview's superview to return the ViewProxy, not the proxied view....although most people are not adding a subview to a slider.
For positioning, I don't think a large frame is going to work well with the underlying objc code. The container frame should define the size, with the contained subview set with flex='WH' to keep the size fixed to the container.
A few properties like
border_widthor corner_radius may give undesirable behavior, unless you set
clipsToBounds=Falseon the subview objcinstance. Presumably you have the container bg_color set to None...
Finally... likely you would want to override the
actionmethod of the embedded view to call the container's action with the container as sender. Otherwise ObjC will send the low level slider to the action, thus breaking the illusion of a single object. delegate methods are trickier, i suspect you would need a DelegateProxy class which intercepts all delegate calls, and calls the corresponding method in the container's delegate but replacing the first argument with the container, rather than the embedded view.
Ugh. Thanks, though.