One of the most powerful features of Xcode storyboards is the implementation of table views through prototype table cells. Prototype cells allow the developer to visually design the user interface elements that will appear in a table cell (such as labels, images, etc.) and then replicate that prototype cell on demand within the table view of the running app. Before the introduction of Storyboards, this would have involved considerable coding work combined with trial and error.
This chapter aims to work through a detailed example to demonstrate dynamic table view creation within a storyboard using table view prototype cells. Once this topic has been covered, the next chapter (entitled Implementing iOS 17 TableView Navigation using Storyboards) will explore the implementation of table view navigation and data passing between scenes using storyboards.
Creating the Example Project
Start Xcode and create an iOS App project named TableViewStory with the Swift programming language option selected.
A review of the files in the project navigator panel will reveal that, as requested, Xcode has created a view controller subclass for us named ViewController. In addition, this view controller is represented within the Storyboard file, the content of which may be viewed by selecting the Main.storyboard file.
To fully understand how to create a Storyboard-based TableView app, we will start with a clean slate by removing the view controller added for us by Xcode. Within the storyboard canvas, select the View Controller scene to highlight it in blue, and press the Delete key on the keyboard. Next, select and delete the corresponding ViewController.swift file from the project navigator panel. Finally, select the option to move the file to the trash in the resulting panel.
At this point, we have a template project consisting solely of a storyboard file and the standard app delegate code file. We are ready to build a storyboard-based app using the UITableView and UITableViewCell classes.
Adding the TableView Controller to the Storyboard
From the user’s perspective, the entry point into this app will be a table view containing a list of tourist attractions, with each table view cell containing the attraction’s name and corresponding image. As such, we will need to add a Table View Controller instance to the storyboard file. Select the Main.storyboard file so that the canvas appears in the center of the Xcode window. Display the Library panel, drag a Table View Controller object, and drop it onto the storyboard canvas as illustrated in Figure 28-1:
With the new view controller selected in the storyboard, display the Attributes Inspector and enable the Is initial View Controller attribute, as shown in Figure 28-2. Without this setting, the system will not know which view controller to display when the app launches.
Within the storyboard, we now have a table view controller instance. Within this instance is a prototype table view cell that we can configure to design the cells for our table. At the moment, these are generic UITableViewCell and UITableViewController classes that do not give us much in the way of control within our app code. So that we can extend the functionality of these instances, we need to declare them as subclasses of UITableViewController and UITableViewCell, respectively. Before doing so, however, we need to create those subclasses.
Creating the UITableViewController and UITableViewCell Subclasses
We will declare the Table View Controller instance within our storyboard as being a subclass of UITableViewController named AttractionTableViewController. Currently, this subclass does not exist within our project, so we need to create it before proceeding. To achieve this, select the File -> New -> File… menu option, and in the resulting panel, select the option to create a new iOS Source Cocoa Touch class. Click Next and on the subsequent screen, name the class AttractionTableViewController and change the Subclass of menu to UITableViewController. Ensure that the Also create XIB file option is turned off and click Next. Select a location into which to generate the new class files before clicking the Create button.
Within the Table View Controller added to the storyboard in the previous section, Xcode also added a prototype table cell. Later in this chapter, we will add a label and an image view object to this cell. To extend this class, it is necessary to, once again, create a subclass. Perform this step by selecting the File -> New -> File… menu option. Within the new file dialog, select Cocoa Touch Class and click Next. On the following screen, name the new class AttractionTableViewCell, change the Subclass of menu to UITableViewCell and proceed with the class creation. Select a location into which to generate the new class files and click on Create.
Next, the items in the storyboard need to be configured to be instances of these subclasses. Begin by selecting the Main.storyboard file and the Table View Controller scene, highlighting it in blue. Within the Identity Inspector panel, use the Class drop-down menu to change the class from UITableViewController to AttractionTableViewController, as illustrated in Figure 28-3:
Similarly, select the prototype table cell within the table view controller storyboard scene and change the class from UITableViewCell to the new AttractionTableViewCell subclass.
With the appropriate subclasses created and associated with the objects in the storyboard, the next step is to design the prototype cell.
Declaring the Cell Reuse Identifier
Later in the chapter, some code will be added to the project to replicate instances of the prototype table cell. This will require that the cell be assigned a reuse identifier. With the storyboard still visible in Xcode, select the prototype table cell and display the Attributes Inspector. Within the inspector, change the Identifier field to AttractionTableCell:
Designing a Storyboard UITableView Prototype Cell
Table Views are made up of multiple cells, each of which is either an instance of the UITableViewCell class or a subclass thereof. A helpful feature of storyboarding allows the developer to visually construct the user interface elements that are to appear in the table cells and then replicate that cell at runtime. For this example, each table cell needs to display an image view and a label which, in turn, will be connected to outlets that we will later declare in the AttractionTableViewCell subclass. Much like any other Interface Builder layout, components may be dragged from the Library panel and dropped onto a scene within the storyboard. With this in mind, drag a Label and an Image View object onto the prototype table cell. Resize and position the items so that the cell layout resembles that illustrated in Figure 28-5, making sure to stretch the label object to extend toward the right-hand edge of the cell.
Select the Image View and, using the Auto Layout Add New Constraints menu, set Spacing to nearest neighbor constraints on the view’s top, left, and bottom edges with the Constrain to margins option switched off. Before adding the constraints, also enable the Width constraint.
Select the Label view, display the Auto Layout Align menu and add a Vertically in Container constraint to the view. With the Label still selected, display the Add New Constraints menu and add a Spacing to nearest neighbor constraint on the left and right-hand edges of the view with the Constrain to margins option off.
Having configured the storyboard elements for the table view portion of the app, it is time to begin modifying the table view and cell subclasses.
Modifying the AttractionTableViewCell Class
Within the storyboard file, a label and an image view were added to the prototype cell, which, in turn, has been declared as an instance of our new AttractionTableViewCell class. We must establish two outlets connected to the objects in the storyboard scene to manipulate these user interface objects from within our code. Begin, therefore, by selecting the image view object, displaying the Assistant Editor, and making sure that it is displaying the content of the AttractionTableViewCell.swift file. If it is not, use the bar across the top of the Assistant Editor panel to select this file:
Ctrl-click on the image view object in the prototype table cell and drag the resulting line to a point just below the class declaration line in the Assistant Editor window. Release the line and use the connection panel to establish an outlet named attractionImage. Repeat these steps to establish an outlet for the label named attractionLabel.
Creating the Table View Datasource
Dynamic Table Views require a datasource to display the user’s data within the cells. By default, Xcode has designated the AttractionTableViewController class as the datasource for the table view controller in the storyboard. Consequently, within this class, we can build a simple data model for our app consisting of several arrays. The first step is to declare these as properties in the AttractionTableViewController.swift file:
import UIKit
class AttractionTableViewController: UITableViewController {
var attractionImages = [String]()
var attractionNames = [String]()
var webAddresses = [String]()
.
.
Code language: Swift (swift)
In addition, the arrays need to be initialized with some data when the app has loaded, making the viewDidLoad method an ideal location. Remaining within the AttractionTableViewController.swift file, add a method named initialize and call it from the viewDidLoad method as outlined in the following code fragment:
override func viewDidLoad() {
super.viewDidLoad()
initialize()
}
func initialize() {
attractionNames = ["Buckingham Palace",
"The Eiffel Tower",
"The Grand Canyon",
"Windsor Castle",
"Empire State Building"]
webAddresses = ["https://en.wikipedia.org/wiki/Buckingham_Palace",
"https://en.wikipedia.org/wiki/Eiffel_Tower",
"https://en.wikipedia.org/wiki/Grand_Canyon",
"https://en.wikipedia.org/wiki/Windsor_Castle",
"https://en.wikipedia.org/wiki/Empire_State_Building"]
attractionImages = ["buckingham_palace.jpg",
"eiffel_tower.jpg",
"grand_canyon.jpg",
"windsor_castle.jpg",
"empire_state_building.jpg"]
tableView.estimatedRowHeight = 50
}
Code language: Swift (swift)
In addition to initializing the arrays, the code also sets an estimated row height for the table view. This will prevent the row heights from collapsing when table view navigation is added later in the tutorial and also improves the performance of the table rendering.
Several methods must be implemented for a class to act as the datasource for a table view controller. The table view object will call these methods to obtain information about the table and the table cell objects to display. When we created the AttractionTableViewController class, we specified that it was to be a subclass of UITableViewController. As a result, Xcode created templates of these data source methods for us within the AttractionTableViewController.swift file. To locate these template datasource methods, scroll down the file until the // MARK: – Table view data source marker comes into view. The first template method, named numberOfSections, must return the number of sections in the table. For this example, we only need one section, so we will return a value of 1 (also note that the #warning line needs to be removed):
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
Code language: Swift (swift)
The next method is required to return the number of rows to be displayed in the table. This is equivalent to the number of items in our attractionNames array, so it can be modified as follows:
override func tableView(tableView: UITableView,
numberOfRowsInSection section: Int) -> Int {
return attractionNames.count
}
Code language: Swift (swift)
The above code returns the count property of the attractionNames array object to obtain the number of items in the array and returns that value to the table view.
The final datasource method that needs to be modified is tableView(_:cellForRowAt:). Each time the table view controller needs a new cell to display, it will call this method and pass through an index value indicating the row for which a cell object is required. This method’s responsibility is to return an instance of our AttractionTableViewCell class and extract the correct attraction name and image file name from the data arrays based on the index value passed through to the method. The code will then set those values on the appropriate outlets on the AttractionTableViewCell object. Begin by removing the comment markers (/* and */) from around the template of this method and then re-write the method so that it reads as follows:
override func tableView(_ tableView: UITableView,
cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell =
self.tableView.dequeueReusableCell(withIdentifier:
"AttractionTableCell", for: indexPath)
as! AttractionTableViewCell
let row = indexPath.row
cell.attractionLabel.font =
UIFont.preferredFont(forTextStyle: UIFont.TextStyle.headline)
cell.attractionLabel.text = attractionNames[row]
cell.attractionImage.image = UIImage(named: attractionImages[row])
return cell
}
Code language: Swift (swift)
Before proceeding with this tutorial, we need to take some time to deconstruct this code to explain what is happening.
The code begins by calling the dequeueReusableCell(withIdentifier:) method of the table view object passing through the cell identifier assigned to the cell (AttractionTableCell) and index path as arguments. The system will find out if an AttractionTableViewCell cell object is available for reuse or create a new one and return it to the method:
let cell =
self.tableView.dequeueReusableCell(withIdentifier:
"AttractionTableCell", for: indexPath)
as! AttractionTableViewCell
Code language: Swift (swift)
Having either created a new cell or obtained an existing reusable cell, the code simply uses the outlets previously added to the AttractionTableViewCell class to set the label with the attraction name, using the row from the index path as an index into the data array. Because we want the text displayed on the label to reflect the preferred font size selected by the user within the Settings app, the font on the label is set to use the preferred font for headline text.
The code then creates a new UIImage object configured with the image of the current attraction and assigns it to the image view outlet. Finally, the method returns the modified cell object to the table view:
let row = indexPath.row
cell.attractionLabel.font =
UIFont.preferredFont(forTextStyle: UIFont.TextStyle.headline)
cell.attractionLabel.text = attractionNames[row]
cell.attractionImage.image = UIImage(named: attractionImages[row])
return cell
Code language: Swift (swift)
Downloading and Adding the Image Files
Before a test run of the app can be performed, the image files referenced in the code need to be added to the project. An archive containing the images may be found in the attractionImages folder of the code sample archive, which can be downloaded from the following URL:
https://www.ebookfrenzy.com/web/ios16/
Select the Assets entry in the Project Navigator panel, Ctrl-click in the left-hand panel of the asset catalog screen, and select the Import… menu option. Then, navigate to and select the attractionImages folder in the file selection dialog and click on the Open button to import the images into a new image set.
Compiling and Running the App
Now that the storyboard work and code modifications are complete, the next step in this chapter is to run the app by clicking on the run button in the Xcode toolbar. Once the code has been compiled, the app will launch and execute within an iOS Simulator session, as illustrated in Figure 28-7.
Clearly, the table view has been populated with multiple instances of our prototype table view cell, each of which has been customized through outlets to display different text and images. Note that the self-sizing rows feature has automatically caused the rows to size to accommodate the attraction images.
Verify that the preferred font size code works by running the app on a physical iOS device, displaying Settings -> Display & Brightness -> Text Size, and dragging the slider to change the font size. If using the Simulator, use the slider on the Settings -> Accessibility -> Larger Text Size screen. Stop and restart the app and note that the attraction names are now displayed using the newly selected font size.
Handling TableView Swipe Gestures
The final task in this chapter is to enhance the app so that the user can swipe left on a table row to reveal a delete button that will remove that row from the table when tapped. This will involve the addition of a table view trailing swipe delegate method to the AttractionTableViewController class as follows:
override func tableView(_ tableView: UITableView,
trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) ->
UISwipeActionsConfiguration? {
let configuration = UISwipeActionsConfiguration(actions: [
UIContextualAction(style: .destructive, title: "Delete",
handler: { (action, view, completionHandler) in
let row = indexPath.row
self.attractionNames.remove(at: row)
self.attractionImages.remove(at: row)
self.webAddresses.remove(at: row)
completionHandler(true)
tableView.reloadData()
})
])
return configuration
}
Code language: Swift (swift)
The code within the method creates a new UISwipeActionsConfiguration instance configured with a destructive contextual action with the title set to “Delete”. Declaring the action as destructive ensures that the table view will remove the entry from the table when the user taps the Delete button. Although this will have removed the visual representation of the row in the table view, the entry will still exist in the data model, causing the row to reappear the next time the table reloads. To address this, the method implements a completion handler to be called when the deletion occurs. This handler identifies the swiped row and removes the matching data model array entries.
Build and run the app one final time and swipe left on one of the rows to reveal the delete option (Figure 28-8). Tap the delete button to remove the item from the list.
The next step, which will be outlined in the following chapter entitled Implementing iOS 17 TableView Navigation using Storyboards, will be to use the storyboard to add navigation capabilities to the app so that selecting a row from the table results in a detail scene appearing to the user.
Summary
The storyboard feature of Xcode significantly eases the process of creating complex table view-based interfaces within iOS apps. Arguably the most significant feature is the ability to visually design the appearance of a table view cell and then have that cell automatically replicated at run time to display information to the user in table form. iOS also includes support for automatic table cell sizing and the adoption of the user’s preferred font setting within table views. Finally, iOS also includes support for swipe interactions within table views. This chapter demonstrated the implementation of a destructive trailing swipe handler designed to allow the user to delete rows from a table view.