Friday, July 8, 2011
Camp Bisco 10 Sells Out
Monday, June 20, 2011
Tuesday, June 7, 2011
kizmet mobile
Monday, April 25, 2011
Pro Tune Planet Waves App
The tuners accurate, it has a huge library of chords, chord finder feature, scales, and an excellent metronome. The only drawback was that the whole/half step detuning options under the tuner tool are not working at this point. For example, If I choose to detune a half step down it will still tune it to normal. I think it's a bug that they'll hopefully fix soon, 3G iPhone
Some suggestions: a custom save option under alternate tunings and a tool dock within each tool so you could change tools quickly without having to exit out to the main tool page would have given this my highest rating.
4 stars!
Tuesday, April 19, 2011
Killer iPhone App Development: PT 2
While we’re on the subject of users, here’s another aspect of a compelling
application: direct manipulation and immediate feedback.
✓ Direct manipulation makes people feel more in control. On the desktop,
that meant a keyboard and mouse; on the iPhone, the Multi-Touch
interface serves the same purpose. In fact, using fingers gives a user a
more immediate sense of control; there’s no intermediary (such as a
mouse) between the user and the object on-screen. To make this effect
happen in your application, keep your on-screen objects visible while
the user manipulates them, for example.
✓ Immediate feedback keeps the users engaged. Great applications
respond to every user action with some visible feedback — such as
highlighting list items briefly when users tap them.
Because of the limitations imposed by using fingers, applications need to be
very forgiving. For example, although the iPhone doesn’t pester the user to
confirm every action, it also won’t let the user perform potentially destructive,
non-recoverable actions (such as deleting all contacts or restarting a
game) without asking, “Are you sure?” Your application should also allow the
user to easily stop a task that’s taking too long to complete.
Notice how the iPhone uses animation to provide feedback. (I especially
like the flipping transitions in the Weather Application when I touch the Info
button.) But keep it simple; excessive or pointless animation interferes with
the application flow, reduces performance, and can really annoy the user.
Why Develop iPhone Applications?
Because you can. Because it’s time. And because it’s fun. Developing my
iPhone applications has been the most fun I’ve had in many years (don’t tell
my wife!). Here’s what makes it so much fun (for me, anyway):
✓ iPhone apps are usually bite-sized — small enough to get your head
around. A single developer — or one with a partner and maybe some
graphics support — can do them. You don’t need a 20-person project
with endless procedures and processes and meetings to create something
valuable.
✓ The applications are crisp and clean, focusing on what the user wants
to do at a particular time and/or place. They’re simple but not simplistic.
This makes application design (and subsequent implementation)
much easier — and faster.
✓ The free iPhone Software Development Kit (SDK) makes development
as easy as possible. I reveal its splendors to you throughout this book.
Killer iPhone App Development: PT 1
on the iPhone. (The Windows OS has always been a bit less user-friendly, if
you ask a typical Mac user.) One of the early appeals of the Macintosh was
how similarly all the applications worked. So Apple (no fools they) carried
over this similarity into the iPhone as well. The resulting success story suggests
the following word to the wise. . . .
A compelling iPhone user experience usually requires familiar iPhone interface
components offering standard functionality, such as searching and navigating
hierarchical sets of data. Use the iPhone standard behavior, gestures,
and metaphors in standard ways. For example, users tap a button to make
a selection and flick or drag to scroll a long list. iPhone users understand
these gestures because the built-in applications utilize them consistently.
Fortunately, staying consistent is easy to do on the iPhone; the frameworks at
your disposal have that behavior built in. This is not to say that you should
never extend the interface, especially if you’re blazing new trails or creating
a new game. For example, if you are creating a roulette wheel for the iPhone,
why not use a circular gesture to spin the wheel, even if it isn’t a “standard”
gesture?
Making it obvious
Although simplicity is a definite design principle, great applications are also
easily understandable to the target user. If I’m designing a travel application,
it has to be simple enough for even an inexperienced traveler to use. But if I’m
designing an application for foreign-exchange trading, I don’t have to make it
simple enough for someone with no trading experience to understand.
✓ The main function of a good application is immediately apparent and
accessible to the users it’s intended for.
✓ The standard interface components also give cues to the users. Users
know, for example, to touch buttons and select items from table views
(as in the contact application).
✓ You can’t assume that users are so excited about your application that
they are willing to invest lots of time in figuring it out.
Early Macintosh developers were aware of these principles. They knew that
users expected that they could rip off the shrink-wrap, put a floppy disk in
the machine (these were really early Macintosh developers), and do at least
something productive immediately. The technology has changed since then;
user attitudes, by and large, haven’t.
Sunday, March 27, 2011
Lick of The Day App
The Lick of The Day App for iPhone and iPad is put out by Guitar World, the 800 pound gorilla of guitar magazines. Like most guitar players I am a lick junkie, and this app provides a steady stream of new licks and instructional features. The licks are also accompanied by video lessons by famous guitar players and respected teachers.
The app features tab and standard notation, with tempo and looping controls that enable you to play along slowly until you get the lick right. Perhaps the coolest feature is the dynamic fretboard that shows finger positions synchronized with the music notation. In addition, the fretboard can be inverted for lefties.
The Lick of The Day App app is listed as free in the appstore and comes with a few licks, however, you will eventually have to pay a subscription fee to continue receiving new content. You can purchase a one, three, or six-month subscription to receive a new and challenging daily lesson. - Alex Borsody
Sunday, February 27, 2011
Lick of The Day App
The Lick of The Day App for iPhone and iPad is put out by Guitar World, the 800 pound gorilla of guitar magazines. Like most guitar players I am a lick junkie, and this app provides a steady stream of new licks and instructional features. The licks are also accompanied by video lessons by famous guitar players and respected teachers.
The app features tab and standard notation, with tempo and looping controls that enable you to play along slowly until you get the lick right. Perhaps the coolest feature is the dynamic fretboard that shows finger positions synchronized with the music notation. In addition, the fretboard can be inverted for lefties.
The Lick of The Day App app is listed as free in the appstore and comes with a few licks, however, you will eventually have to pay a subscription fee to continue receiving new content. You can purchase a one, three, or six-month subscription to receive a new and challenging daily lesson. - Alex Borsody
Sunday, January 30, 2011
Planet Wave Music Apps-Chord & Scale Master from PlanetWaves
Guitar.com a subsidiary of Diadario, puts out music iPhone apps under their company Planet Waves, that cool company you can grab guitar straps and other gear from. They have created some amazing music iPhone apps including, some great ones for guitar players. Check out their app section
Check out one of their newest apps still in Beta...
Chord & Scale Master
Developed by Hank Wallace of Atlantic Quality Design utilizing the Planet Waves ChordMaster chord library by Stephen Salerno and the ScaleMaster scale library written by Marc Schonbrun, Chordmaster/Scalemaster was designed to work best using Internet Explorer on the PC or Safari on both PC and Mac platforms. It can be run using Firefox on the PC, but there have been unresolved compatibility problems with Firefox on the Mac platform.
A handheld model of the ChordMaster library is available from Planet Waves. BUY NOW
An iPhone application for ChordMaster and Scale Wizard is now available through iTunes.
The Web Chordmaster and Scalemaster (WCM) helps you study and play new chords and scales to enhance your guitar skills. Combining ease of access with about 7,800 chords and 1,500 scales, the WCM lets you explore the complete library of guitar chords and scales, quickly hear how groups of chords sound together, and also how scales sound when played against the chords you select. Use the WCM to explore chords you find in sheet music, as a practice aid, and to create exciting new sounds that you can learn to play on guitar. You can also use this application to print out practice sheets for the selected chords and scales.
Playing Chords
To play a chord, select the Root and Variant of the chord from the drop down boxes. The chord appears on the vertical fretboard. Click Play to play the chord using the MIDI device inside your computer. Click the arrow buttons to move the chord to other fret positions on the neck.
Playing Scales
To play a scale, select the Root and Type of the scale from the drop down boxes. The scale appears on the horizontal fretboard. Click Play to play the scale using the MIDI device inside your computer. Click the arrow buttons to move the scale on the neck. Mouse over the clef, to see the notes of the scale selected notated on a staff.
Playing Progressions
Create chord and scale progressions using the area at the bottom of the screen. There are eight slots each for chords and scales. Each lights up with a menu when you move the mouse over it. Use the menu to add, delete, and move chords and scales. Buttons for playing chords, playing scales, or playing both together allow you to hear your progression in various ways. There is a button to allow ALL of the notes for a selected scale to be played for each selected fret position, or else to play only the scale notes starting from the root of the scale.
Suggesting Chords and Scales
A powerful feature of the program is its ability to use intelligence to match chords and scales that will sound musically interesting when played together. Click the Suggest Chords to find chords that are compatible with each selected scale. Click the Suggest Scales to find scales that are compatible with the current chord. Experts in music theory are the brains behind these associations, and all this power is at your disposal for free!
Custom Chords and Scales
Do you want to create your own chords and scales and hear how they sound together? Just click on the fretboard to add and delete notes from the chord or from the scale. Click Play to hear how your creations sound, or to make a progression with your custom choices.
Program Setup
Click the Setup button to control MIDI operation and to control chord arpeggiation.
Help!
Click the Help button for a detailed explanation of program operation, and to learn more about the team that produced this application. Disable the popup window blocker in your browser if the help page does not display.
Tuesday, January 25, 2011
Table Views
iPhone OS. Many of the default applications use table views extensively,
including Mail, Safari, Phone, iPod, iTunes, and more. In fact, there are
fewer default apps that don’t use tables than those that do. Because of
tables’ utility and convenience and the fact that your user will be thoroughly
accustomed to tables, it is highly likely that your applications
will want to present some of its interface with table views.
In this chapter, we’ll explore how to present data in a table format by
reusing the Movie class from Chapter 4, View Controllers, on page 62.
In that chapter, we used view controllers to present and edit a single
Movie object; here, we’ll use a table view to show many Movie objects
and navigate to a second view controller that will let us edit an existing
object or create a new one.
5.1 Parts of a Table
On the iPhone, a table view is a one-dimensional, top-to-bottom list
of items, optionally split into multiple sections. The sections actually
make the list a two-dimensional data structure. Each section has a
variable number of items, so a given item in a table is identified by its
section and its row within that section.
In Figure 5.1, on the next page, we can see Interface Builder’s presentation
of table views, with dummy data that provides U.S. state names for
section headers and provides cities for row titles. There are two visual
styles for tables: a “plain” style that allows cells to stretch horizontally
to the table’s bounds and a “grouped” style that uses corner-rounding
and indentation to group the rows of each section. The grouped table
PARTS OF A TABLE 87
Figure 5.1: UITableViews in Interface Builder, with “plain” style (left) and
“grouped” style (right)
in the figure shows two sections: one with four rows for the first section
(“California”) and three rows for the second (“New York”).
An iPhone table consists of three things: a view, a data source, and
a delegate. You start with a UITableView class that presents the table
on-screen and handles user interaction, like a tap to select a row or a
swipe to delete an item. The UITableView depends on one or more other
objects to provide its functionality:
• A table view data source is an object that manages the relationship
between the visual UITableView and its contents. Methods in
the UITableViewDataSource protocol provide the number of sections
and rows in the table, provide titles for headers and footers, and
generate the views for each cell. The data source also has methods
to handle the insertion, deletion, or reordering of table rows. Most
of these features are optional: the only required methods are table-
View:numberOfRowsInSection: and tableView:cellForRowAtIndexPath:.
• A table view delegate allows the host application a greater level
of control over the table’s visual appearance and behavior. An
SETTING UP TABLE-BASED NAVIGATION 88
object implementing the UITableViewDelegate protocol is notified
of various user actions like the beginning and end of row selection
or editing. Other methods allow the delegate to provide customized
views for headers and footers and to specify nondefault
cell heights. The idea of Cocoa delegates was introduced in Section
3.6, Customizing Behavior with Delegation, on page 52, and
in the previous chapter’s Section 4.6, Creating the New View Controller,
on page 75, we used a UITextFieldDelegate to customize keyboard
behavior on a text field.
To use a table in your application, you will create a UITableView, typically
in Interface Builder, and connect it to objects that implement
the data source and delegate protocols. Often, the view controller that
manages the table view will also serve as both the data source and
the delegate. By making the view controller the data source for the
table, you’ll be required to implement tableView:numberOfRowsInSection:
and tableView:cellForRowAtIndexPath: from UITableViewDataSource, and at
a minimum you will usually also implement UITableViewDelegate’s table-
View:didSelectRowAtIndexPath: to handle the user tapping one of the table
rows.
5.2 Setting Up Table-Based Navigation
The UITableView is used as a navigation metaphor throughout the iPhone
OS. In Mail, you use tables to select an account, then a mailbox within
that account, and then a message within that mailbox. For each of
these steps, the data is presented as a table, and selecting a row navigates
to a new view, drilling down either to another table or to a view
that shows the contents of one message. To standardize this kind of
behavior across applications, Xcode provides a Navigation-based Application
project template that uses a table for its first view. We’ll use this
template to learn about tables and navigation in this chapter.
In Xcode, select File > New Project, and choose the Navigation-based
Application template. Make sure the checkbox labeled “Use Core Data
for storage” is not selected.
Name the project MovieTable, and Xcode will set you up with a project
containing two classes (MovieTableAppDelegate and RootViewController),
as well as two nibs (MainWindow.xib and RootViewController.xib).
This is a somewhat more advanced project template than you’ve seen
before, and it helps to understand how the pieces go together. Open
MODELING TABLE DATA 89
Figure 5.2: Navigation objects in the MainWindow.xib file
MainWindow.xib with Interface Builder, and switch the view mode from
icons to list (the middle button of the view mode control). By expanding
the tree structure, you’ll see the arrangement shown in Figure 5.2. The
nib has a navigation controller, an object that we’ll use in code to navigate
forward and backward between views. The navigation controller
has two children: a navigation bar, which you’ll recognize as the bar at
the top of navigation screens (where it usually hosts buttons like Back
or Edit), and the RootViewController, which has a navigation item object.
That’s all well and good for navigating, but where’s the table? If you
inspect the RootViewController, you will see that it gets its view from
RootViewController.xib. Open that nib and look: its default contents are
a single UITableView object. The take-away for now is that a navigation
application has this UINavigationController class that’s responsible
for navigation, which is a parent of the RootViewController, which is the
view controller for the first view the user sees, which is a UITableView.
5.3 Modeling Table Data
Because the RootViewController owns the table view, let’s take a look
at the class. In its implementation file, RootViewController.m, you’ll see
default implementations for some of the table data source and table
delegate methods, three of which are uncommented: numberOfSectionsInTableView:,
tableView:numberOfRowsInSection:, and tableView:cellForRowMODELING
TABLE DATA 90
AtIndexPath. If you look at RootViewController.xib with Interface Builder,
you’ll find that the table’s dataSource and delegate outlets are connected
to File’s Owner. The net result is that this table is wired up and ready
to run; the table expects the RootViewController to serve as its delegate
and data source, and the class provides the minimum implementation
of those protocols to run the application. The protocols are not
explicitly declared in RootViewController.h, because the controller subclasses
UITableViewController, whose declaration includes the two protocols.
Keep in mind that if you ever use some other view controller with
a table, you’ll have to add
to the @interface in the header file to declare that you implement these
protocols.
The default implementation provides for a table that has one section
with zero rows. It also provides logic for creating cell views in code,
but with zero rows, that code will never be used. So, the first thing we
need to do is to implement the section- and row-count methods in a
nontrivial way. This means we need to develop a model for the table
data; with the RootViewController mediating between the view and this
model, we’ll have implemented the classic Model View Controller design
pattern.
A table model does not have to be anything fancy; it’s not a class unto
itself as it is in other languages. For a one-section table, it’s practical
to just use an NSArray, which contains the objects the table represents.
The array’s length gives you the number of rows, and the contents for
a given cell can be looked up with the array’s objectAtIndex: method.
At the beginning of this chapter, we said that we would reuse the previous
chapter’s Movie class as the data for our table. In Groups & Files,
Ctrl+click or right-click the Classes folder, and choose Add > Existing
Files. Navigate to the previous Movie project,1 select its Classes folder,
use the Command key (D) to select Movie.h and Movie.m, and click the
Add button. In the next dialog box, make sure the checkbox for “Copy
items into destination group’s folder (if needed)” is selected, and click
Add again to copy the files into this project.
Also add #import "Movie.h" to RootViewController.h, since we’ll be using the
Movie class in our view controller.
1. In the book’s downloadable code, for example, this would be ViewControllers/Movie02.
MODELING TABLE DATA 91
Next, since we’re going to be offering an editable list of Movies, we’ll
want to use an array that we can add to and remove from. So, declare
the instance variable NSMutableArray *moviesArray; in the @interface block
of RootViewController.h. This array needs to be initialized, and we’ll want
to provide some data for our table (we’ll allow the user to add their
own data later), so uncomment the provided viewDidLoad method in
RootViewController.m, and add the highlighted code to create one Movie
and add it to the array:
Download TableViews/MovieTable01/Classes/RootViewController.m
- (void)viewDidLoad {
[super viewDidLoad];
moviesArray = [[NSMutableArray alloc] init];
Movie *aMovie = [[Movie alloc] init];
aMovie.title = @"Plaything Anecdote" ;
aMovie.boxOfficeGross = [NSNumber numberWithInt: 191796233];
aMovie.summary =
@"Did you ever think your dolls were really alive? Well, they are." ;
[moviesArray addObject: aMovie];
[aMovie release];
}
Now that our model has some genuine data, we need to update the
UITableViewDataSource methods to get that data to the on-screen UITable-
View. The default numberOfSectionsInTableView: returns 1, which is fine
as is. However, the tableView:numberOfRowsInSection: returns 0, which is
wrong. We want it to return the length of the array:
Download TableViews/MovieTable01/Classes/RootViewController.m
- (NSInteger)tableView:(UITableView *)tableView
numberOfRowsInSection:(NSInteger)section {
return [moviesArray count];
}
That will tell the table view that the one section has one row. As a result,
when the application runs, it’ll call the tableView:cellForRowAtIndexPath:
method to get a UITableViewCell for that one row. The template provides
a default implementation that creates a cell in code; we just have to
customize that cell, immediately after the provided comment // Configure
the cell and before return cell;.
What we need to do is to figure out which member of the array—there’s
only one now, but there will be many later—we want to use for the
cell’s contents. The key is to use the indexPath variable. An NSIndexPath
is an object that specifies a path through a tree structure as a set of
TABLE CELLS 92
zero-based integer indexes. On iPhone OS, this class is extended with
properties specifically for use with UITableViews: section and row. In other
words, any time you handle tableView:cellForRowAtIndexPath:, the section
and row of the cell being requested are indicated as indexPath.section
and indexPath.row.
So, right before return cell; in the provided implementation, add the following
code:
Download TableViews/MovieTable01/Classes/RootViewController.m
Movie *aMovie = [moviesArray objectAtIndex:indexPath.row];
cell.textLabel.text = aMovie.title;
The first gets the member of moviesArray that corresponds to the selected
row, which is the value of indexPath.row. Then we just need to present
the Movie’s title in the cell. The UITableViewCell provides two UILabels as
properties: textLabel and detailTextLabel. For this simple case, we set the
textLabel’s text to the movie title.
That’s all that’s necessary for a basic table. Build and Go. You’ll see the
one-row table shown in Figure 5.3, on the next page.
5.4 Table Cells
Thus far, we’ve provided enough of a data source implementation to get
a minimal table on-screen, but there’s a lot more we can do with this
table, starting with the table cells. After all, while our Movie class has
three member properties, we’re showing only one of them in the table.
Let’s look into getting more use from our cells.
Cell Styles
The provided implementation of tableView:cellForRowAtIndexPath: creates
a UITableViewCell object called cell that we customize before returning
it to the caller. The default cell has three visual properties that can be
used to put our data in the cell: textLabel, detailTextLabel, and imageView.
In this example, we set the text of the textLabel to get the basic, default
appearance. If the Movie class had an NSImage member (like a screenshot
or DVD box art), then we could set the imageView’s image property
to make the image appear on the left side of the cell.
TABLE CELLS 93
Figure 5.3: A basic UITableView
To make use of the detailTextLabel, we need to choose a different cell
style. The idea of the style is new in iPhone 3.0, and four styles are
provided:
• UITableViewCellStyleDefault: Presents the textLabel single block of
left-aligned black text. The detailTextLabel is absent. This default
is identical in appearance to table cells in iPhone 2.x.
• UITableViewCellStyleSubtitle: Presents a large left-aligned black text-
Label and a second line for a smaller, gray detailTextLabel below it,
similar to the iPod or Music application.
• UITableViewCellStyleValue1: Presents a large left-aligned black textLabel
on the left and a slightly smaller right-aligned detailTextLabel
on the right in blue. This layout resembles cells in the Settings
application and is intended only for use in group-style tables.
TABLE CELLS 94
Figure 5.4: UITableViews displaying the four provided UITableViewCell-
Styles, in plain and grouped mode
• UITableViewCellStyleValue2: Presents a small right-aligned blue text-
Label on the left and a small left-aligned black detailTextLabel on the
right, similar to the Contacts application. Again, this button-like
style is appropriate for use only in grouped tables.
In Figure 5.4, we can see these four styles in a modified version of
the sample application. We’ve changed the contents of the cells based
on their style, because some of the layouts are inappropriate for large
strings, particularly UITableViewCellStyleValue2, whose left-side label will
truncate after about ten characters. Since the “value” styles are meant
for a button-like presentation in grouped tables, the screenshot on the
right of the figure puts each cell in its own section, while the left screenshot
is a one-section table with four rows.
TABLE CELLS 95
Use Styles for Table Cells, Not CGRect
Prior to iPhone SDK 3.0, UITableViewCell’s designated initializer
was initWithFrame:reuseIdentifier:, which took a CGRect (usually
the constant CGRectZero, since it wasn’t actually used) for
the frame argument. The navigation-application template provided
a call to this initializer, as did all other table code. However,
in iPhone SDK 3.0, this initializer is deprecated in favor of
initWithStyle:reuseIdentifier:, which takes one of the style constants
instead of the CGRect. It’s trivially easy to convert old code to
the new standard by just switching to the new call and using
the UITableViewCellStyleDefault style.
The provided styles offer some flexibility in presenting your data in the
space afforded by a list on a small device like the iPhone. If none of
these styles, with or without the optional imageView, suits your needs,
then continue to Section 5.7, Custom Table View Cells, on page 105, in
which we’ll look at how to create custom cell layouts.
Cell Reuse
Along with a style, the initializer for a UITableViewCell takes a reuseIdentifier
string. Understanding how this object is used is critical to creating
tables that perform as expected. Fortunately, the default implementation
of tableView:cellForRowAtIndexPath: shows us what this property does
and how it is to be used.
A UITableView caches cells for later reuse, which improves performance
by recycling cells rather than repeatedly creating them anew. When a
cell completely scrolls off the top or bottom of the screen, it becomes
available for reuse. So, when you need to create a table cell in table-
View:cellForRowAtIndexPath:, you first try to retrieve an existing cell from
the table’s cache. If it works, you just reset that cell’s contents; if it
fails, presumably because no cached cells are available, only then do
you create a new cell.2
2. This means that dequeuing occurs only when there is enough data for the table to
fill the screen and the user has scrolled far enough for one or more cells to go entirely
off-screen.
EDITING TABLES 96
Here’s the default implementation in tableView:cellForRowAtIndexPath::3
Download TableViews/MovieTable01/Classes/RootViewController.m
Line 1 static NSString *CellIdentifier = @"Cell" ;
2 UITableViewCell *cell =
3 [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
4 if (cell == nil) {
5 cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault
6 reuseIdentifier:CellIdentifier] autorelease];
7 }
Line 1 creates a cell identifier, a string that indicates the kind of cell
we want. The idea here is that if you use different styles of cells in the
same table (either default styles or layouts of your own creation), you
will need to distinguish them in the table’s cache so you get back the
style of cell you need. In the default case, you use only one style, so any
arbitrary string like "Cell" will suffice. Next, lines 2–3 attempt to dequeue
a cell, that is to say, to retrieve a cell from the table’s cache, passing in
the identifier to indicate what kind of cell is needed. If this fails, then a
new cell is allocated and initialized.
5.5 Editing Tables
So now, we’ve covered how to provide table contents and gain some
control over how the contents of a cell are presented. The next step is
to make the table editable. What this really means is that we want to
make the table serve as an interface for editing the underlying model.
When we delete a row in the table, we want to delete the object from
the model, and when we add an item to the model, we want the table
updated to reflect that.
Let’s start with deletes, which are easier. In fact, the commented-out
code provided by the navigation-application template includes the
basics of what we need to provide deletion. Start with tableView:canEdit-
RowAtIndexPath:. The default implementation (and the default behavior,
if this UITableViewDataSource method is not implemented at all) is to not
permit editing of any row. Uncomment the default implementation, and
change it to return YES;. When you do this, you’ll find that you can swipe
horizontally on table rows to bring up a Delete button.
3. We’ve reformatted the default code to fit the layout of this book.
EDITING TABLES 97
To implement the delete, we need to implement tableView:commitEditing-
Style:forRowAtIndexPath:. The commented-out implementation has an ifthen
block for handling cases where the editing style is UITableView-
CellEditingStyleDelete and UITableViewCellEditingStyleInsert. We need to support
the former only. To perform a delete, we need to do two things:
remove the indicated object from the moviesArray model, and then refresh
the on-screen UITableView. For the latter, UITableView provides the
method deleteRowsAtIndexPaths:withRowAnimation:, which is exactly what
we need. Add the highlighted line to the default implementation, as
shown here, and delete the else block for UITableViewCellEditingStyleInsert:
Download TableViews/MovieTable01/Classes/RootViewController.m
- (void)tableView:(UITableView *)tableView
commitEditingStyle: (UITableViewCellEditingStyle)editingStyle
forRowAtIndexPath:(NSIndexPath *)indexPath {
if (editingStyle == UITableViewCellEditingStyleDelete) {
// Delete the row from the data source.
[moviesArray removeObjectAtIndex: indexPath.row];
[tableView deleteRowsAtIndexPaths:
[NSArray arrayWithObject:indexPath]
withRowAnimation:UITableViewRowAnimationFade];
}
}
This gives us swipe-to-delete behavior, but some users don’t even know
it exists. Fortunately, since we’re a navigation app, we have a navigation
bar at the top of the screen that is well suited to hosting an Edit button.
As in other apps, its default behavior when active is to add an “unlock
to delete” button to the left side of every table row that allows editing,
which brings up the right-side Delete button when tapped.
In the viewDidLoad method you uncommented, you might have noticed
the following commented-out code:
Download TableViews/MovieTable01/Classes/RootViewController.m
// Uncomment the following line to display an Edit button in the
// navigation bar for this view controller.
// self.navigationItem.rightBarButtonItem = self.editButtonItem;
You might recall from Section 5.2, Setting Up Table-Based Navigation,
on page 88 that in MainView.xib, the RootViewController came set up with
UINavigationItem as a child element. That represents the blue bar above
the table, typically used for forward-back navigation and for editing
tables. It has two properties for setting buttons in the bar: leftBarButtonItem
and rightBarButtonItem. Then, on the right side of this assignment,
notice the reference to self.editButtonItem. Every UIViewController
NAVIGATING WITH TABLES 98
supports this editButtonItem property, which returns a UIBarButtonItem
that calls the view controller’s setEditing:animated: method and toggles
its state between Edit and Done.
The commented-out line is almost what we want, but let’s put the Edit
button on the left, so we can leave the right side for an Add button that
we’ll create later. So, here’s the line you’ll need in viewDidLoad:
Download TableViews/MovieTable01/Classes/RootViewController.m
self.navigationItem.leftBarButtonItem = self.editButtonItem;
Once you Build and Go, you should now be able to tap the Edit button
and bring up the unlock-to-delete button for all the rows. In Figure 5.5,
on the next page, we can see the table in editing mode (with some more
sample data to fill out its rows).
5.6 Navigating with Tables
Our next task is to allow the user to add a table row. In the previous
chapter, we developed a MovieEditorViewController, and that’s perfectly
well suited to entering the fields of a new Movie object or editing an
existing one. And once created, it would be simple enough to add the
new Movie object to the model and update the table.
So, where do we put the editor? In the previous chapter, we used the
UIViewController method presentModalViewController:animated: to slide in
the editor. In this case, we’re going to learn something new: how to
use the navigation objects at our disposal. We created the project as a
navigation-based application in part because it gave us a good starting
point for our table, and navigation also turns out to be a good idiom for
switching between our viewing and editing tasks.
Navigation on the iPhone uses a “drill-down” metaphor that you are
probably familiar with from the Mail, iPod/Music, and Settings applications.
In the SDK, this is managed by a UINavigationController, which
maintains the navigation state as a stack of view controllers. Every time
you drill down, you push a new UIViewController onto the stack. When
you go back, you pop the current view controller off the stack, returning
to the previous view. The navigation is handled in code, independent of
how it’s represented on-screen: whether you navigate by tapping rows
in a table or buttons in the navigation bar, the underlying stack management
is the same.
NAVIGATING WITH TABLES 99
Figure 5.5: Using the default editButtonItem to delete rows from a UITable-
View
Adding the MovieEditorViewController
To try this, let’s get to the MovieEditorViewController by means of the navigation
API. In fact, we’ll use it for two purposes: to edit items already
in the table and to create new items.
As with the Movie class, you’ll need to copy the MovieEditorViewController.
h and MovieEditorViewcontroller.m classes to your project’s Classes
folder and then add those copies to the Xcode project. Also copy over
the MovieEditorViewController.xib (with Add > Existing Files as before) to
the project’s Resources group. In the earlier examples, this editor view
was presented modally and took up the whole screen. In this application,
it’s part of the navigation, and therefore the navigation bar will
take up some space above the view. Fortunately, Interface Builder lets
us simulate a navigation bar to make sure everything still fits in the
NAVIGATING WITH TABLES 100
view. Open the nib in IB, select the view, and bring up its Property
inspector (D1). Under Simulated Interface Elements, set Top Bar to
Navigation Bar to see how the view will look as part of the navigation.
In this case, the Done button won’t be pushed off-screen, but you might
want to adjust its position to get it inside IB’s dashed margin.
To bring up the movie editor, our RootViewController needs to push an
instance of the MovieEditorViewController on to the navigation stack. We
could create the view controller in code, but since we only ever need one
instance, it makes sense to create it in Interface Builder. The first step,
then, is to create an IBOutlet in RootViewController.h. Add an instance variable
MovieEditorViewController* movieEditor; inside the @interface’s curlybrace
block, and then declare the property as an outlet after the close
brace:
Download TableViews/MovieTable01/Classes/RootViewController.h
@property (nonatomic, retain) IBOutlet MovieEditorViewController *movieEditor;
As usual, you’ll need to @synthesize this property in the .m file. Also,
remember to put #import "MovieEditorViewController.h" in the header.
Now you’re ready to create an instance of MovieEditorViewController in
Interface Builder. Open RootViewController.xib with IB, and drag a UIView-
Controller from the Library into the nib document window. Select this
view controller, and use the Identity inspector (D4) to set its class to
MovieEditorViewController. The last step is to connect this object to the
outlet you just created. Ctrl+click or right-click File’s Owner (or show its
Connections inspector with D2), and drag a connection from movieEditor
to the view controller object you just created. We’re done with IB for
now, so save the file.
Editing an Existing Table Item
Let’s start by using the MovieEditorViewController to edit an item in the
table. When the user selects a row, we’ll navigate to the editor and load
the current state of the selected Movie object into the editor.
The first thing we need to do is to react to the selection event. The
UITableViewDelegate gets this event in the delegate method tableView:did-
SelectRowAtIndexPath:. The navigation-application template provides a
commented-out version of this method in RootViewController, though its
sample code creates a new view controller programatically. We don’t
need to do that, since we already have the next view controller. It’s the
movieEditor that we just set up in Interface Builder. So, we just need to
set up that view controller and navigate to it.
NAVIGATING WITH TABLES 101
Declare an instance variable of type Movie* named editingMovie in the
header file. It remembers which Movie object is being edited, so we’ll
know what to update in the table when we navigate to the table. Once
you’ve done that, the steps here are pretty simple. Remember what
movie we’re editing, tell the MovieEditorViewController what movie it’s editing,
and navigate to that view controller with the UINavigationController’s
pushViewController:animated: method.
Download TableViews/MovieTable01/Classes/RootViewController.m
- (void)tableView:(UITableView *)tableView
didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
editingMovie = [moviesArray objectAtIndex:indexPath.row];
movieEditor.movie = editingMovie;
[self.navigationController pushViewController:movieEditor animated:YES];
}
What’s interesting about the last step is how we get a reference to the
navigation controller. . . remember, we haven’t defined an ivar or property
for it; in fact, the navigation controller was created for us in Main-
Window.xib, and we haven’t touched it with IB. The neat trick is the navigationController
property defined by the UIViewController class and therefore
inherited by RootViewController. This property (also callable as an
instance method) looks through the object hierarchy to find a parent or
ancestor object that is a UINavigationController. Thanks to this method,
you never need to explicitly make connections to your navigation controller.
Your root view controller and any view controllers it pushes onto
the navigation stack can get to the navigation controller with this property,
using it to navigate forward or back or to update the on-screen
navigation bar.
This is all we need to do to the movie editor view; now we need a way to
get back from the editor to the root. MovieEditorViewController has a done
method that’s connected in IB to the Done button,4 but its implementation
needs to be updated. Instead of dismissing itself as a modal view
controller, it needs to navigate back to the previous view controller:
Download TableViews/MovieTable01/Classes/MovieEditorViewController.m
- (IBAction)done {
[self.navigationController popViewControllerAnimated:YES];
}
4. If we didn’t already have a Done button in the view, it would be more typical to set up
a Done or Back button in the navigation bar. The navigation in the example in Chapter 8,
File I/O, on page 138 will work like this.
NAVIGATING WITH TABLES 102
As you can see, the MovieEditorViewController also can use the inherited
navigationController property to get the UINavigationController.
This will navigate to and from the movie editor; the only task left to
attend to is to update the table when we return from an edit. The
RootViewController will get the viewWillAppear: callback when we navigate
back to it, so we can use that as a signal to update the table view:
Download TableViews/MovieTable01/Classes/RootViewController.m
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
// update table view if a movie was edited
if (editingMovie) {
NSIndexPath *updatedPath = [NSIndexPath
indexPathForRow: [moviesArray indexOfObject: editingMovie]
inSection: 0];
NSArray *updatedPaths = [NSArray arrayWithObject:updatedPath];
[self.tableView reloadRowsAtIndexPaths:updatedPaths
withRowAnimation:NO];
editingMovie = nil;
}
}
We gate our update logic with a check to see whether a movie is being
edited, since this method will also be called at other times (at startup,
for example). If we are returning from an edit, we need to identify the
one table row being updated. We can figure this out by getting the array
index that corresponds to editingMovie, constructing an NSIndexPath that
goes to that row in section 0 of the table, and pass the path to the table
view’s reloadRowsAtIndexPaths:withAnimation: method.
Adding an Item to the Table
Another thing we’d like to support is the ability to add new items to the
table. We can actually make this a special case of editing. When the
user taps an Add button, we quietly add an empty Movie to the table
model, insert a table row, and navigate to the editor.
Previously, we used the navigation bar’s leftBarButtonItem for the provided
editButtonItem, so let’s put the Add button on the right side of the
navigation bar. We don’t inherit an Add button from UIViewController like
we did with the Edit button, so we’ll create one ourselves.
NAVIGATING WITH TABLES 103
First, go to RootViewController.h, and set up an IBAction to handle an event
from the button we’re about to create:
Download TableViews/MovieTable01/Classes/RootViewController.h
-(IBAction) handleAddTapped;
Now, since we need to work with the navigation objects that Xcode
created for us, we’ll use Interface Builder to open the MainWindow.xib
file, where they’re defined. Switch the view mode in the nib document
window to list or column view, and double-click the Navigation Controller
object. This will bring up a window with the navigation bar at
the top and a view placeholder at the bottom that says it’s loaded from
RootViewController. You’ll notice that the Edit button is absent from the
left side of the navigation bar, because we add it with code at runtime.
Go to the Library, and find the icon for the Bar Button Item. This is
different from the usual Round Rect Button, so make sure the object
you’ve found lists its class as UIBarButtonItem. Drag the bar button to the
right side of the navigation bar, where you’ll find it automatically finds
its way to a highlighted landing spot, making it the navigation bar’s
rightBarButtonItem. Select the bar button, bring up the Identity inspector
(D1), and change its identifier to Add. This will change its appearance
to a simple plus sign (+).
The next step is to connect this button to the handleAddTapped method.
This is a little different from the connections you’ve made thus far. First,
when you bring up the button’s Connections inspector (D2), you won’t
see the usual battery of touch events like Touch Up Inside. Instead,
there’s a single Sent Action called selector. This is because the UIBar-
ButtonItem has a different object hierarchy than regular buttons and
doesn’t have UIControl, UIView, and UIResponder as superclasses. Instead,
this object has properties called target and selector; when the bar button
is tapped, the method named by selector is called on the target object.
You could set both of those properties in code; since we’re already here
in Interface Builder, let’s set it up here.
To set the selector and target, we drag the selector action from the Connections
inspector to one of the other objects in the nib. This time,
however, we don’t drag it to the File’s Owner. Since this is the MainWindow.
xib, the File’s Owner proxy object points to a generic UIApplication.
The handleAddTapped method that we want the button to call is defined
in the RootViewController class, so we drag the connection to the Root
View Controller object in the nib window, as shown in Figure 5.6, on
the next page. When you release the mouse button at the end of the
NAVIGATING WITH TABLES 104
Figure 5.6: Connecting a UIBarButtonItem’s selector to the RootViewController
drag, the names of the target’s IBAction methods will appear, and you’ll
select the only one: handleAddTapped.
With the connection made, save in IB and return to Xcode. Now we can
implement the handleAddTapped method that will be called when the
user taps the Add button:
Download TableViews/MovieTable01/Classes/RootViewController.m
-(IBAction) handleAddTapped {
Movie *newMovie = [[Movie alloc] init];
editingMovie = newMovie;
movieEditor.movie = editingMovie;
[self.navigationController pushViewController:movieEditor animated:YES];
// update UITableView (in background) with new member
[moviesArray addObject: newMovie];
NSIndexPath *newMoviePath =
[NSIndexPath indexPathForRow: [moviesArray count]-1 inSection:0];
NSArray *newMoviePaths = [NSArray arrayWithObject:newMoviePath];
[self.tableView insertRowsAtIndexPaths:newMoviePaths withRowAnimation:NO];
[newMovie release];
}
CUSTOM TABLE VIEW CELLS 105
This method starts by creating an empty Movie object, setting it as the
editingMovie, and navigating to the MovieEditorViewController, much like
the code to edit an existing Movie did. What’s different is that after navigating,
it does cleanup work on the table view (while the table is out
of sight) by adding the new object to the model array and then calling
insertRowsAtIndexPaths:withRowAnimation: to update the table to reflect
the new state of the model. The inserted Movie has blank fields, but
when the user returns from the editor, the object will be updated in
viewWillAppear:, just like when an existing item is edited.
Let’s review. We used the navigation-application template to set up an
application with a table view, which we backed with a model (a simple
NSMutableArray) to provide a list of Movie objects. After looking at the
various table cell styles, we added the ability to delete from the table
either with horizontal swipes (by implementing tableView:canEditRowAt-
IndexPath:), or with the Edit button (by adding the default editButtonItem
and implementing tableView:commitEditingStyle:forRowAtIndexPath:). Then
we looked at how to access the UINavigationControl to navigate between
view controllers and used the MovieEditorViewController to edit a Movie
indicated by a selected row in the table and then to edit a new Movie in
response to the tap of an Add bar button.
5.7 Custom Table View Cells
Back in Section 5.4, Cell Styles, on page 92, we looked at the four cell
styles provided by iPhone OS. Although they suit a wide range of uses,
sometimes you might want something else. If your GUI uses a unique
color theme, the default black or blue text on white cells might not suit
you. If you need to populate more than two labels, then none of the
available styles will work for you.
It is possible, with a little work, to custom design your own table cell
in Interface Builder and have your table use this design instead of the
built-in styles. In this section, we’ll use this technique to create a table
that shows all three of the Movie fields.5
5. Because we will change so much in the project to use custom table cells, the book’s
downloadable code examples have split this exercise into a separate project. The previous
material is represented by MovieTable01, and the custom-cell project is MovieTable02.
CUSTOM TABLE VIEW CELLS 106
Designing a Custom Table Cell
Every UITableViewCell has a contentView, so it’s possible to programmatically
create subviews and add them to this view; some Apple sample
code does this. The problem is that you then have to customize the location,
font, size, and other properties of each subview with code, without
a visual editor. The second approach is to create a UITableViewCell in a
nib file, add the subviews visually, and load that nib when the table
needs a new cell. This is what we’ll do.
In Xcode, select the Resources group and use File > New File to create a
new file, choose User Interface from the iPhone OS section, and create
an empty nib file called MovieTableCell.xib. Open this file in Interface
Builder. The document will contain just the usual two proxy objects:
File’s Owner and First Responder. From the Library, drag a table view
cell into the nib window. Double-click to edit the object, which will
open a small window the size of a typical table view cell, with a gray
area designated as the content view.
The content view is really just an IB visual artifact, a placeholder for the
created-at-runtime view that contains all our subviews, so we’ll place
our UI elements directly on top of it. The Movie class has three fields,
so we’ll use three labels to put those fields in a single cell, adjusting
the font, color, sizing, and layout appropriate to the items’ respective
importance. Drag three UILabels from the library into the cell, using
the positioning handles and the Attributes inspector (D1) to customize
their location, bounds, color, and font. For the samples in the book’s
downloadable example code, here’s what we used:
• Movie Title: Georgia 17-point font, yellow text, left-aligned near the
left side of the cell, toward the top
• Box Office Gross: Helvetica 17-point font, green text, right-aligned
near the right edge
• Summary: Helvetica 10-point font, light blue text, along the entire
bottom of the cell
Our cell design in Interface Builder is shown in Figure 5.7, on the next
page. We used lighter colors because we plan to use a black background
for the table, although this makes the colors harder to see against the
background of the gray Content View placeholder. You’ll also notice that
we’ve put somewhat plausible data in each of the fields to get a sense of
how much space each needs and what they’ll look like with real data.
Save MovieTableCell.xib, then open RootViewController.xib, find the table,
CUSTOM TABLE VIEW CELLS 107
Figure 5.7: Designing a custom UITableViewCell in Interface Builder
and use the Attributes inspector to set its background color to black.
We have to do this because parts of the table cell are transparent and
we might not have enough cells to fill the table, and we want empty
parts of the table to have the same background as populated cells.
Loading and Using a Custom Table Cell
We have a custom table cell, so how do we use it in the table? If you
think about it, we really need many table cells. The default behavior
of the table is to create a new cell in code each time we try to fail to
dequeue a reusable cell from the table’s cache. If we’re going to use the
cell from this nib, then we have to load a new custom cell each time we
would have created a cell with code.
There’s an interesting trick to how we do this. We can manually load the
nib in code and find the cell within the nib. To do this, create an IBOutlet
property in RootViewController.h to hold onto a UITableViewCell loaded from
the nib.
Download TableViews/MovieTable02/Classes/RootViewController.h
@interface RootViewController : UITableViewController {
// ... other ivars omitted here for space
UITableViewCell *nibLoadedCell;
}
@property (nonatomic, retain) IBOutlet MovieEditorViewController *movieEditor;
@property (nonatomic, retain) IBOutlet UITableViewCell *nibLoadedCell;
-(IBAction) handleAddTapped;
@end
Now, go back to editing MovieTableCell.xib in Interface Builder. Select
File’s Owner, bring up its Identity inspector (D4), and change its class
to RootViewController. Having done this, you should be able to switch to
the Connections inspector (D2) and connect the nibLoadedCell outlet to
CUSTOM TABLE VIEW CELLS 108
The Secret of File’s Owner
The technique of loading a custom table cell from a nib
should also demystify the nature of File’s Owner in Interface
Builder. In IB, File’s Owner is a proxy object that refers to whatever
object “owns” the nib file. You can set the class of File’s
Owner in order to access that class’ outlets and actions, but
all you’re really doing is making an implicit assertion that some
object of that class will be the one that owns the nib when
it’s loaded. Here, you see the other side of that relationship:
loadNibNamed:owner:options: loads the nib, specifying an owner
object. Any connections to File’s Owner get connected to or
from this object as part of loading the nib.
the cell object in the nib window. While you’re in IB, select the table
cell, bring up its Attributes inspector, and change the identifier (the
first field) to Cell. You may recognize this as the “reuse identifier” string
we used in Section 5.4, Cell Reuse, on page 95.
Now for the surprising part. In RootViewController.m, go to the table-
View:cellForRowAtIndexPath: method, and rewrite the if (cell==nil) block as
follows:
Download TableViews/MovieTable02/Classes/RootViewController.m
Line 1 if (cell == nil) {
2 [[NSBundle mainBundle] loadNibNamed:@"MovieTableCell"
3 owner:self options:NULL];
4 cell = nibLoadedCell;
5 }
This eliminates the programmatic creation of the table cell, but the
means by which cell gets assigned is not necessarily obvious, because
the most important step is implicit. On line 2 is where we load the MovieTableCell
nib. This returns an NSArray of the nib contents, which we
could iterate over to find the table cell object. But we don’t have to,
because we declared an outlet from that cell to the nibLoadedCell property.
The outlets are connected as a consequence of loading the nib,
meaning that when loadNibNamed:owner:options: returns, the nibLoaded-
Cell has a reference to the custom cell loaded from the nib, which we
can then assign to the local variable, cell, on line 4.
SORTING TABLE DATA 109
Assigning Values in a Custom Table Cell
Each time a new cell is needed, loadNibNamed:owner:options: will be
called again, creating a new cell object in memory. So, at the end of
the if, we have a cell (either dequeued from the table or loaded from
the nib) that we need to customize with the values of a Movie from
the model. But with a custom cell, we can no longer use the textLabel or
detailTextLabel properties. Instead, we need a way to access the subviews
we added in Interface Builder.
One option would be to create a custom UITableViewCell subclass, declare
and connect outlets in that class, and then cast the cell to that
class when loaded. The only downside is that there are lots more classes
to write, one for every kind of table cell in your application. Here’s a
somewhat more direct technique. Open the cell in Interface Builder,
and select the title label. Open the Attributes inspector, and scroll down
to the field marked Tag. The tag is a simple, unique integer to identify
one view within a view hierarchy. Use the Attributes inspector to set
the title label’s tag to 1, the box office gross label to 2, and the summary
label to 3.
Now, back in tableView:cellForRowAtIndexPath:, you can customize each
label’s text by looking up the label with the cell’s viewWithTag: method.
Download TableViews/MovieTable02/Classes/RootViewController.m
// Configure the cell.
Movie *aMovie = [moviesArray objectAtIndex:indexPath.row];
UILabel *titleLabel = (UILabel*) [cell viewWithTag:1];
titleLabel.text = aMovie.title;
UILabel *boxOfficeLabel = (UILabel*) [cell viewWithTag:2];
boxOfficeLabel.text = [NSString stringWithFormat: @"%d" ,
[aMovie.boxOfficeGross intValue]];
UILabel *summaryLabel = (UILabel*) [cell viewWithTag:3];
summaryLabel.text = aMovie.summary;
return cell;
And now, we’re ready to go. We have a custom cell design in a nib, along
with new table code to load and populate that cell. Build and Go to see
a table like the one shown in Figure 5.8, on the next page.
5.8 Sorting Table Data
Another common task for developers who use tables is to sort the data
in the table. Fortunately, Cocoa and Objective-C give us some unique
advantages and make this an enviably easy task.
SORTING TABLE DATA 110
Figure 5.8: A UITableView with custom-designed cells
To add sortability, we’ll start by adding a sorting control to our user
interface.6 Open MainWindow.xib in Interface Builder, and double-click
the Navigation Controller object to bring up its view. Drag a segmented
control from the Library to the center of the navigation bar. The UISegmentedControl
is a useful control that allows the user to select one of
a small number of preset values. Even though it automatically adjusts
its size for the limited space of the navigation bar, this one won’t have
room for many options, so let’s just use three. Select the segmented
control, and open the Attributes inspector. Set the number of segments
to 3, using the Title field to set the titles of the segments to A-Z, Z-A, and
$ (or whatever monetary symbol makes the most sense for your locale).
6. Once again, we’re making enough changes to merit a separate project in the book’s
downloadable code. The sorting version of the project is MovieTable03.
SORTING TABLE DATA 111
We’ll need to access this segmented control from code, so we’ll need an
outlet to it. In RootViewController.h, declare the instance variable UISegmentedControl*
sortControl; and set up a property for it with the usual
@property and @synthesize statements, as with the other properties you’ve
already created. You’ll also need to declare this method to handle the
event when the user taps the sort control:
Download TableViews/MovieTable03/Classes/RootViewController.h
-(IBAction) handleSortChanged;
In IB, still in MainWindow.xib, you should now be able to connect the
Root View Controller object’s sortControl outlet to the segmented control,
as well as connect the segmented control’s Value Changed event to the
Root View Controller’s handleSortChanged method.
We’ll need to sort the array both in response to the user changing the
segmented control and to other causes: adding or editing an item will
require a re-sort, plus we’ll need to sort the array when the application
first starts up. So, let’s plan on writing a sortMoviesArray method, which
we’ll get to in a minute. We can now implement handleSortChanged
pretty trivially:
Download TableViews/MovieTable03/Classes/RootViewController.m
-(IBAction) handleSortChanged {
[self sortMoviesArray];
[self.tableView reloadData];
}
Whenever the sort type changes, we sort the array and then tell the
UITableView to reload all its data. This could be expensive, but a sort
may well change every row of the table, making the update of individual
rows impractical. We also need to add these two lines of code to the
bottom of viewWillAppear: so that we re-sort and update the table when
the application starts up, when an item is edited, and when an item is
added.
How do we perform the sort? It’s actually pretty easy. The sortMoviesArray
method needs to appear in the implementation before any call to it
(or else you can put the method signature in the header file, although
that exposes it publicly). To perform the sort, we’ll rely on the fact that
the NSArray provides a number of methods to return a sorted copy of an
array, and NSMutableArray offers these as methods to sort the mutable
array itself. Some of these take function pointers or Objective-C selectors,
allowing you to write a custom sorting function. But the easiest
option is to use sort descriptors.
SORTING TABLE DATA 112
The NSSortDescriptor is a class that describes a sorting criteria, consisting
simply of a key and a BOOL to indicate whether the sort is ascending.
The way this works is to use Key-Value Coding to access the field to sort
by. The key is a string that defines a key path, which is a dot-separated
path of “getable” properties of an object. For each step of the path, the
path segment is retrieved by attempting to access a property, accessor
method, or instance variable with the key name. The sort descriptor
then uses a default selector defined for many Cocoa objects, compare:,
to actually perform the sort.7
Our Movie objects are very simple, having three properties. To sort
alphabetically by the title, we just create a sort descriptor whose key
is title, the name of the property.
With that description in mind, look how simple it is to implement all
three of our sorts:
Download TableViews/MovieTable03/Classes/RootViewController.m
-(void) sortMoviesArray {
NSSortDescriptor *sorter;
switch (sortControl.selectedSegmentIndex) {
case 0: // sort alpha ascending
sorter = [[NSSortDescriptor alloc]
initWithKey:@"title" ascending:YES];
break;
case 1: // sort alpha descending
sorter = [[NSSortDescriptor alloc]
initWithKey:@"title" ascending:NO];
break;
case 2:
default: // sort $$ ascending
sorter = [[NSSortDescriptor alloc]
initWithKey:@"boxOfficeGross" ascending:YES];
break;
}
NSArray *sortDescriptors = [NSArray arrayWithObject: sorter];
[moviesArray sortUsingDescriptors:sortDescriptors];
[sorter release];
}
This implementation sets up a single NSSortDescriptor appropriate to the
selected sort type and puts it into an array for use by NSMutableArray’s
sortUsingDescriptors:. The reason this takes an array is that you could
7. If your properties were custom classes that didn’t respond to compare:, you could
change the sorting selector, but you’ll usually be sorting Cocoa classes like NSString and
NSNumber, which have sensible implementations of compare:.
SORTING TABLE DATA 113
Figure 5.9: Sorting the table alphabetically by title
provide multiple descriptors to perform secondary sorts; two objects
determined to be equal by the first sort descriptor would then be sorted
by the second descriptor in the array, and so on.
With these changes, our sorting behavior is ready to go. In fact, you
will see it as soon as you launch the application, since viewWillAppear:
makes a call to sortMoviesArray at startup.
Monday, January 24, 2011
Killer Beats iPhone App Review
Category: Music
Updated: 16 December 2010
Current Version: 1.1
1.1 (iOS 4.0 Tested)
Size: 161 MB
Language: English
Developer: Musicroom.com
© Music Sales Limited
Rated 4+