Forum Archive

Need help with UI, tableviewcell_for_row

crazyfox

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

https://imgur.com/qzsDEOO

cvp

@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()
crazyfox

Thanks @cvp
This is what I’ve cobbled together (with inspiration from @cvp and @jonb.
Very crude.
1) I’m trying to understand how to position UI elements in table view row.
2) 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()

cvp

@crazyfox first, replace load_str by load_view_str 😀

cvp

@crazyfox sincerely, I don't understand your

        cell = ui.TableViewCell()
        cell = MyDropDown(tableview, section, row, frame=(100,0,200,100), items=itemslist).as_cell() 
cvp

@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()

crazyfox

@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