Forum Archive

Access TableViewCells

Drizzel

Is there an easy way to access the cells in a Tableview? And, more specifically, the currently selected cell?

For now, I modified the datasource to give every cell an attribute with itβ€˜s section and row, and then append them to an array. But cycling through this list to find the currently selected cell gets a bit inefficient with longer tables...

Drizzel

And Iβ€˜m confused about how the superview of a cell is itβ€˜s tableview, but the tableview doesn’t have any subviews

stephen

@Drizzel

tableview_cell_for_row inside your data_source πŸ€“πŸ˜Ž

class MyTableViewDataSource (object):
     def tableview_number_of_sections(self, tableview):
          # Return the number of sections (defaults to 1)
          return 1

     def tableview_number_of_rows(self, tableview, section):
          # Return the number of rows in the section
          return 0

     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 = 'Foo Bar'
          return cell

     def tableview_title_for_header(self, tableview, section):
          # Return a title for the given section.
          # If this is not implemented, no section headers will be shown.
          return 'Some Section'

     def tableview_can_delete(self, tableview, section, row):
          # Return True if the user should be able to delete the given row.
          return True

     def tableview_can_move(self, tableview, section, row):
          # Return True if a reordering control should be shown for the given row (in editing mode).
          return True

     def tableview_delete(self, tableview, section, row):
          # Called when the user confirms deletion of the given row.
          pass

     def tableview_move_row(self, tableview, from_section, from_row, to_section, to_row):
          # Called when the user moves a row with the reordering control (in editing mode).
          pass
Drizzel

@stephen Alright, thanks. I just thought that creating a new cell wasn’t β€želegantβ€œ, if you now what I mean :)

Edit:
Iβ€˜m pretty sure I’m missing something, though

import ui 

tv = ui.TableView(frame = (0,0,300,300))
tv.data_source = ui.ListDataSource([i for i in range(10)])
tv.data_source.tableview_cell_for_row(tv, 0, 2).background_color = 'red'

tv.present('sheet')

cvp

@Drizzel dirty but shortest

import ui 
def tableview_cell_for_row(tableview, section, row):
    # Create and return a cell for the given section/row
    cell = ui.TableViewCell()
    cell.text_label.text = str(tableview.data_source.items[row])
    selected_cell = ui.View()
    selected_cell.bg_color = 'red'
    cell.selected_background_view = selected_cell
    return cell
tv = ui.TableView(frame = (0,0,300,300))
tv.data_source = ui.ListDataSource(items=[i for i in range(10)])
tv.data_source.tableview_cell_for_row = tableview_cell_for_row
tv.present('sheet')
stephen

@Drizzel if you dont mind me asking.. what exactly are you doing?

shinyformica

@Drizzel the important bit here is that you aren't really supposed to directly access tableview cells by index, since in all likelihood, they don't exist.

This is because the way the underlying control is implemented is designed to only create cells when they need to be displayed, and it keeps only the ones it absolutely needs to keep (with some caching), it even reuses existing cells that have gone out of the visible area, instead of creating new ones.

That source.tableview_cell_for_row() is called to generate a cell for a particular index at the moment the view requires it, and it will be destroyed or reused, so you can't really depend on it being "kept" somewhere as a subview you can always access.

Now, you can actually use objc_util magic to get a cell at an index path, but the cell will be None if that index is invalid or not visible.

So the best thing to do if you want to somehow modify a cell (I'm guessing that's what you want here) is to implement tableview_cell_for_row() and make any modifications there for cells being created. If you need to update a visible cell, just call table.reload() and it will cause all visible cells to be re-created.

JonB

@Drizzel the way to think of tableview cells is that you never access them from other code. You populate it inside of cell_for_row -- basically only the cells currently on screen ever actually exist. It is a memory thing -- you could have a 100000 row table, but only 6 tableview cells ever exist at once.

Now, it is possible, if you create your own premade list of cells, to just have cell_for_row return the premade cell. Or, you could use the content_view if you have something expensive that you want to reuse, like, say, a webview.

Drizzel

Thanks, I thought TableViewCells would always exist, nevermind if they’re on screen or not. That explains why this example doesn’t work properly. I’m going to try the suggestion of @JonB


