Read iOS Programming: The Big Nerd Ranch Guide, 3/e (Big Nerd Ranch Guides) Online
Authors: Aaron Hillegass,Joe Conway
Tags: #COM051370, #Big Nerd Ranch Guides, #iPhone / iPad Programming
In
ItemsViewController
’s
tableView:cellForRowAtIndexPath:
method, you will create an instance of
HomepwnerItemCell
for every row in the table. At the top of
ItemsViewController.h
, import the header file for
HomepwnerItemCell
so that
ItemsViewController
knows about it.
In earlier implementations of
tableView:cellForRowAtIndexPath:
, you would ask the table view if it had any reusable cells first and create a brand new one if it did not. When using a XIB file to load a
UITableViewCell
subclass, the process is a little different: you register this XIB file with the
UITableView
for a given reuse identifier when the table first loads.
In
ItemsViewController.m
, override
viewDidLoad
to register
HomepwnerItemCell.xib
for the
HomepwnerItemCell
reuse identifier.
Notice that we waited until
viewDidLoad
to register the XIB. If we registered it in the designated initializer, the table view would only see this registration once. A low-memory warning, in this situation, would effectively unregister this XIB file because a new instance of
UITableView
would be created without re-registering it.
To get an instance of
HomepwnerItemCell
, all you do is ask the table view to dequeue a cell for the
HomepwnerItemCell
reuse identifier. If the table has a reusable instance of
HomepwnerItemCell
, it will return that. If it doesn’t, it will load
HomepwnerItemCell.xib
and give you an instance of the archived cell. In
ItemsViewController.m
, locate
tableView:cellForRowAtIndexPath:
and modify it like so:
Build and run the application. Your cells should look like the cells in
Figure 15.8
.
Figure 15.8 HomepwnerItemCell in action
Let’s go back to the
UINib
class you used when loading
HomepwnerItemCell.xib
. An instance of
UINib
knows how to read a XIB file. (Remember, XIB and NIB are used interchangeably; technically, an application loads NIBs, but we work with XIBs, so it’s easier to call them that.)
An instance of
UINib
is created with the contents of a XIB file. It loads the data in that file and holds on to it as long as it lives. When it is sent the message
instantiateWithOwner:options:
, the
UINib
parses that data: all of the archived objects come alive, and all of the connections are established. (The object passed as the first argument is the
File's Owner
.)
In
Chapter 10
, you loaded a XIB file by sending the message
loadNibNamed:owner:options:
to the main
NSBundle
without ever using
UINib
. Well,
loadNibNamed:owner:options:
uses
UINib
under the hood. It creates an instance of
UINib
and sends it the message
instantiateWithOwner:options:
, relaying the options and owner arguments.
Both of these methods return an
NSArray
. This array contains all of the top-level objects in the XIB file (the ones that aren’t under a disclosure tab in the outline view of the XIB). When you register a XIB file with a table view, it scans this array for an instance of
UITableViewCell
or a subclass of it and returns it to your data source.
Since the table view just scans the XIB file for a
UITableViewCell
, it is important that you only put one instance of
UITableViewCell
in a XIB file used for this purpose. Otherwise, the table view will get confused and throw an exception.
To display an image within a cell, you could just resize the large image of the item from the image store. However, doing so would incur a performance penalty because a large number of bytes would need to be read, filtered, and resized to fit within the cell. A better idea is to create and use a thumbnail of the image instead.
To create a thumbnail of a
BNRItem
image, you are going to draw a scaled-down version of the full image to an offscreen context and keep a pointer to that new image inside a
BNRItem
instance. You also need a place to store this thumbnail image so that it can be reloaded when the application launches again.
In
Chapter 12
, we put the full-sized images in the
BNRImageStore
so that they can be flushed if necessary. However, the thumbnail images will be small enough that we can archive them with the other
BNRItem
instance variables.
Big problem, though: the thumbnail will be an instance of
UIImage
.
UIImage
doesn’t conform to the
NSCoding
protocol, so you can’t encode the thumbnail directly in an
NSCoder
. What you can do is encode the thumbnail as data (PNG format) and wrap it in an
NSData
object, which does conform to
NSCoding
.
Open
BNRItem.h
. Declare two new properties: a
UIImage
and an
NSData
. You will also want a method to turn a full-sized image into a thumbnail.
Synthesize these properties in
BNRItem.m
. You will eventually override the default setter and getter methods for these properties, but synthesizing automatically generates the two instances variables you need.
When an image is chosen for this
BNRItem
, you will give that image to the
BNRItem
. It will chop it down to a much smaller size and then keep that smaller-sized image as its
thumbnail
. It will also create an
NSData
object that is the PNG representation of that image and set it as its
thumbnailData
. The
thumbnailData
will be archived with the
BNRItem
, and every time it is loaded from an archive, it will recreate its
thumbnail
from this data.
In
BNRItem.m
, create a getter method for
thumbnail
that will create it from the data if necessary:
Now let’s turn to the
setThumbnailDataFromImage:
method. This method will take a full-sized image, create a smaller representation of it in an offscreen context object, and set the
thumbnail
pointer to the image produced by the offscreen context.
iOS provides a convenient suite of functions to create offscreen contexts and produce images from them. To create an offscreen image context, you use the function
UIGraphicsBeginImageContextWithOptions
. This function accepts a
CGSize
structure that specifies the width and height of the image context, a scaling factor, and whether the image should be opaque. When this function is called, a new
CGContextRef
is created and becomes the current context.
To draw to a
CGContextRef
, you use Core Graphics, just as though you were implementing a
drawRect:
method for a
UIView
subclass. To get a
UIImage
from the context after it has been drawn, you call the function
UIGraphicsGetImageFromCurrentImageContext
.
Once you have produced an image from an image context, you must clean up the context with the function
UIGraphicsEndImageContext
.
In
BNRItem.m
, implement the following methods to create a thumbnail using an offscreen context.
In
DetailViewController.m
, add the following line of code to
imagePickerController:didFinishPickingMediaWithInfo:
to create a thumbnail when the camera takes the original image.
Now that
BNRItem
s have a thumbnail, you can use this thumbnail in
ItemsViewController
’s table view. In
ItemsViewController.m
, update
tableView:cellForRowAtIndexPath:
.
Now build and run the application. Take a picture for a
BNRItem
instance and return to the table view. That row will display a thumbnail image along with the name and value of the
BNRItem
. (Note that you will have to retake pictures for existing
BNRItem
s.)
Don’t forget to add the thumbnail data to your archive! Open
BNRItem.m
:
Build and run the application. Take some photos of items and then exit and relaunch the application. The thumbnails will now appear for saved item objects.