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.
Access TableViewCells
-
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...
-
And I‘m confused about how the superview of a cell is it‘s tableview, but the tableview doesn’t have any subviews
-
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
-
@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, thoughimport 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')
-
@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')
-
@Drizzel if you dont mind me asking.. what exactly are you doing?
-
@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.
-
@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.
-
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')
-
@JonB couldnt you use a user defined list which elements are all
TableViewCell
objects and then insidetableview_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
-
@stephen That’s how I understood him. And it worked out really well, after some minor adaptions