import ui

class MyTableViewDataSource (object):
    def __init__(self, data, **kwargs):
        #self.__dict__.update((k, v) for k, v in kwargs.items() if k in allowed_keys)
        self.__dict__.update(kwargs)
        self.items = data   
        self.previousCell = False
        self.currentCell = False
        self.cells = [[] for i in range(len(self.items))]

    def tableview_number_of_sections(self, tableview):
        return len(self.items)

    def tableview_number_of_rows(self, tableview, section):
        return len(self.items[section])

    def tableview_cell_for_row(self, tableview, section, row):
        data = tableview.data_source.items[section][row]
        cell = ui.TableViewCell('subtitle')
        cell.section, cell.row = section, row
        cell.text_label.text = data

        self.cells[section].append(cell)
        return cell

    def tableview_title_for_header(self, tableview, section):
        # Return a title for the given section.
        # If this is not implemented, no section headers will be shown.
        return 'section: '+str(section)

    def tableview_can_delete(self, tableview, section, row):
        # Return True if the user should be able to delete the given row.
        return False

    def tableview_can_move(self, tableview, section, row):
        # Return True if a reordering control should be shown for the given row (in editing mode).
        return False

    def tableview_delete(self, tableview, section, row):
        # Called when the user confirms deletion of the given row.
        pass

    def tableview_move_row(self, tableview, from_section, from_row, to_section, to_row):
        # Called when the user moves a row with the reordering control (in editing mode).
        pass

#–––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––

class BasicTableViewDelegate(object):
    def tableview_did_select(self, tableview, section, row):
        # Called when a row was selected.

        if tableview.data_source.currentCell:   
            if tableview.data_source.previousCell:
                tableview.data_source.previousCell.background_color = 'white'

            tableview.data_source.previousCell = tableview.data_source.currentCell
            del tableview.data_source.currentCell
            tableview.data_source.previousCell.background_color = '#eeeeee'


        #find current cell
        for cell in tableview.data_source.cells[section]: #dont like having to cycle through all cells
            if cell.row == row:
                tableview.data_source.currentCell = cell
                break

    def tableview_did_deselect(self, tableview, section, row):
        # Called when a row was de-selected (in multiple selection mode).
        pass

    def tableview_title_for_delete_button(self, tableview, section, row):
        # Return the title for the 'swipe-to-***' button.
        return 'Delete'

if __name__ == '__main__':

    section1 = [str(i) for i in range(10)]
    section2 = [str(i) for i in range(10)]

    tv = ui.TableView()
    tv.data_source = MyTableViewDataSource([section1, section2])
    tv.delegate = BasicTableViewDelegate()

    tv.frame = (0, 0, 300, 400)
    tv.present('sheet')


stephen

@JonB couldnt you use a user defined list which elements are all TableViewCell objects and then inside tableview_cell_for_row return that list at respective row index? giving access to cells even if the cells not presented?


class MyTableView(ui.View):
    def __init__(self, *args, **kwargs):
        self.tv=ui.TableView()
        self.cells=list()
        self.tv.delegate = self.tv.data_source = self


    def tableview_create_cell(self, **kwargs):
        cell = ui.TableViewCell()

        for k, v in kwargs.items():
            setattr(cell, k, v)

        self.cells.append(cell)
        return cell

    def tableview_did_select(self, tableview, section, row):
        pass

    def tableview_did_deselect(self, tableview, section, row):
        pass

    def tableview_title_for_delete_button(self, tableview, section, row):
        return 'Delete'

    def tableview_number_of_sections(self, tableview):
        return 1

    def tableview_number_of_rows(self, tableview, section):
        return len(self.cells)

    def tableview_cell_for_row(self, tableview, section, row):
        return self.cells[row]

    def tableview_title_for_header(self, tableview, section):
        return ''

    def tableview_can_delete(self, tableview, section, row):
        return True

    def tableview_can_move(self, tableview, section, row):
        return True

    def tableview_delete(self, tableview, section, row):
        self.cells.remove(self.cells[row])

    def tableview_move_row(self, tableview, from_section, from_row, to_section, to_row):
        pass

Drizzel

@stephen That’s how I understood him. And it worked out really well, after some minor adaptions