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.
Getting touch-ended/touch-cancelled in a scrollview?
-
Small update:
I tried doing something clever via ui.delay(). When a touch_began() occurs, I set a ui.delay() which keeps checking if the scroll view has scrolled in any way (looks at the dragging attribute and checks the current content offset, I can't check the tracking attribute, since that is set to true whether or not the scroll view has been scrolled).
Anyway, what I'm noticing is that under certain circumstances, it appears that no touch events are delivered to my custom view, and the ui.delay() is also suspended, while the scroll view is being scrolled. So there is literally no way to know that scrolling was initiated.
-
Ok, here's the solution I've settled on for the moment...it works better than other ideas, though it is still ugly.
What I landed on was: replace any existing delegate assigned to the parent scroll view with my own, saving the original delegate (if any) so that I can call it directly from my own (basically wrapping the existing delegate with my own). The delegate appears to get its scrollview_did_scroll() method called properly whenever the scrollview scrolls, so I can cleanly end user interaction if the scroll happens after a touch_began() occurred in my custom view.
Anyway, it works, though it isn't pretty.
-
I believe you can use @mikael's pythonista_gestures library. I believe this let's you get all of the events, including cancel.
-
@JonB I'm using that module extensively already (thank you @mikael). Unfortunately, there's two issues using it in this case:
-
I need to see the initial press/touch all the way through to the lift/touch end. A tap gesture is only recognized when the tap is complete, so you only get the tap callback after the finger is lifted.
-
Even if I were to make a custom gesture recognizer, or attempt to use tap gestures, you still have to go through all the trouble of setting up the "requires failure of" gesture relationship with the scroll view, and other complications to get it to work for my purposes.
The whole thing is somewhat complicated, but in the end it just boils down to not having a touch_cancelled() method on regular ui.Views. UIControls actually have a way to connect to the various touch phases, but regular UIViews do not.
I will say that using the delegate as above works pretty flawlessly.
-
-
is there an example of how you get a scroll view to recognize touch events?
-
@donLaden
some methods to overide from the View class.def touch_began(self, touch): # Called when a touch begins. pass def touch_moved(self, touch): # Called when a touch moves. pass def touch_ended(self, touch): # Called when a touch ends. pass def keyboard_frame_will_change(self, frame): # Called when the on-screen keyboard appears/disappears # Note: The frame is in screen coordinates. pass def keyboard_frame_did_change(self, frame): # Called when the on-screen keyboard appears/disappears # Note: The frame is in screen coordinates. pass
-
-
-
@mikael @Stephen Thanks for the quick responses.
I’ve never really dabbled in Python, I’ve just started to really try and teach myself so I could build an app to help manage my shop at work. I was trying to build a pull to refresh function into my scroll view. I’m just working on trying to make sense of all this and make something useful I can add to my app later. I’ll give it a go this weekend and see what I can accomplish. -
-
@donLaden, thinking about it a bit more: all you need to do is to set a delegate for the scrollview, and in the
scrollview_did_scroll
method check thecontent_offset
to see if it has been pulled down and how far.import ui class Delegate: max_pull = -100 def __init__(self): self.refreshing = False def scrollview_did_scroll(self, scrollview): x, y = scrollview.content_offset if y < 0: delta = max(y, self.max_pull)/self.max_pull scrollview.background_color = (1, 0, 0, delta) if ( y < self.max_pull and not scrollview.dragging and not self.refreshing ): self.refresh() def refresh(self): self.refreshing = True print('refresh') sv = ui.ScrollView() c = ui.View(background_color='darkgrey') c.width, c.height = ui.get_screen_size() sv.content_size = c.frame.size sv.add_subview(c) sv.delegate = Delegate() sv.present('fullscreen')
-
@mikael Thanks for the pull to refresh example. I really appreciate the help. I had a couple issues. The biggest issue was only being able to refresh once after the view is loaded. The second Issue was when pulled too far down, the refresh function was called multiple times. I think I solved these issues. I attached the solution I came up with below.
import ui, sound class scrollViewDelegate: maxPull = -100 def __init__(self): self.refresh = False def scrollview_did_scroll(self, scrollview): x, y = scrollview.content_offset if y <= 0: delta = max(y, self.maxPull) / self.maxPull if (y <= self.maxPull and not scrollview.tracking and scrollview.decelerating and not self.refresh): scrollview['refresh_bg'][ 'refresh_label'].text_color = 0.50, 0.50, 0.50, delta self.contentRefresh() elif (y <= self.maxPull - 10 and scrollview.tracking and not scrollview.decelerating and not self.refresh): scrollview['refresh_bg']['refresh_label'].text = 'Release to refresh' scrollview['refresh_bg'][ 'refresh_label'].text_color = 0.50, 0.50, 0.50, delta elif (y >= self.maxPull - 10 and scrollview.tracking and not scrollview.decelerating and not self.refresh): scrollview['refresh_bg']['refresh_label'].text = 'Pull to refresh' scrollview['refresh_bg'][ 'refresh_label'].text_color = 0.60, 0.60, 0.60, delta def contentRefresh(self): self.refresh = True sound.play_effect('ui:click3') #print('refreshing') ui.delay(self.contentDidRefresh, 0) def contentDidRefresh(self): self.__init__() w, h = ui.get_screen_size() sv = ui.ScrollView() sv.name = 'Scrollview' sv.always_bounce_horizontal = False sv.always_bounce_vertical = True sv.directional_lock_enabled = True sv.scroll_indicator_insets = (10, 0, 10, 0) sv.bounces = True sv.paging_enabled = False sv.indicator_style = 'black' sv.scroll_enabled = True sv.background_color = 1.0, 1.0, 1.0 sv.content_inset = (0, 0, 0, 0) sv.content_size = (w - 10, h*2) sv.delegate = scrollViewDelegate() rb = ui.View() rb.name = 'refresh_bg' rb.alpha = 1.0 rb.background_color = 0.90, 0.90, 0.90 rl = ui.Label() rl.name = 'refresh_label' rl.alpha = 1.0 rl.alignment = ui.ALIGN_CENTER #rl.background_color = 'white' rl.text = 'Pull to Refresh' rl.text_color = None rl.font = ('<system>', 14) rb.add_subview(rl) sv.frame = (0, 0, w, 0.5) rb.frame = (0, -600, sv.width, 600) rl.frame = (0, rb.height - 32, sv.width, 32) sv.add_subview(rb) sv.present('fullscreen')