19
Subclassing UITableViewCell

A UITableView displays a list of UITableViewCell objects. For many applications, the basic cell with its textLabel, detailTextLabel, and imageView is sufficient. However, when you need a cell with more detail or a different layout, you subclass UITableViewCell.

In this chapter, you will create a custom subclass of UITableViewCell named BNRItemCell that will display BNRItem instances more effectively. Each of these cells will show a BNRItem’s name, its value in dollars, its serial number, and a thumbnail of its image (Figure 19.1).

Figure 19.1  Homepwner with subclassed table view cells

Homepwner with subclassed table view cells

Creating BNRItemCell

UITableViewCell is a UIView subclass. When subclassing UIView (or any of its subclasses), you often override its drawRect: method to customize the view’s appearance. However, when subclassing UITableViewCell, you usually customize its appearance by adding subviews to the cell. You do not add them directly to the cell though; instead you add them to the cell’s content view.

Each cell has a subview named contentView, which is a container for the view objects that make up the layout of a cell subclass (Figure 19.2). When you subclass UITableViewCell, you often change its look and behavior by changing the subviews of the cell’s contentView. For instance, you could create instances of the classes UITextField, UILabel, and UIButton and add them to the contentView.

Figure 19.2  UITableViewCell hierarchy

UITableViewCell hierarchy

Adding subviews to the contentView instead of directly to the cell itself is important because the cell will resize its contentView at certain times. For example, when a table view enters editing mode the contentView resizes itself to make room for the editing controls (Figure 19.3). If you were to add subviews directly to the UITableViewCell, these editing controls would obscure the subviews. The cell cannot adjust its size when entering edit mode (it must remain the width of the table view), but the contentView can resize, and it does.

(By the way, notice the UIScrollView in the cell hierarchy? That is how iOS moves the contents of the cell to the left when it enters editing mode. You can also use a right-to-left swipe on a cell to show the delete control, and this uses that same scroll view to get the job done. It makes sense then that the contentView is a subview of the scroll view.)

Figure 19.3  Table view cell layout in standard and editing mode

Table view cell layout in standard and editing mode

Open Homepwner.xcodeproj. Create a new NSObject subclass and name it BNRItemCell.

In BNRItemCell.h, change the superclass to UITableViewCell.

@​i​n​t​e​r​f​a​c​e​ ​B​N​R​I​t​e​m​C​e​l​l​ ​:​ ​N​S​O​b​j​e​c​t​
@​i​n​t​e​r​f​a​c​e​ ​B​N​R​I​t​e​m​C​e​l​l​ ​:​ ​U​I​T​a​b​l​e​V​i​e​w​C​e​l​l​

Configuring a UITableViewCell subclass’s interface

The easiest way to configure a UITableViewCell subclass is with a XIB file. Create a new Empty XIB file and name this file BNRItemCell.xib. (The Device Family is irrelevant for this file.)

This file will contain a single instance of BNRItemCell. When the table view needs a new cell, it will create one from this XIB file.

In BNRItemCell.xib, select BNRItemCell.xib and drag a UITableViewCell instance from the object library to the canvas. (Make sure you choose UITableViewCell, not UITableView or UITableViewController.)

Select the Table View Cell in the outline view and then the identity inspector (the Configuring a UITableViewCell subclass’s interface tab). Change the Class to BNRItemCell (Figure 19.4).

Figure 19.4  Changing the cell class

Changing the cell class

A BNRItemCell will display three text elements and an image, so drag three UILabel objects and one UIImageView object onto the cell. Configure them as shown in Figure 19.5. Make the text of the bottom label a slightly smaller font and a dark shade of gray.

Figure 19.5  BNRItemCell’s layout

BNRItemCell’s layout

Exposing the properties of BNRItemCell

In order for BNRItemsViewController to configure the content of a BNRItemCell in tableView:cellForRowAtIndexPath:, the cell must have properties that expose the three labels and image view. These properties will be set through outlet connections in BNRItemCell.xib.

The next step, then, is to create and connect outlets on BNRItemCell for each of its subviews. You will use the same technique you have been using in the last few chapters – Control-dragging from the XIB file into the source file to create the outlets.

Option-click on BNRItemCell.h while BNRItemCell.xib is open. Control-drag from each subview to the method declaration area in BNRItemCell.h. Name each outlet and configure the other attributes of the connection as shown in Figure 19.6. (Pay attention to the Connection, Storage, and Object fields.)

Figure 19.6  BNRItemCell connections

BNRItemCell connections

Double-check that BNRItemCell.h looks like this:

@​i​n​t​e​r​f​a​c​e​ ​B​N​R​I​t​e​m​C​e​l​l​ ​:​ ​U​I​T​a​b​l​e​V​i​e​w​C​e​l​l​

