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.
Need help with UI, tableviewcell_for_row
-
Hi all,
Any ideas on recreating the UI seen in Apple Mail rules (attached) or similar(smart mailboxes, etc)? i.e. Expandable table with multiple dropdowns in each row.I am populating tableviewcell_for_row with custom UIView class and other standard elements. The layout has been difficult to define and dropdown selections are restricted to row height. I think I can make it work but is not pretty.
Is there a more elegant way to approach this?
KP
-
@crazyfox you could try this script' or this one but I don't remember from where I get it
# coding: utf-8 ''' DropDown A custom view that acts as a flyout selector, based on a ListView and button. It does not allow text input, as does jsbain's version. Author: Steven Pollack, Ph.D. Date: July 26, 2015 ''' import ui,inspect,console import _ui # needed for Beta 1.6 class _DropDownDelegate(object): def setitems(self,values): if not values: self.items = [] if not isinstance(values[0],dict): self.items = [{'title':x, 'accessory_type':'none'} for x in values] else: self.items = [] for item in values: if 'title' not in list(item.keys()): raise ValueError("invalid dictionary. missing critical key") else: item['accessory_type']='none' # just in case its not there. self.items.append(item) self._row = 0 def __init__(self,dd): self.dd = dd self.setitems(dd._data) def tableview_number_of_rows(self, tableview, section): # Return the number of rows in the section return len(self.items) def tableview_cell_for_row(self, tableview, section, row): # Create and return a cell for the given section/row cell = ui.TableViewCell() cell.text_label.text = self.items[row]['title'] return cell def tableview_did_select(self, tableview, section, row): # Called when a row was selected. def animate(): self.dd.tv.frame = self.dd.tvFrame self.dd.frame = tuple([self.dd.frame[x] for x in (0,1)]) + (self.dd.frame[2], self.dd.smallSize) self._row = row for i,_ in enumerate(self.items): self.items[i]['accessory_type'] = 'none' self.items[row]['accessory_type'] = 'checkmark' tableview.content_offset = (0,row*tableview.row_height+self.dd.offset_eps) tableview.reload_data() self.dd.expanded = False ui.animate(animate,duration=0.2) if self.dd.action: self.dd.action(self.dd,row) class DropDown(ui.View): def __init__(self, frame=(0,0,150,32), buttonSize = (32,32), data = "this is a test".split(), font = None, initialItem = 0, offset_eps = 0, action = None, fullSize = 300, name = 'dropdown'): self.frame = frame self._position = [ self.frame[x] for x in (0,1)] self.smallSize = frame[3] self.bg_color = None self.border_width = 0 self.border_color = 'black' self.buttonSize = buttonSize self._data = data self.delegate = _DropDownDelegate(self) if action: if inspect.isfunction(action) and len(inspect.getargspec(action).args) == 2: self.action = action else: raise TypeError('single argument function') self.tvFrame = (0,0, self.frame[2] - self.buttonSize[0], self.buttonSize[1]) self.tv = ui.TableView(frame=self.tvFrame) self.tv.row_height = self.smallSize self.tv.name = 'tableview' self.tv.allows_selection = True self.tv.delegate = self.tv.data_source = self.delegate self.tv.border_color = 'black' self.tv.border_width = 1 self.button = ui.Button(frame = (self.frame[2]-self.buttonSize[0], 0) + self.buttonSize) self.button.bg_color = 'white' self.button.name = 'button' self.button.action = self.onArrow self.button.border_width = 1 self.button.border_color = 'black' self.button.image=ui.Image.named('ionicons-arrow-down-b-24') self.expanded = False self.add_subview(self.tv) self.tv.frame = self.tvFrame self.add_subview(self.button) self.fullSize = fullSize self.smallSize = self.frame[3] self.offset_eps = offset_eps self.name = name self._hidden = False def send_to_back(self): self.tv.send_to_back() self.button.send_to_back() def bring_to_front(self): self.tv.bring_to_front() self.button.bring_to_front() def onArrow(self,button): #console.hud_alert("{}".format(self.expanded)) self.bring_to_front() if not self.expanded: self.frame = tuple([self.frame[x] for x in (0,1)]) + (self.frame[2],self.fullSize) self.tv.frame = tuple([self.tv.frame[x] for x in (0,1)]) + (self.tv.frame[2],self.fullSize) self.expanded = True else: self.frame = tuple([self.frame[x] for x in (0,1)]) + (self.frame[2],self.smallSize) self.tv.frame = tuple([self.tv.frame[x] for x in (0,1)]) + (self.tv.frame[2],self.smallSize) self.tv.content_offset = (0,self.row*self.tv.row_height+self.offset_eps) self.expanded = False @property def position(self): return self._position @position.setter def position(self,value): self._position = value self.frame = self._position + tuple([self.frame[x] for x in (2,3)]) @property def hidden(self): return self._hidden @hidden.setter def hidden(self,value): self._hidden = value self.button.hidden = value self.tv.hidden = value @property def data(self): return self._data @data.setter def data(self,value): self._data = value self.delegate.setitems(value) self.tv.reload_data() @property def row(self): return self.delegate._row @row.setter def row(self,value): if 0 <= value <= (len(self.delegate.items)-1): self.delegate._row = value self.tv.content_offset = (0,self.delegate._row*self.tv.row_height+self.offset_eps) @property def current(self): return self.delegate.items[self.delegate._row] if __name__ == '__main__': def setRow(): dd.row = 2 def test(sender,row): print("from test row={}".format(row)) print(sender.current) ddd = DropDown(data='Mary had a little lamb'.split(),action=test) dd = DropDown(data=[{'title':'fred'}, # will autopopulate 'accessory_type" entry {'title':'arthur', 'accessory_type':'none'}, {'title':'tina', 'accessory_type':'none', 'flag':'a'}], action=test) root = ui.View(frame = (0,0,1000,900)) root.bg_color = '#deff92' root.add_subview(ddd) dd.position = (50,50) root.present()
-
Thanks @cvp
This is what Iโve cobbled together (with inspiration from @cvp and @jonb.
Very crude.- Iโm trying to understand how to position UI elements in table view row.
- looks like row_height in main table has to tall enough for dropdown views and touch capture.
Iโm an absolute beginner, so any feedback is appreciated.
Thanks, KP[picture link] donโt know why I canโt do inline images. https://imgur.com/n4U3RCf
import ui, console,dialogs pyui_str = r''' [ { "nodes" : [ { "nodes" : [ ], "frame" : "{{21, 24}, {480, 287}}", "class" : "TableView", "attributes" : { "flex" : "WH", "data_source_items" : "Row 1\nRow 2\nRow 3", "name" : "tableview1", "frame" : "{{120, 110}, {200, 200}}", "data_source_number_of_lines" : 1, "class" : "TableView", "background_color" : "RGBA(1.0, 1.0, 1.0, 1.0)", "data_source_delete_enabled" : true, "data_source_font_size" : 18, "row_height" : 66, "uuid" : "BE2CD45E-DD3C-496F-98C3-93E1222A94AA" }, "selected" : false } ], "frame" : "{{0, 0}, {523, 405}}", "class" : "View", "attributes" : { "enabled" : true, "background_color" : "RGBA(1.000000,1.000000,1.000000,1.000000)", "tint_color" : "RGBA(0.000000,0.478000,1.000000,1.000000)", "border_color" : "RGBA(0.000000,0.000000,0.000000,1.000000)", "flex" : "" }, "selected" : false } ] ''' class MyDropDown(ui.View): def __init__(self, tableview, section, row, items=[], frame=(0,0,200,200), *args, **kwargs): super().__init__(*args, **kwargs) self.frame = frame self.border_width = 1 self.border_color = 'red' self.background_color='#f6feff' tf = ui.TextField(name='tf') tf.enabled = False tf.frame = (self.x,self.y,self.width-32,32) tf.border_width= 1 tf.corner_radius = 5 self.add_subview(tf) self.tf = tf b = ui.Button() b.frame = (tf.x+tf.width,self.y,32,32) b.image = ui.Image.named('iob:arrow_down_b_32') b.border_width = 1 b.corner_radius = 5 b.action = self.b_action self.add_subview(b) tv = ui.TableView() tv.frame = (self.x,tf.height,tf.width,self.height-32) tv.border_width = 1 tv.corner_radius = 5 tv.data_source = ui.ListDataSource(items=items) tv.height = min(24*3,24*len(items)) tv.row_height= 24 tv.delegate = self tv.hidden = True self.add_subview(tv) self.tv = tv def b_action(self,sender): self.h = self.height def showtable(): self.border_color = 'green' self.tv.hidden = False self.height = self.tv.height+32 ui.animate(showtable,.4) def tableview_did_select(self,tableview, section, row): # Called when a row was selected data = tableview.data_source.items[row] self.tf.text = data def hidetable(): self.border_color = 'red' self.height = self.h ui.animate(hidetable,.4) tableview.hidden = True def as_cell(self): c=ui.TableViewCell() self.frame=c.content_view.bounds c.content_view.add_subview(self) c.set_needs_display() return c class MainView(ui.View): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) v = ui.load_str(pyui_str) t = v['tableview1'] t.delegate = t.data_source = self t.row_height = 222 v.present() self.content_view = None self.tbl = t def tableview_number_of_rows(self, tableview, section): # Return the number of rows in the section #return len(self.items) return 2 def tableview_cell_for_row(self, tableview, section, row): # Create and return a cell for the given section/row if row==0: itemslist = ['as','sd','df'] elif row==1: itemslist = ['wer','qwe','ghj'] cell = ui.TableViewCell() cell = MyDropDown(tableview, section, row, frame=(100,0,200,100), items=itemslist).as_cell() seg = ui.SegmentedControl(name='segRL') seg.segments = ['R','L'] seg.selected_index = -1 seg.frame = (0,0,96,32) seg.action = self.seg_action cell.content_view.add_subview(seg) tf_stent = ui.TextField(name='tf_stent',placeholder='boo') tf_stent.enabled = True tf_stent.clear_button_mode = 'while_editing' tf_stent.frame = (500,0,60,32) tf_stent.border_width= 1 tf_stent.border_color='blue' tf_stent.corner_radius = 5 cell.content_view.add_subview(tf_stent) tfl = ui.TextField(name='tfl') tfl.enabled = True tfl.clear_button_mode = 'while_editing' tfl.frame = (seg.width+4,0,120,32) tfl.border_width= 1 tfl.corner_radius = 5 self.add_subview(tfl) self.tfl = tfl return cell def change_row_ht(ht=44): self.tbl.row_height = ht #redraw display self.tbl.set_needs_display() def seg_action(self, sender): sideRL = sender.segments[sender.selected_index] MainView()
-
@crazyfox first, replace load_str by load_view_str ๐
-
@crazyfox sincerely, I don't understand your
cell = ui.TableViewCell() cell = MyDropDown(tableview, section, row, frame=(100,0,200,100), items=itemslist).as_cell()
-
@crazyfox try to start with this
import ui, console,dialogs pyui_str = r''' [ { "nodes" : [ { "nodes" : [ ], "frame" : "{{21, 24}, {480, 287}}", "class" : "TableView", "attributes" : { "flex" : "WH", "data_source_items" : "Row 1\nRow 2\nRow 3", "name" : "tableview1", "frame" : "{{120, 110}, {200, 200}}", "data_source_number_of_lines" : 1, "class" : "TableView", "background_color" : "RGBA(1.0, 1.0, 1.0, 1.0)", "data_source_delete_enabled" : true, "data_source_font_size" : 18, "row_height" : 66, "uuid" : "BE2CD45E-DD3C-496F-98C3-93E1222A94AA" }, "selected" : false } ], "frame" : "{{0, 0}, {523, 405}}", "class" : "View", "attributes" : { "enabled" : true, "background_color" : "RGBA(1.000000,1.000000,1.000000,1.000000)", "tint_color" : "RGBA(0.000000,0.478000,1.000000,1.000000)", "border_color" : "RGBA(0.000000,0.000000,0.000000,1.000000)", "flex" : "" }, "selected" : false } ] ''' class MyDropDown(ui.View): def __init__(self, items=[], frame=(0,0,200,200), *args, **kwargs): super().__init__(*args, **kwargs) self.frame = frame self.border_width = 1 self.border_color = 'red' self.background_color='#f6feff' tf = ui.TextField(name='tf') tf.enabled = False tf.frame = (0,0,self.width-32,32) tf.border_width= 1 tf.corner_radius = 5 self.add_subview(tf) self.tf = tf b = ui.Button() b.frame = (tf.width,0,32,32) b.image = ui.Image.named('iob:arrow_down_b_32') b.border_width = 1 b.corner_radius = 5 b.action = self.b_action self.add_subview(b) tv = ui.TableView() tv.frame = (0,tf.height,tf.width,self.height-32) tv.border_width = 1 tv.corner_radius = 5 tv.data_source = ui.ListDataSource(items=items) tv.height = min(24*3,24*len(items)) tv.row_height= 24 tv.delegate = self tv.hidden = True self.add_subview(tv) self.tv = tv def b_action(self,sender): self.h = self.height def showtable(): self.border_color = 'green' self.tv.hidden = False self.height = self.tv.height+32 ui.animate(showtable,.4) def tableview_did_select(self,tableview, section, row): # Called when a row was selected data = tableview.data_source.items[row] self.tf.text = data def hidetable(): self.border_color = 'red' self.height = self.h ui.animate(hidetable,.4) tableview.hidden = True def as_cell(self): # unused ======================= c=ui.TableViewCell() self.frame=c.content_view.bounds c.content_view.add_subview(self) c.set_needs_display() return c class MainView(ui.View): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) v = ui.load_view_str(pyui_str) t = v['tableview1'] t.delegate = t.data_source = self t.row_height = 222 v.present() self.content_view = None self.tbl = t def tableview_number_of_rows(self, tableview, section): # Return the number of rows in the section #return len(self.items) return 2 def tableview_cell_for_row(self, tableview, section, row): # Create and return a cell for the given section/row if row==0: itemslist = ['as','sd','df'] elif row==1: itemslist = ['wer','qwe','ghj'] cell = ui.TableViewCell() dd1 = MyDropDown(frame=(100,0,200,32), items=itemslist) cell.content_view.add_subview(dd1) seg = ui.SegmentedControl(name='segRL') seg.segments = ['R','L'] seg.selected_index = -1 seg.frame = (0,0,96,32) seg.action = self.seg_action cell.content_view.add_subview(seg) tf_stent = ui.TextField(name='tf_stent',placeholder='boo') tf_stent.enabled = True tf_stent.clear_button_mode = 'while_editing' tf_stent.frame = (400,0,60,32) tf_stent.border_width= 1 tf_stent.border_color='blue' tf_stent.corner_radius = 5 cell.content_view.add_subview(tf_stent) dd2 = MyDropDown(frame=(464,0,200,32), items=itemslist) cell.content_view.add_subview(dd2) tfl = ui.TextField(name='tfl') tfl.enabled = True tfl.clear_button_mode = 'while_editing' tfl.frame = (seg.width+4,0,120,32) tfl.border_width= 1 tfl.corner_radius = 5 self.add_subview(tfl) self.tfl = tfl return cell def change_row_ht(ht=44): self.tbl.row_height = ht #redraw display self.tbl.set_needs_display() def seg_action(self, sender): sideRL = sender.segments[sender.selected_index] MainView()
-
@cvp Thank you for taking the time.
I think I was making it harder than it should be.@crazyfox first, replace load_str by load_view_str ๐
...I see it now. Editing/posting error trying include pyui string.
@crazyfox sincerely, I don't understand your
cell = ui.TableViewCell() cell = MyDropDown(tableview, section, row, frame=(100,0,200,100), items=itemslist).as_cell()
...This evolved out of series of TypeErrors and issues with arguments I could not fix elegantly.
Much more to learn. Thanks again for your help.
Until next mental block.
-KP