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.
Listening for View.frame changes
-
Hi all, is it possible to listen for changes in View.frame, View.x etc... so that changing them could results in calling other functions?
I tried subclassing ui.View and using a property(get, set, del) called x but changes on it doesn’t reflect in View.frame.class PView(ui.View): def __init__(self): self._x = None @property def x(self): return self._x @x.setter def x(self, value): self._x = value self.layout() def layout(self): print(self.frame)
Let say I have c = PView()
Now c.frame leads to Rect(0, 0, 100, 100)
Calling c.x = 7 will print Rect(0, 0, 100, 100) instead of Rect(7, 0, 100, 100)
Also, calling c.frame = (9, 0, 100, 100) doesn’t print Rect(9, 0, 100, 100) as requested and c.x doesn’t lead to 9. -
@brx , here's how to do that:
import ui class TrackView(ui.View): @property def x(self): return ui.View.x.__get__(self) @x.setter def x(self, value): print('changed') ui.View.x.__set__(self, value) v = TrackView() v.x = 7 assert v.frame == (7, 0, 100, 100)
Now, writing that several times for all the different properties would be boring, so:
from functools import partial import ui class TrackView(ui.View): def _prop(attribute): p = property( lambda self: partial(TrackView._getter, self, attribute)(), lambda self, value: partial(TrackView._setter, self, attribute, value)() ) return p def _getter(self, attribute): return getattr(ui.View, attribute).__get__(self) def _setter(self, attribute, value): print('changed') return getattr(ui.View, attribute).__set__(self, value) x = _prop('x') y = _prop('y') width = _prop('width') # And so on... v = TrackView() v.width = 200 assert v.frame == (0, 0, 200, 100)
This works for your custom view, but if you wanted to track the change of any view (like ui.Label), we would need to use the ObjC KeyValueObserving protocol. Implementation here, or you can get it with
pip install pythonista-anchors
. -
Thank you @mikael , it works. Maybe it is something basic but for my actual level in python, it was hard to imagine. Also I think I have to study more to understand the second example… can’t say anything about the third, python first, then bindings… 😤
-
@brx, the last option is the easiest to use, I think. See the example below, where the key part is the
on_change
function. It takes a view and a function to call if the size or position of the view changes; called function gets the view that changed as an argument.import ui from anchors.observer import on_change class Mover(ui.View): def __init__(self, **kwargs): super().__init__(**kwargs) self.add_subview( ui.Label( text='Move me', text_color='white', alignment=ui.ALIGN_CENTER, center=self.bounds.center(), flex='RTBL', ) ) def touch_moved(self, t): self.center += t.location - t.prev_location v = ui.View() mover = Mover( background_color='darkred', center=v.bounds.center(), flex='RLTB', ) mirror = ui.View( background_color='lightgrey' ) v.add_subview(mover) v.add_subview(mirror) def move_mirror(sender): mirror.center = sender.center + (0, 200) on_change(mover, move_mirror) v.present('fullscreen', animated=False)
Several functions or methods can be registered to watch one view, and there is a
remove_on_change
function to stop observing the view (same arguments ason_change
). -
That seems easier… I’ll try it! Thanks