@​p​r​o​p​e​r​t​y​ ​(​w​e​a​k​,​ ​n​o​n​a​t​o​m​i​c​)​ ​I​B​O​u​t​l​e​t​ ​U​I​I​m​a​g​e​V​i​e​w​ ​*​t​h​u​m​b​n​a​i​l​V​i​e​w​;​
@​p​r​o​p​e​r​t​y​ ​(​w​e​a​k​,​ ​n​o​n​a​t​o​m​i​c​)​ ​I​B​O​u​t​l​e​t​ ​U​I​L​a​b​e​l​ ​*​n​a​m​e​L​a​b​e​l​;​
@​p​r​o​p​e​r​t​y​ ​(​w​e​a​k​,​ ​n​o​n​a​t​o​m​i​c​)​ ​I​B​O​u​t​l​e​t​ ​U​I​L​a​b​e​l​ ​*​s​e​r​i​a​l​N​u​m​b​e​r​L​a​b​e​l​;​
@​p​r​o​p​e​r​t​y​ ​(​w​e​a​k​,​ ​n​o​n​a​t​o​m​i​c​)​ ​I​B​O​u​t​l​e​t​ ​U​I​L​a​b​e​l​ ​*​v​a​l​u​e​L​a​b​e​l​;​

@​e​n​d​

Note that you did not specify the File's Owner class or make any connections with it. This is a little different than your usual XIB files where all of the connections happen between the File's Owner and the archived objects. To see why, let’s see how cells are loaded into the application.

Using BNRItemCell

In BNRItemsViewController’s tableView:cellForRowAtIndexPath: method, you will create an instance of BNRItemCell for every row in the table.

In BNRItemsViewController.m, import the header file for BNRItemCell so that BNRItemsViewController knows about it.

#​i​m​p​o​r​t​ ​"​B​N​R​I​t​e​m​C​e​l​l​.​h​"​

Previously, you registered a class with the table view to inform it which class should be instantiated whenever it needs a new table view cell. Now that you are using a custom NIB file to load a UITableViewCell subclass, you will register that NIB instead.

In BNRItemsViewController.m, modify viewDidLoad to register BNRItemCell.xib for the "BNRItemCell" reuse identifier.

-​ ​(​v​o​i​d​)​v​i​e​w​D​i​d​L​o​a​d​
{​
 ​ ​ ​ ​[​s​u​p​e​r​ ​v​i​e​w​D​i​d​L​o​a​d​]​;​

 ​ ​ ​ ​[​s​e​l​f​.​t​a​b​l​e​V​i​e​w​ ​r​e​g​i​s​t​e​r​C​l​a​s​s​:​[​U​I​T​a​b​l​e​V​i​e​w​C​e​l​l​ ​c​l​a​s​s​]​
 ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​f​o​r​C​e​l​l​R​e​u​s​e​I​d​e​n​t​i​f​i​e​r​:​@​"​U​I​T​a​b​l​e​V​i​e​w​C​e​l​l​"​]​;​

 ​ ​ ​ ​/​/​ ​L​o​a​d​ ​t​h​e​ ​N​I​B​ ​f​i​l​e​
 ​ ​ ​ ​U​I​N​i​b​ ​*​n​i​b​ ​=​ ​[​U​I​N​i​b​ ​n​i​b​W​i​t​h​N​i​b​N​a​m​e​:​@​"​B​N​R​I​t​e​m​C​e​l​l​"​ ​b​u​n​d​l​e​:​n​i​l​]​;​

 ​ ​ ​ ​/​/​ ​R​e​g​i​s​t​e​r​ ​t​h​i​s​ ​N​I​B​,​ ​w​h​i​c​h​ ​c​o​n​t​a​i​n​s​ ​t​h​e​ ​c​e​l​l​
 ​ ​ ​ ​[​s​e​l​f​.​t​a​b​l​e​V​i​e​w​ ​r​e​g​i​s​t​e​r​N​i​b​:​n​i​b​
 ​ ​ ​ ​ ​ ​ ​ ​ ​f​o​r​C​e​l​l​R​e​u​s​e​I​d​e​n​t​i​f​i​e​r​:​@​"​B​N​R​I​t​e​m​C​e​l​l​"​]​;​
}​

The registration of a NIB for a table view is not anything fancy: the table view simply stores the UINib instance in an NSDictionary for the key "BNRItemCell". A UINib contains all of the data stored in its XIB file, and when asked, can create new instances of the objects it contains.

Once a UINib has been registered with a table view, the table view can be asked to load the instance of BNRItemCell when given the reuse identifier "BNRItemCell".

In BNRItemsViewController.m, modify tableView:cellForRowAtIndexPath:.

