omz:forum

    • Register
    • Login
    • Search
    • Recent
    • Popular

    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.


    Touch Events outside of bounds

    Pythonista
    touch began cliptobounds ui.view bounds
    3
    7
    2005
    Loading More Posts
    • Oldest to Newest
    • Newest to Oldest
    • Most Votes
    Reply
    • Reply as topic
    Log in to reply
    This topic has been deleted. Only users with topic management privileges can see it.
    • mcriley821
      mcriley821 last edited by mcriley821

      Hello,

      I’m trying to make a custom UI view that allows the user to select an option from a drop down box (like a custom ComboBox).

      Everything seems to be working okay, until I actually try to select a row in the TableView. I know it has to do with the way that iOS does hit-testing for views, but I can’t think of an elegant way to get around this. The only thing I can think of is to customize the root view also (but I want to avoid that so I can simply import it to work with any view ootb).

      Essentially, the root view receives the location of the touch, and it then asks its subviews if they contain that touch in their bounds. The custom view then returns False since the touch isn’t in its bounds, although visually the touch is on the TableView.

      Apologies for any formatting confusion/differences (I’m on iPhone), and for any mistakes/issues I’m not completely finished yet!

      Here’s the code:

      import ui
      
      
      """ComboBox is a ui View class that allows a
      string to be chosen from a list of choices by
      tapping on the dropbox button."""
      class ComboBox (ui.View):
      	"""Initializing of ComboBox:
      		Accepts all kwargs of ui.View, as well as:
      			font -> Chosen string font
      			choices -> List of choice strings
      			selected_index -> Initial choice index
      			padding -> Internal padding around the label and dropdown button
                              button_tint -> Color of the dropbox button"""
      	def __init__(self, *args, **kwargs):
      		self.font = kwargs.pop(
      			'font', ('<System>', 20))
      		self.text_color = kwargs.pop(
      			'text_color', "black")
      		self.choices = kwargs.pop(
      			'choices', [""])
      		self.selected_index = kwargs.pop(
      			'selected_index', 0)
      		self.padding = kwargs.pop(
      			'padding', 3)
      		self.button_tint = kwargs.pop(
      			'button_tint', "grey")
      		kwargs.setdefault('border_width', 1.0)
      		kwargs.setdefault(
      			'border_color', '#e5e5e5')
      		kwargs.setdefault('corner_radius', 3.5)
      		ui.View.__init__(self, *args, **kwargs)
      		
      		# button for the dropbox
      		_x = self.width - self._padding - 30
      		_h = self.height - self._padding * 2
      		self.drop_button = ui.Button(
      			bg_color=self.bg_color,
      			frame=(_x, self._padding, 
      						 self.height, _h),
      			image=ui.Image('iow:arrow_down_b_24'),
      			tint_color=self.button_tint,
      			action=self.dropbox_should_open
      		)
      		
      		# label for selected item
      		# default to item 0
      		_w = self.width - self.drop_button.width - self._padding * 2
      		self.selected_label = ui.Label(
      			bg_color=self.bg_color,
      			alignment=ui.ALIGN_CENTER,
      			font=self.font,
      			text=self.choices[self.selected_index],
      			frame=(self._padding, self._padding, 
      						 _w, _h),
      			text_color=self.text_color
      		)
      		
      		# dropbox
      		_row_h = ui.measure_string(
      			self.choices[0], font=self.font)[1]
      		_row_h += self._padding
      		_h *= 5 if len(self.choices) > 5 else len(self.choices)
      		self.dropbox = ui.TableView(
      			bg_color=self.bg_color,
      			row_height=_row_h,
      			seperator_color=self.border_color,
      			data_source=self._data_source,
      			selected_row=-1,
      			allows_selection=True,
      			frame=(self._padding, self.height - 1,
      						 self.selected_label.width,
      						 _h),
      			border_color=self.border_color,
      			border_width=self.border_width,
      			corner_radius=self.corner_radius,
      			hidden=True,
      			touch_enabled=True
      		)
      		
      		# draw tableview although out of bounds
      		obj = self.objc_instance
      		obj.setClipsToBounds_(False)
      		
      		# add subviews
      		self.add_subview(self.selected_label)
      		self.add_subview(self.drop_button)
      		self.add_subview(self.dropbox)
      	
      	@property
      	def selected_index(self):
      		return self._selected_index
      	
      	@selected_index.setter
      	def selected_index(self, value):
      		if value < len(self.choices):
      			self._selected_index = value
      			if hasattr(self, 'selected_label'):
      				self.selected_label.text = self.choices[value]
      				self.set_needs_display()
      
      	@property
      	def selected_text(self):
      		return self.choices[self._selected_index]
      	
      	@property
      	def padding(self):
      		return self._padding
      	
      	@padding.setter
      	def padding(self, value):
      		if value < self.height / 2:
      			self._padding = value
      			self.set_needs_display()
      	
      	@property
      	def choices(self):
      		return self._data_source.items
      	
      	@choices.setter
      	def choices(self, value):
      		if type(value) is list and len(value) > 0:
      			ds = ui.ListDataSource(value)
      			ds.delete_enabled = False
      			ds.move_enabled = False
      			ds.font=self.font
      			ds.action=self.index_changed
      			ds.text_color=self.text_color
      			self._data_source = ds
      	
      	def layout(self):
      		# selected label layout
      		self.selected_label.width = self.width - self.height - self._padding * 2
      		self.selected_label.height = self.height - self._padding * 2
      		# drop button layout
      		self.drop_button.x = self.width - self.height
      		self.drop_button.width = self.height - self._padding
      		self.drop_button.height = self.height - self._padding * 2
      		# dropbox layout
      		self.dropbox.width = self.selected_label.width
      		self.dropbox.y = self.height
      		_h = ui.measure_string(
      			self.choices[0], font=self.font)[1]
      		_h += self._padding
      		_h *= 5 if len(self.choices) > 5 else len(self.choices)
      		self.dropbox.height = _h	
      	
      	def touch_began(self, touch):
      		print(touch)
      		if self._touch_in_frame(touch, self.selected_label.frame) and touch.phase == "began":
      			if self.dropbox.hidden:
      				self.dropbox_should_open(None)
      			else:
      				self.dropbox_should_close(None)
      
      	@staticmethod
      	def _touch_in_frame(touch, frame):
      		x, y, w, h = frame
      		xmin, xmax = x, x + w
      		ymin, ymax = y, y + h
      		x, y = touch.location
      		if xmin < x < xmax:
      			if ymin < y < ymax:
      				return True
      		return False
      	
      	def draw(self):
      		# draw the splitter border
      		p = ui.Path()
      		p.move_to(
      			self.selected_label.width + self._padding * 1.5, 0)
      		p.line_to(
      			self.selected_label.width + self._padding * 1.5,
      			self.height)
      		p.line_width = self.border_width
      		ui.set_color(self.border_color)
      		p.stroke()
      	
      	def dropbox_should_open(self, sender):
      		# animate drop box
      		if sender:
      			sender.action = self.dropbox_should_close
      		ui.animate(self.do_dropbox)
      	
      	def do_dropbox(self):
      		self.dropbox.hidden = not self.dropbox.hidden
      
      	def dropbox_should_close(self, sender):
      		if sender:
      			sender.action = self.dropbox_should_open
      		ui.animate(self.do_dropbox)
      
      	def index_changed(self, sender):
      		new_index = sender.selected_row
      		if new_index != self.selected_index and new_index != -1:
      			self.selected_index = new_index
      
      
      if __name__ == "__main__":
      	root = ui.View(bg_color="white")
      	combo = ComboBox(bg_color="white",
      									 choices=['test', 'test2'],
      									 corner_radius=3.5,
      									 )
      	combo.frame = (50, 50, 275, 40)
      	root.add_subview(combo)
      	root.present('sheet', hide_title_bar=True)
      
      
      cvp JonB 2 Replies Last reply Reply Quote 0
      • cvp
        cvp @mcriley821 last edited by

        @mcriley821 you could try this one

        1 Reply Last reply Reply Quote 0
        • JonB
          JonB @mcriley821 last edited by

          This post is deleted!
          1 Reply Last reply Reply Quote 0
          • JonB
            JonB last edited by

            So, I think the options are:

            1. change the frame size when the box is activated. That's what polymerchm is doing in his code.

            2. see https://stackoverflow.com/questions/5432995/interaction-beyond-bounds-of-uiview

            It would be possible to create a custom UIView class, I think, where you override pointInside_withEvent_ (I think that's how it would translate to objc_util).
            Or you can swizzle that method on SUIView or whatever it is called .

            1 Reply Last reply Reply Quote 0
            • cvp
              cvp last edited by

              Why no use of did_select delegate of ui.TableView?

              1 Reply Last reply Reply Quote 0
              • JonB
                JonB last edited by

                Iirc, listdatasource.action is called by did_select, it you don't otherwise override it.

                mcriley821 1 Reply Last reply Reply Quote 0
                • mcriley821
                  mcriley821 @JonB last edited by

                  @JonB @cvp
                  Thank you for the suggestions! I ended up going the easy way and requiring to pass in the master view. Kept the TableView hidden on the master (and thus un-touchable) until the combobox is touched.

                  1 Reply Last reply Reply Quote 0
                  • First post
                    Last post
                  Powered by NodeBB Forums | Contributors