Embedded Systems

Making Two-Way Tables in iOS

Although iOS has several Cocoa Touch classes that specialize in displaying text, the class of choice for making two-way tables is UITextField

The Chicken and the Egg Problem

The solution seems simple enough: Just write the edited value from the text field back into its proper element in the array. However, the problem becomes apparent after a little thought: How do you know which array and which element to update? UITextField carries a lot of information about itself, but not the section or row of the cell that you tapped on. Nor does the cell have any information about which text field was tapped. How can we find this critical information?

One way to deal with the problem is, when textFieldShouldReturn executes, work your way up the view hierarchy using superview until you get the view class of the cell, and have it obtain the reference to the table. Knowing the cell, you can message the table to get its indexPath. You then use the indexPath to extract the row and column information:

Then you can compare the textField object passed to this method and compare it to our date, cooling, and heating text field objects to find out which object was tapped. However, that is an awful lot of work to get this information. Fortunately, you can use the text field's view tag to expedite the process. When the cell is created or updated, you encode the section, row, and view information into the view's tag. When the textFieldShouldReturn method is invoked, just decode the value in tag of the UITextField's view to get the section and row information.

Let's look at the cell update code in cellForRowAtIndexPath in Listing Six to see how the section, row, and field information are encoded into the tag value:

Here we encode the section information by multiplying its value by 100, while the row value is multiplied by ten, and the field value is just a single digit. These values are summed together to form the tag value.

When textFieldShouldReturn executes now, determining the section, row and field information of the object that was tapped just takes a few compare operations (Listing Seven):

With the section and row values in hand, it's easy to calculate the array index, then use NSMutableArray's replaceObjectAtIndex method to write the new value into the correct element in the array. The other point of interest is that we use the section and row information to create an indexPath. This indexPath is used to specify the exact row that needs refreshing when invoking reloadRowsAtIndexPaths. For small tables, invoking reloadData to refresh the entire contents of the table is suitable. However, for large, complex tables you should use reloadRowsAtIndexPaths to keep the overhead required to perform the update to a minimum. You shouldn't see the update occur, which is fine: Such operations should be transparent to the user. If you want to confirm that the refresh takes place, modify the animation argument in the reloadRowsAtIndexPaths method and see what happens. This completes the construction of the two-way table. What you do with the updated data in the table's data model is up to you.

Final Touches (No Pun)

When entering text into timeField, if you enter anything other than a time consisting of digits, a colon, and AM or PM, the dateFormatter tosses an exception. You need to add code to do sanity checks on the edited text, or restrict what can be entered.

One way to do this is to use the UITextInputTraits_Protocol's keyboardType property to specify a keypad for numeric input only. This works for entering temperatures, but still does not solve the problem for timeField, because it requires some non-numeric characters. Alternatively, you can use the UITextFieldDelegate_Protocol's textFieldShouldBeginEditing method to present a date picker rather than a keyboard. How to do that is the subject of an entire article in itself.

The iOS interface is a colorful one, and we can leverage that to make the table UI more useful. For example, we can make the heatingField view red and the coolingField view blue to help remind users as to which temperature they are changing.

To do this, go back into IB, open TempCell.xib, and select the heatingField object. Use the Attributes Inspector to change the background color of the view. If you run the app however, all you get is a colored rectangular border around the field. For whatever reason, adding color to a rounded text field is fraught with gotchas. First, to get color within the proper field, adjust the view's alpha channel to 0.75. This value strikes a reasonable balance between color intensity in the view while keeping the displayed text from becoming too faint. You still have that rectangular border, however. To fix this, add the Quartz Core libraries to the app by inserting the following header file into the TableTempControl.m file.

#import <QuartzCore/QuartzCore.h>

We will use the Quartz layer feature to trim the colored corners. Go to the file's cellForRowAtIndexPath method and revise the cell creation code to match that in Listing Eight:

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task.
However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

Video

This month's Dr. Dobb's Journal

This month,
Dr. Dobb's Journal is devoted to mobile programming. We introduce you to Apple's new Swift programming language, discuss the perils of being the third-most-popular mobile platform, revisit SQLite on Android
, and much more!