-​ ​(​U​I​T​a​b​l​e​V​i​e​w​C​e​l​l​ ​*​)​t​a​b​l​e​V​i​e​w​:​(​U​I​T​a​b​l​e​V​i​e​w​ ​*​)​t​a​b​l​e​V​i​e​w​
 ​ ​ ​ ​ ​ ​ ​ ​ ​c​e​l​l​F​o​r​R​o​w​A​t​I​n​d​e​x​P​a​t​h​:​(​N​S​I​n​d​e​x​P​a​t​h​ ​*​)​i​n​d​e​x​P​a​t​h​
{​
 ​ ​ ​ ​U​I​T​a​b​l​e​V​i​e​w​C​e​l​l​ ​*​c​e​l​l​ ​=​
 ​ ​ ​ ​ ​ ​ ​ ​[​t​a​b​l​e​V​i​e​w​ ​d​e​q​u​e​u​e​R​e​u​s​a​b​l​e​C​e​l​l​W​i​t​h​I​d​e​n​t​i​f​i​e​r​:​@​"​U​I​T​a​b​l​e​V​i​e​w​C​e​l​l​"​
 ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​f​o​r​I​n​d​e​x​P​a​t​h​:​i​n​d​e​x​P​a​t​h​]​;​

 ​ ​ ​ ​/​/​ ​G​e​t​ ​a​ ​n​e​w​ ​o​r​ ​r​e​c​y​c​l​e​d​ ​c​e​l​l​
 ​ ​ ​ ​B​N​R​I​t​e​m​C​e​l​l​ ​*​c​e​l​l​ ​=​
 ​ ​ ​ ​ ​ ​ ​ ​[​t​a​b​l​e​V​i​e​w​ ​d​e​q​u​e​u​e​R​e​u​s​a​b​l​e​C​e​l​l​W​i​t​h​I​d​e​n​t​i​f​i​e​r​:​@​"​B​N​R​I​t​e​m​C​e​l​l​"​
 ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​f​o​r​I​n​d​e​x​P​a​t​h​:​i​n​d​e​x​P​a​t​h​]​;​

 ​ ​ ​ ​N​S​A​r​r​a​y​ ​*​i​t​e​m​s​ ​=​ ​[​[​B​N​R​I​t​e​m​S​t​o​r​e​ ​s​h​a​r​e​d​S​t​o​r​e​]​ ​a​l​l​I​t​e​m​s​]​;​
 ​ ​ ​ ​B​N​R​I​t​e​m​ ​*​i​t​e​m​ ​=​ ​i​t​e​m​s​[​i​n​d​e​x​P​a​t​h​.​r​o​w​]​;​
 ​ ​ ​ ​c​e​l​l​.​t​e​x​t​L​a​b​e​l​.​t​e​x​t​ ​=​ ​i​t​e​m​.​d​e​s​c​r​i​p​t​i​o​n​;​

 ​ ​ ​ ​/​/​ ​C​o​n​f​i​g​u​r​e​ ​t​h​e​ ​c​e​l​l​ ​w​i​t​h​ ​t​h​e​ ​B​N​R​I​t​e​m​
 ​ ​ ​ ​c​e​l​l​.​n​a​m​e​L​a​b​e​l​.​t​e​x​t​ ​=​ ​i​t​e​m​.​i​t​e​m​N​a​m​e​;​
 ​ ​ ​ ​c​e​l​l​.​s​e​r​i​a​l​N​u​m​b​e​r​L​a​b​e​l​.​t​e​x​t​ ​=​ ​i​t​e​m​.​s​e​r​i​a​l​N​u​m​b​e​r​;​
 ​ ​ ​ ​c​e​l​l​.​v​a​l​u​e​L​a​b​e​l​.​t​e​x​t​ ​=​
 ​ ​ ​ ​ ​ ​ ​ ​ ​ ​[​N​S​S​t​r​i​n​g​ ​s​t​r​i​n​g​W​i​t​h​F​o​r​m​a​t​:​@​"​$​%​d​"​,​ ​i​t​e​m​.​v​a​l​u​e​I​n​D​o​l​l​a​r​s​]​;​
 ​ ​ ​ ​

 ​ ​ ​ ​r​e​t​u​r​n​ ​c​e​l​l​;​
}​

First, the reuse identifier is updated to reflect your new subclass. The code at the end of this method is fairly obvious – for each label on the cell, set its text to some property from the appropriate BNRItem. (You will deal with the thumbnailView later.)

Build and run the application and create a new BNRItem. The cells load, but the layout of each cell is most likely off. You have not set up constraints for each of the subviews of the cell’s contentView, so let’s do that now.

Constraints for BNRItemCell

BNRItemsViewController’s table view will change its size to match the size of the window. When a table view changes its width, each of its cells also change their width to match. Thus, you need to set up constraints in the cell that account for this change in width. (The height of a cell will not change unless you explicitly ask the table view to change it, either through the property rowHeight or its delegate method tableView:heightForRowAtIndexPath:.)

