iOS Table
From Xojo Documentation
(Redirected from UserGuide:Working with Tables)Contents
A Table is used to display lists of data. Tables can display information in sections, can have "accessories" to indicate that more information is available. Each row of a Table can display images and up to 2 lines of text. In addition you can customize the rows of a table to show pretty much whatever you want.
If you have created desktop or web projects, you can think of an iOS Table as analogous to a single-column ListBox. An iOS Table can have a single column (called a cell) that contains a main line of text and optional smaller detail text underneath it. It can also have an image that displays in front of the cell. For more sophisticated display you can also create your own custom table cells to display almost anything you want, including multiple columns and controls.
Below is a list of commonly used events, properties and methods. Refer to iOSTable in the Language Reference for the complete list.
Events
- Called when the Detail accessory for a row is tapped.
- Called when a row is selected. You are provided the section and row that was tapped.
- Use this event to set up any row actions that are available for the row.
- Called when the user initiates a "pull-to-refresh" action.
- Called when a row action has been selected.
- Indicates the editing style for the row when the table is put into edit mode.
Properties
- Enable usage of the "pull-to-refresh" action for the table.
- Use this configure a Table to gets its data from a data source class rather than populating it using the methods.
- Indicates the table is currently in edit mode.
- Indicates how the table is displayed. There are two choices: Plain and Grouped.
- The number of sections in the Table. Use this in conjunction with the various methods to get row counts and other information about the table.
Visible
- A boolean that indicates if the Table is visible when your app runs.
Methods
- Adds new rows to the end or inserts rows at a specific position of the table.
- Add new sections to the end or inserts sections at a specific position of the table.
- Creates new cells (of type iOSTableCellData) to display in the table.
- Creates new custom cells that are made using iOSCustomTableCell.
ReloadData, ReloadDataInSection, ReloadRow
- Reloads all the data in the table, just the data for a specific section or a single row.
RemoveAll, RemoveRow, RemoveSection
- Removes all rows in the table, a single row in a section or an entire section.
- The count of rows in the specified section.
- Returns an instance of iOSTableCellData that contains all the information about a row in a section.
- Gets or sets the title of a section.
Cell Data
When working with a Table, you will need to manage its rows. The class iOSTableCellData contains information about a row in a table. To populate a table manually, you can create instances of iOSTableCellData (using iOSTable.CreateCell), set its properties and then add it to the Table. Or you can add rows using just a subset of values manually.
Use the RowData method to get the iOSTableCellData for a specific row, which you can then use or modify to change what is displayed.
Below are commonly used properties. Refer to iOSTableCellData in the Language Reference for the complete list.
Properties
- The type of (optional) accessory to display for the row. This uses the AccessoryTypes enumeration to choose the type of accessory: None, Disclosure, Detail, Checkmark.
- The Detail Text is smaller text that displays below the main text for the row.
- When an image is specified, it appears in front of the row Text.
- The Tag is an Auto that can be used to store any useful, related information about the cell for retrieval later (such as a primary key to a database or an instance of a class containing additional information).
- The main text that is displayed for the cell row.
Data Source
Although you can directly add rows to a Table using the AddRow methods, this is only practical for when there are a small number of rows. When you have 50 or more rows in the table you should consider using a Data Source to provide the data for the table to display. This makes much better use of the resources available on iOS.
iOSTableDataSource is an Interface that you implement on a class that will provide the data to the Table. The table is then populated by the class so you do not have to use the Add/Insert methods.
These are the methods of the interface that have to be implemented in a class:
- Returns the number of the rows in the specified section.
- This method is used to create the iOSTableCellData for the specified section and row.
- Returns the total number of sections.
- Returns the title for the specified section.
Usage
Starting simply, you can manually add rows to a table. A table always has to have at least one section, even if you don't display it. So at a minimum, you would have code like this to add a row to the first section:
Table1.AddRow(0, "First Row")
Using this version of the AddRow method only lets you provide the text for the row. If you need to set other row values, such as the DetailText or an Image, you will need to first create an iOSTableCellData, set its properties and then add it to the Table:
cell = Table1.CreateCell
cell.Text = "Product Name"
cell.DetailText = "Product Details"
cell.Image = ProductImage
Table1.AddRow(0, cell)
Insert works the same as Add but has an additional paramter to specify the row position (0-based) within the section for the newly inserted row.
Using these methods, you can populate a table by looping through the data. For example, if you have an array of customer names, you can loop through them and populate the Table like this:
Table1.AddSection("") // add heading for first letter of name
For i As Integer = 0 To CustomerNames.LastRowIndex
Table1.AddRow(0, CustomerNames(i))
Next
To add rows with accessories, you have to use iOSTableCellData because that is where the AccessoryType property is. This code adds rows to a table and sets the accessory to a checkmark:
Table1.AddSection("")
For i As Integer = 0 To 50
cellData = Table1.CreateCell
cellData.Text = "Line " + i.ToText
cellData.AccessoryType = iOSTableCellData.AccessoryTypes.Checkmark
Table1.AddRow(0, cellData)
Next
To toggle the checkmark on and off when the cell is tapped, you can put this code in the Table's Action event handler:
cell = Me.RowData(section, row)
If cell.AccessoryType = iOSTableCellData.AccessoryTypes.Checkmark Then
cell.AccessoryType = iOSTableCellData.AccessoryTypes.None
Me.ReloadRow(section, row)
Else
cell.AccessoryType = iOSTableCellData.AccessoryTypes.Checkmark
Me.ReloadRow(section, row)
End If
This code gets the selected cell and tests if the accessory is a checkmark. If it is, then it sets the accessory to None. If there is no accessory, then it sets it to a checkmark. It then reloads the changed row to show the change to the accessory.
The AccessoryTypes enumeration has other accessories you can use instead: Disclosure, Detail and None. The default for cells is None.
If you use an AccessoryType of Detail then tapping the detail indicator calls the Table's AccessoryAction event. In this event you can put the code that performs an accessory-specific action, which is typically to display a new View with additional details. One technique is to have the additional details stored in the Tag of the cell so that you can get it to send to the new View like this:
The RowData method is used to get the details of a cell for a specific row. You can then use the data or you can modify it to change what is displayed. After modifying cell data, you'll need to reload the row (or rows) that were modified. This Action event code changes the text of a row that was tapped:
cell = Me.RowData(section, row)
cell.DetailText = "This row was tapped."
Me.ReloadRow(section, row)
To display an image you also have to use iOSTableCellData. This adds a row using an image from the project:
cell = Table1.CreateCell
cell.Image = Product1Image
cell.Text = "Product 1"
Table1.AddRow(0, cell)
Sections can be used to provide ways to group the displayed data. One example might be to group a list of names by the first character of their name. So all names that start with "D" would be in their own group. Assuming you have an array of names, you can sort the array and then populate the Table, creating a new group when the first letter of the name changes:
"Fred", "Greg", "Harry", "Henry")
Var lastSectionTitle As Text
Var sectionNum As Integer
For i As Integer = 0 To names.LastRowIndex
If names(i).Left(1) <> lastSectionTitle Then
lastSectionTitle = names(i).Left(1)
sectionNum = Table1.AddSection(lastSectionTitle)
End If
Table1.AddRow(sectionNum, names(i))
Next
Using a Data Source
In each of the above examples, the Table was populated manually using the various Add methods. Another way you can populate a Table is by using a class that implements the iOSTableDataSource interface as the DataSource. The class is then responsible for getting the data and returning it using the methods provided by the iOSTableDataSource interface. This is a bit more work to set up, but does provide a noticable performance boost for large numbers of rows and it's a nice separation of the Table UI and its data. The class you create can get the data from anywhere. It could have its data built-in, it could fetch data from a file, the Internet or a database.
To use a DataSource, you need to create a class that implements the iOSTableDataSource interface (refer to the UserGuide:Interfaces section if you need to re-familiarize yourself with this). In your class, you have to implement the four interface methods: RowCount, RowData, SectionCount and SectionTitle.
As an exercise, this is how you would create a Data Source class to populate the names in a section as shown previously.
First, create a new class called "NamesData" and set add the iOSTableDataSource interface to it. In this new class, add two properties:
Add a Constructor to the class and add the code to initialize these properties:
"Fred", "Greg", "Harry", "Henry")
Var lastSectionTitle As Text
Var sectionNum As Integer
For i As Integer = 0 To names.LastRowIndex
If Names(i).Left(1) <> lastSectionTitle Then
lastSectionTitle = Names(i).Left(1)
SectionNames.AddRow(Names(i).Left(1))
End If
Next
Now you can add the code to the four methods to return the correct information.
This is the code for the SectionCount method:
This is the code for the SectionTitle method:
This is the code for the RowCount method:
// of the specified section.
Var count As Integer
For i As Integer = 0 To Names.LastRowIndex
If Names(i).Left(1) = SectionNames(section) Then
count = count + 1
End If
Next
Return count
This is the code for the RowData method:
Var lookupRow As Integer
For i As Integer = 0 To Names.lastRowIndex
If Names(i).Left(1) = SectionNames(section) Then
// Found the start of this section, which now
// serves as an offset into the array.
lookupRow = i
Exit For
End If
Next
Var cell As New iOSTableCellData
cell.Text = Names(lookupRow + row)
Return cell
This sets up the Data Source. Yes, it is a bit more complicated, but it eliminates the need for any code on the View to populate the Table and it will be much faster when there are a lot of rows. To set up the Table, go to View1 and add a Table (Table1). Also add a property:
In the Open event handler for the View, create a new instance of NamesData and assign it to the DataSource for the Table:
Table1.DataSource = Names
You may be wondering why it is necessary to have the Names property to store the NamesData instance. This is so that the instance does not go out of scope, preventing its data from being loaded into the table.
Now when the table is displayed, it will request only the rows that need to be shown from the data source.
Loading Data Manually
Loading data manually means that you are creating the sections and rows yourself to supply the data for the table. Before you can add any row to an iOSTable, it must first have a section. Sections provide a way for the data to appear in groups. As an example, if you look at the Contacts app in iOS you'll see that there are sections for each letter of the alphabet and the contacts are grouped by the letter they start with.
In its simplest form, you may not want to show any sections so you can just add an empty section:
Once you have added a section, you can add the rows using the AddRow method. This method requires that you tell it the section, which is why you must have a section to start with. Sections are 0-based so the first section is 0. This adds 50 rows to the table:
For i As Integer = 1 To 50
ExampleTable.AddRow(0, "Line " + i.ToText)
Next
Example Project: iOS/Controls/Table/SimpleTableExample
More Loading Data From a Data Source
Although loading data manually using the AddRow method is quick and easy, performance can really start to suffer once a lot ( > 100) of rows have been added. So in most cases you are going to want to use a separate data source to provide the data for the table to display. To provide a data source, you create a class that implements the iOSTableDataSource interface and then use its methods to provide data from whatever your source of data is. This data can be in a file, a database, text or anything else you want.
The example below displays MLB teams using a data source.
The first thing to do is to create a new class (named BaseballTeams). In the Inspector, click the Choose button for the Interfaces property and select the iOSTableDataSource interface. This will add these methods to your class:
- RowCount
- RowData
- SectionCount
- SectionData
But before you add any code to those methods, you first want to set up the actual data you will display. There are 30 MLB teams, separated into six divisions. A simple way to manage this data is to use a Dictionary for the divisions and then for each division in the Dictionary, store an array of the team names. To start, create a property on the class:
Now add a Constructor method with the code to populate the Dictionary:
Var ALEast() As Text = Array("Red Sox", "Yankees", "Rays", "Orioles", "Blue Jays")
Var ALCentral() As Text = Array("Tigers", "White Sox", "Twins", "Royals", "Indians")
Var ALWest() As Text = Array("Angels", "Mariners", "Rangers", "Athletics", "Astros")
Var NLEast() As Text = Array("Mets", "Braves", "Nationals", "Marlins", "Phillies")
Var NLCentral() As Text = Array("Cardinals", "Brewers", "Reds", "Pirates", "Cubs")
Var NLWest() As Text = Array("Dodgers", "Pardres", "Giants", "Diamondbacks", "Rockies")
Divisions.Value("AL East") = ALEast
Divisions.Value("AL Central") = ALCentral
Divisions.Value("AL West") = ALWest
Divisions.Value("NL East") = NLEast
Divisions.Value("NL Central") = NLCentral
Divisions.Value("NL West") = NLWest
Now you'll want one other helper method to get the name for a specific section number. Create a new method:
And use this code:
Select Case section
Case 0
name = "AL East"
Case 1
name = "AL Central"
Case 2
name = "AL West"
Case 3
name = "NL East"
Case 4
name = "NL Central"
Case 5
name = "NL West"
End Select
Return name
With this all set up, you can now populate the interface methods. The RowCount method returns the number of rows in a specific section. This is the number of rows in the array that belongs to the Dictionary key for the section. You get it like this:
The SectionCount method returns the number of sections. This is simply the count of keys in the Dictionary:
The SectionTitle method returns the name for a specific section. You can use the GetSectionName method get the name:
Lastly, this leaves the RowData method. Here you provide the data to display in the form of an iOSTableCellData instance. This code gets the team name for the appropriate section and row and sets it as the Text for the cell data:
teams() = Divisions.Value(GetSectionName(section))
Var cell As iOSTableCellData = table.CreateCell
cell.Text = teams(row)
Return cell
And that's it to set up the data source. The last step is to add a Table to a View and tell it to use your new data source. On the View, you'll want to add a property to reference the data source:
And then in the Open event handler for the Table, you can create an instance of your data source and assign it to the table:
Now you can run the project and you'll see the MLB team names grouped by division.
Example Project: iOS/Controls/Table/TableDataSource
Getting the Selected Row
When a row in the table is tapped, the Action event handler is called passing in the section and row that were selected. Use the RowData method to get the data in the cell for the selected row:
Row Actions
Row actions are the buttons that appear when a user swipes left across a row of a table. For example, if you swipe left across an email message in the Mail app, you'll see "More", "Flag" and "Trash" buttons. Xojo row action buttons can only contain text (not icons) and are added using the iOSTableRowAction class in conjunction with the iOSTable.ActionsForRow event. When the user selects one of the buttons, then the iOSTable.RowAction event is called.
Row actions only works for tables that use the iOSTableDataSourceEditing interface. In the interface implementation you will load your data as described above and in the RowIsEditable method you'll need to Return True.
To set up row actions, you specify them in the ActionsForRow method. This code adds Flag and Archive actions for all rows:
Const kFlagTag As Text = "Flag"
Const kArchiveTag As Text = "Archive"
// Create the Flag button
Actions(0) = New iOSTableRowAction(iOSTableRowAction.Styles.Normal, "Flag", kFlagTag)
// Create the Archive button
Actions(1) = New iOSTableRowAction(iOSTableRowAction.Styles.Destructive, "Archive", kArchiveTag)
Return Actions
The "destructive" action appears on the far right and displays as red.
You then check which row action was selected in the RowAction event handler:
Select Case actionTag
Case "Flag"
// Call flag code
Case "Archive"
// Call archive code
End Select
The user can choose to "cancel" a row action by tapping anywhere but on one of the buttons.
Example Project: iOS/Controls/Table/TableActions
Row Editing and Re-Ordering
Both row editing and row re-ordering are enabled by putting the table into "editing" mode. You do this by setting the EditingEnabled property to True. Row editing displays "+" and "-" widgets to the left or each row that can be used to add and remove rows in the table. Row re-ordering displays a "drag" control to the right of each row that you can drag around to move the row in the table. Putting the table into edit mode is simple. You'll probably have an "Edit" button on the Navigation toolbar (called EditButton) with code like this to turn editing on an off:
Case EditButton
If SampleTable.EditingEnabled Then
SampleTable.EditingEnabled = False
EditButton.Caption = "Edit"
Else
SampleTable.EditingEnabled = True
EditButton.Caption = "Done"
End If
End Select
Row Editing is supported by using the iOSTableDataSourceEditing data source interface and row re-ordering uses the iOSTableDataSourceReordering interface. This means that in order to use these two features, you table must be populated from a data source. Both of these two additional interfaces aggregate (or extend) the iOSTableDataSource interface, so several methods are shared. And remember, you can select multiple interfaces for your data source class. So to support a data source, row editing and row re-ordering, you can select these three interfaces in the interface selector for your class: iOSTableDataSource, iOSTableDataSourceEditing, iOSTableDataSourceReordering.
Technically, because iOSTableDataSouceEditing and iOSTableDataSourceReordering "aggregate" iOSTableDataSource, you do not actually have to separately select iOSTableDataSource. But you may want to do so for clarity.
Row Editing
The iOSTableDataSourceEditing interface adds two additional methods to the iOSTableDataSource interface that you need to implement: RowIsEditable and RowEditingCompleted.
Before worrying about the interface implementation, you need to specify the editing style that appears for each row when the table is put into edit mode. You do this by returning an iOSTable.RowEditingStyles enum from the iOSTable.RowEditingStyle event handler. This code sets all rows to display the deletion control when the table is edited:
Moving on to the interface implemention, you want to Return True from RowIsEditable to allow the row to display the editing controls when the table is put into edit mode:
Because this method supplies the section and row numbers as parameters, you can choose to only allow row editing for specific sections and rows by only returning True for those that match your criteria. For example, this only enables row editing for rows in section 2:
Also supplied to the method is a reference to the table itself. You can use this to look up the contents of the row to determine if it contains something you want to allow to be deleted. For example, you may want to add a tag value to some of the cell data to indicate that it is default data and should not be deleted.
When the users selects one of the editing buttons to delete the row, then the RowEditingCompleted method is called in your class. Use the iOSTable.RowEditingStyles enum to determine which button was selected. This code checks if the row was selected to be deleted and then removes it from the data source:
Data.Remove(row)
table.RemoveRow(section, row)
End If
It is important to realize that you have to specifically remove the row from your data source. In the above code that means removing it from the array containing the data, but if you are using something else (such as a database), you'll want to remove the row as appropriate. You also have to specifically remove the row from being displayed in the table by calling its RemoveRow method as shown in the above code.
Example Project: iOS/Controls/Table/TableEditing
Row Re-Ordering
Row re-ordering works similarly to row editing. You add the iOSTableDataSourceReordering interface to your data source class which adds two new methods: RowIsMoveable and RowMoved.
In the RowIsMoveable method, you return True for the rows you want to allow to be moved. In most cases you'll probably want to allow all rows to be moved:
The RowMoved event handler is called after a user drags a row to move it and then drops it into its new position. In this method, you need to update your data source to reflect the new state of the data. The table UI is automatically updated for you, so you may be deceived into thinking you don't need to do any other work. But it is important that your data source is properly updated to reflect the new ordering. This code uses an array (a property called Data) as the source of the data that is displayed in the table. It removes the item from its original location in the array and then inserts the item at its new position so that the array continues to match the order of the data that is now displayed in the table:
Var origValue As Text = Data(sourceRow)
Data.Remove(sourceRow)
// Add it to its new location in the Data array
Data.Insert(destRow, origValue)
Example Project: iOS/Controls/Table/TableEditing
Custom Cell Data
To really get fancy with what you display in your tables, you'll want to create custom cells. With a custom cell, you can put any control you want into a cell. To do this you create an iOSCustomTableCell with the actual controls you want displayed in each cell. Then you create a custom cell for the table, supplying the custom table cell class. iOSCustomTableCell is a subclass of iOSContainerControl and allows you to add and layout any controls you want using the Layout Editor.
Custom cells are incredibly powerful. You can create multiple columns in a table by creating a column with several labels lined up in a column. Using a label also allows you to change the font, size and other properties of the displayed text. But you don't have to stick with simple text. You can also add UI controls such as Switches, Buttons or anything else you want to show.
For best results you will really want to use a DataSource to populate the table. You can get away with adding the rows yourself manually, but only if your table has just a few rows.
When using a data source, it is important to understand that the instances of the container are re-used as necessary. This means that you want to be sure to properly set (or reset) all the values of the container each time you set up the RowData. For example, if you change the font for a label in one container, but do not reset it, then that changed font could end up being used for a completely different set of data that is displayed. Below is an example that updates the MLB team data example from above to use custom table cells.
The first thing to do before writing any code is to create the custom table cell itself. Go to the Library and drag an iOSCustomTableCell to the Navigator to add a subclass to your project.
Instead of dragging Custom Table Cell from the Library, you can add a Container Control to your project and change its Super to iOSCustomTableCell.
Change its name to TeamTableCell. As this is a subclass of iOSContainerControl, you'll see a Layout Editor where you can place controls. For the purposes of this example, you'll add a single Label (TeamNameLabel) to display the team name, a Button (TestButton) to display a cheer for the team and an ImageView (LogoImage) to just show some simple graphics.
Your layout might look like this:
Now you can go to the RowData method on the BaseballTeams data source class your created in the data source example. Get rid of the existing code that is there and replace it with this code that adds a team custom cell to the table, assigning the name to the label and setting the TeamLogo to a simple filled oval:
teams() = Divisions.Value(GetSectionName(section))
Var cell As iOSTableCellData = table.CreateCustomCell(GetTypeInfo(TeamTableCell))
Var container As TeamTableCell = TeamTableCell(cell.Control)
container.TeamNameLabel.Text = teams(row)
// Draw a blue oval
Var logo As New iOSBitmap(64, 64, 1.0)
logo.Graphics.FillColor = &c0000ff
logo.Graphics.FillOval(10, 10, 50, 50)
container.TeamLogo.Image = logo.Image
Return cell
The trickiest part of this code is that you have to use GetTypeInfo to pass in the type of your class. You do this rather than passing in an instance of the class because the instances are re-used by the table to make things more efficient.
This example showed you how to use a few controls in a iOSCustomTableCell to display whatever you want in your iOSTables. Remember, you can use any control you want. The table will resize to fit the controls in your layout.
If your custom table cells have different sizes you can set the iOSCustomTableCell.UseDynamicHeight property to true so that the table resizes the row as necessary.
Example Projects: iOS/Controls/Table/CustomCellsAndScroll, CustomCellDynamicHeight
Example Projects
These example projects demonstrate various features of iOS Tables:
- Examples/iOS/Controls/Table/CustomCellDynamicHeight
- Examples/iOS/Controls/Table/CustomCellsAndScroll
- Examples/iOS/Controls/Table/GroupTableExample
- Examples/iOS/Controls/Table/SectionTableExample
- Examples/iOS/Controls/Table/SelectableTable
- Examples/iOS/Controls/Table/SimpleTableExample
- Examples/iOS/Controls/Table/TableActions
- Examples/iOS/Controls/Table/TableCellSizes
- Examples/iOS/Controls/Table/TableCheckmark
- Examples/iOS/Controls/Table/TableDataSource
- Examples/iOS/Controls/Table/TableDetail
- Examples/iOS/Controls/Table/TableDisclosure
- Examples/iOS/Controls/Table/TableEditing
- Examples/iOS/Controls/Table/TableDataSourceDatabase
- Examples/iOS/Apps/XojoNotes
- Examples/iOS/Database/SQLiteExample
- Examples/Sample Applications/EddiesElectronics/iOS/EEiOS
See Also
iOSTable, iOSTableCellData, iOSCustomTableCell, iOSTableRowAction classes; iOSTableDataSource, iOSTableDataSourceEditing, iOSTableDataSourceReordering interfaces; UserGuide:iOS UI topic