Right now, BNRItemCell.xib has an initial position and size for each view. For example, the thumbnailView’s size in the XIB file is 40 points wide and 40 points tall (if yours is not exactly 40x40, do not worry; it will be soon.). However, Auto Layout does not care about how big a view is when it is first created; it only cares about what the constraints say. If you were to add a constraint to the image view that pins its width to 500 points, the width would be 500 points – the original size does not factor in.

Here are the constraints you need:

  1. Make the UIImageView 40x40 points, close to the left edge of the content view and vertically centered with the content view.

  2. Make sure both the nameLabel and serialNumberLabel stay the same fixed distance away from the image view, stretch to fill the length of the cell (minus the size of the image view and the valueLabel), and maintain their vertical stacking.

  3. Keep the valueLabel centered vertically with the content view on the right edge of the cell and a fixed distance away from the other two labels.

First, pin the width and height of imageView. You can use the Pin menu or Control-click and drag diagonally from the image view to itself.

Next, center the image view within its container. You can use the Align menu to do this (selecting Vertical Center in container) or Control-click and drag from the image view to the container (selecting Center Vertically in Container). If you Control-click and drag, make sure you do not drag to another subview. An easy way to make sure you drag to the correct view is to drag to the Content View in the document outline (Figure 19.7).

Figure 19.7  Dragging to the document outline

Dragging to the document outline

Let’s set up the horizontal constraints for all of the views at once. Select the four subviews and then, from the Pin menu, check the left and right struts at the top. Click Add 6 Constraints. The image view now has all of the constraints that it needs, as evident by its blue constraint lines. (If yours are not blue yet, do not worry. Your image view’s frame may not match its constraints, and you will make sure this is fixed soon.) The completed constraints for the image view are shown in Figure 19.8.

Figure 19.8  Image view constraints

Image view constraints

Now, finish the constraints for nameLabel and serialNumberLabel. Select these two labels and open the Pin menu. Check the top and bottom strut, as well as Height, and click Add 5 Constraints. The constraints for those two labels will turn blue, but there could be unsatisfiable constraints if the table view cell’s height ever changes. Why? Right now, all of the vertical constraints that were added have their equality set to Equal. In the visual format language, this gives the equation:

V​:​|​-​1​-​[​n​a​m​e​L​a​b​e​l​(​=​=​2​1​)​]​-​5​-​[​s​e​r​i​a​l​N​u​m​b​e​r​L​a​b​e​l​(​=​=​1​5​)​]​-​1​-​|​

Notice that these add up to 43, exactly the current height of the content view. If the height of the table view cell changes, one of these constraints will have to break. (Want to see this? Go ahead and change the height of the cell from the Size Inspector. It is probably a good idea to change it back after you see the effects, but it is not absolutely necessary.) You will fix this by changing the relation of one of the constraints to be greater than or equal.

The constraint that you will modify is the one that pins the bottom of nameLabel to the top of serialNumberLabel. While you could select this constraint in the canvas, it is very small and difficult to select. Instead, select nameLabel, and then open up its Size inspector.

Here you can see all of the constraints that affect the selected view. Click on the gear icon associated with the Bottom Space constraint. This will pop up a menu allowing you to Select and Edit... or Delete the constraint. Choose Select and Edit.... At the top of the Attributes inspector, change the Relation to Greater Than or Equal. The constraints for these two labels are shown in Figure 19.9.

Figure 19.9  Name and serial number label constraints

Name and serial number label constraints

Next, select valueLabel and select and add the constraint for Vertical Center in Container from the Align menu.

You need to make one last change to get rid of an ambiguous layout. Since all three labels do not have their widths pinned, they all want to be the width of their intrinsicContentSize. Since the width of the two labels in the middle + the spacing + the width of the value label is greater than their intrinsic widths + that spacing, something will have to give: either the width of nameLabel and serialNumberLabel will have to grow, or the width of valueLabel will have to grow.

Let’s fix this ambiguity by adjusting the priority of the value label’s Content Hugging Priority to be higher than that of the other two labels. This is a better approach than pinning the width of the value label. If the width is pinned, then text longer than can be displayed will be truncated. By increasing the Content Hugging Priority, the value label’s width will always be exactly the width needed to display all of the text. (Unless the label is so long that the text must be truncated to satisfy all the constraints.)

Select valueLabel and open the Size Inspector. Change the Horizontal Content Hugging Priority to be 1000. The finished valueLabel constraints are shown in Figure 19.10.

Figure 19.10  Value label constraints

Value label constraints

You are done! All of the subviews should have blue constraint lines. If any do not, select the cell and then click Update All Frames in Item Cell from the Resolve Auto Layout Issues menu.

It was a bit of work to configure the cell, but its contents will now scale elegantly if the size of the cell changes or if the textual content changes.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset