Cocoa for Scientists (XXX): Developing for iPhone
Author: Drew McCormack
Around a month ago, I published a book on scientific scripting with Python. Nothing unusual, except the manner in which it was published: the book was only made available via the iTunes App Store.
I wrote a short piece announcing the experiment here on MacResearch, and promised to release the source code for those either wanting to do the same or just learn how you write a simple iPhone app. Today, I am coming good on the promise. This tutorial will be an introduction to developing on the iPhone, using the book reader I developed as an example.

The book reader app.
The Experiment
Because I already had a completed manuscript, I always considered this exercise an experiment, rather than a genuine attempt to get rich. It cost me relatively little time or expense to publish the book, so there were no great expectations as to how much profit it would generate. A good thing too, because — to be honest — sales haven’t been stellar.
For anyone keeping score, the first month only generated around $500 in profit, and that after being linked to from the high profile Daring Fireball web site. So if you are planning on making a living doing this, a different approach would probably be needed. At least pick a subject matter that forms less of a niche than what I have done, maybe targeting iPhone users more directly.
It should also be noted that many of the big players of tech publishing are starting to get interested in this market. You can now purchase books from O’Reilly in the iPhone application Stanza, and you can download your books from the Pragmatic Programmers in formats supported by iPhone book readers.
The difference between traditional publishers and self publishers lies largely in the price — a book from a publisher is usually $30 or more, where a self publisher can set their price lower (eg my book costs $4.99) — and in the attention to detail. A publisher will usually have several people check a manuscript, including an editor and a technical expert, in addition to the author. A self publisher may not. (A comical example of this is that in my haste to finish, I misspelt the word ‘scripting’ in the title of my book on the app’s startup screen.)
Presentation Engine
Let’s turn now to the app itself, and the choices that I made while developing it. One of the most important issues I faced was the presentation technology that should be used. Book readers like Stanza use standard e-book formats, and layout the text themselves, but these apps have to work with thousands of books. My app only had to work with a single book, so I had complete control over the look.
One option was to use HTML, but HTML does not give you complete control over pagination and the like. I wanted WYSIWYG control, and for that, PDF seemed the best approach. With PDF, you know that what you see in the word processor is what will appear to the reader, and when you are working with source code, that can be a good thing, because you don’t want wrapping or truncation of lines.
There are two options for viewing PDF files on the iPhone. Initially, it seemed that using Core Graphics’ PDF facilities was the best approach, giving the most control. And my first prototype did work very nicely. I developed some cool navigation features where you could skim pages like in iPhoto or iMovie to browse quickly. I was well on the way, but there was a big problem: The PDF functionality in Core Graphics seems to have some serious memory flaws. In short, it accumulates memory, and nothing you do can make it release the memory. After a few minutes browsing around, the app was inevitably killed every time. Even completely releasing the PDF document, and reloading it, did not cure this ailment.
So cool navigation or not, I was forced to look in a different direction: WebKit. WebKit, the engine behind Safari, can display PDF files. The downside is that it doesn’t give you anywhere near as much control as Core Graphics. Even something as fundamental as scrolling the document to a particular page is not self evident. After some effort, I was able to kludge together a solution involving javascript, mixed with a lot of trial and error. That’s the solution I will present below.
Design
In my first article, I gave a brief list of some of the design decisions I took related to how the book should present to the user, and it is worth revisiting those before we get into technicalities. First, I decided that the app should only be viewable in landscape mode. The rationale for this is that I needed as much width as possible to avoid wrapping of source code, and that landscape allowed for a larger font.
A second design choice was that pages should be narrower and longer than standard pages in a physical book. The narrowness — in relation to text size — was obviously to make text readable on the small iPhone screen, but the reason for the page length is less obvious. Because the book has lots of source code examples, I wanted to reduce the number of page breaks that fell within the examples. Longer pages help. Ideally, you would just have one continuous page, but having pagination aids in navigation, so it still serves a purpose.
Finally, navigation is much more important in a technical book than, say, a novel. Many people use technical books for reference purposes, rather than reading cover-to-cover. In short, I figured an easily accessible and detailed table of contents was a must.

The Table of Contents screen.
Becoming an iPhone Developer
Now that we have covered the basic specs of the book reader, we can start to look at how you go about developing one for the iPhone. However, before you can do that, there are a few preliminaries we need to cover so that you can follow along with the tutorial.
First, you have to sign up as a Registered iPhone Developer. This is free, and gives you access to the tools you need to develop for the iPhone. Once you have done that, download and install the latest iPhone SDK (2.2 at time of writing). This installs the Xcode tools, and various iPhone specific frameworks and documentation. It also installs the iPhone simulator, which is how you test your application most of the time.
If you want to get serious about developing for the iPhone, and actually want to start distributing your apps through the iTunes App Store, you will need to enroll in the iPhone Developer Program. This costs $99 a year, and you may need to wait several weeks before you are admitted. If you wish to install your apps on an iPhone, rather than just in the simulator, you need to join this program.
Source Code
With the iPhone SDK installed, you can download the source code for the book reader app. Unzip the archive, and double click the Xcode project to get started.
The Interface
The ‘Books’ app is just about the simplest iPhone app you could imagine — flashlight apps aside — so it is a good place to start learning about iPhone development. There are only a handful of classes: the obligatory application delegate, and two so-called view controller classes. All of these classes get instantiated via the MainWindow.xib Interface Builder file, which is loaded on launch.

The MainWindow.xib file is used to instantiate all controller objects.
If you open the MainWindow.xib file, and double click any of the view controller objects, you will see that the views they control are actually in other Interface Builder files. For example, if you double click on ‘Books View Controller’, you will see a window that indicates that the view is loaded from the file ‘BooksViewController.nib’. (In actual fact, it is loaded from the ‘BooksViewController.xib’, a small bug in Interface Builder.) If you open the BooksViewController.xib file from the Resources group in Xcode, you can see the main view of the application.

The main view of the app.
The BooksViewController is the file owner of this XIB file, and the top level view object is automatically assumed to be the controllers view object. You can very easily create a view—controller IB file like this by choosing File > New File… in Xcode: locate the iPhone OS section in the new file pane, and choose User Interfaces. Lastly, select the View XIB icon, and finish up by naming the new file.
The view in the BooksViewController.xib file contains a UIWebView, and a single button, which brings up the table of contents. The target of the button is the BooksViewController object, and the action is showTableOfContents:.
View Controllers
Even from this brief look through the Interface Builder files, it is clear that the iPhone is quite a different beast to the Mac. The mechanics of building an app are similar, but the UI classes are completely different. Indeed, an iPhone interface is built on a framework called UIKit, rather than the AppKit that is used to develop Mac apps. Developing with UIKit is no more difficult than developing with AppKit — in fact, it is somewhat simpler because Apple have been able to fix design decisions that are set in stone in AppKit — but even the most experienced Mac developer will need a little time to get used to the different approach.
One of the most fundamental differences between developing on the Mac and developing on the iPhone is that the iPhone makes extensive use of view controllers. Any top level view that is displayed on the screen will tend to be associated with a particular instance of UIViewController, or a subclass thereof (eg UITableViewController). These view controllers are responsible for loading the view, responding to interaction from the user, and unloading the view when it is no longer needed.
The iPhone is a very modal device. In contrast to the Mac, an iPhone app tends to be made up of a hierarchy of modal screens. Think about an app like Mail: you drill down through layers of hierarchy — account, mailbox, message — each layer associated with a different view. And each of these views has its own view controller. In fact, its the view controllers that get connected together to form the hierarchy; each view gets displayed onscreen as its corresponding view controller is moved to the top of the view controller stack.
Table Views Everywhere
Another big departure for the iPhone from the Mac is its use of Table Views. Whenever you see a list on the screen, you are unquestionably looking at a UITableView object. Table Views are not only used for simple lists, but can be used to present graphical content or settings. For example, the beautiful book shelf in the Classics app is a table view, and the whole Settings app is no more than pages and pages of UITableView instances.

Settings are usually presented in a UITableView
In our book reader, a table view is used for the Table of Contents. The TableOfContentsViewController class is a subclass of UITableView.
#import <UIKit/UIKit.h>
@class BooksViewController;
@interface TableOfContentsViewController : UITableViewController {
NSDictionary *tableOfContentsDictionary;
IBOutlet BooksViewController *booksController;
}
@property (assign) IBOutlet BooksViewController *booksController;
@end
This class loads a property list in the method viewDidLoad, which is then used to populate the table.
-(void)viewDidLoad {
[super viewDidLoad];
// Load Table of Contents
NSString *tocPath = [[NSBundle mainBundle] pathForResource:@"TableOfContents" ofType:@"plist"];
tableOfContentsDictionary = [[NSDictionary dictionaryWithContentsOfFile:tocPath] retain];
}
The viewDidLoad method, along with methods like viewWillAppear: and viewDidAppear:, are very common in UIViewController subclasses. They offer a good place to setup or load elements of the UI just before or after it comes on screen.
It may come as a surprise to some that view classes on the iPhone do not support bindings. As you become more experienced in iPhone development, the reasons for this become more evident, but initially it seems an odd choice. Instead, you use traditional Cocoa patterns like delegates and data sources to populate views. The UITableView class has many of these methods, most of which are optional. Here are a few of the data source methods from TableOfContentsViewController (for the full list, see the source code).
-(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return [[tableOfContentsDictionary objectForKey:@"chapters"] count];
}
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
NSDictionary *chapterDict = [[tableOfContentsDictionary objectForKey:@"chapters"] objectAtIndex:section];
return [[chapterDict valueForKey:@"sections"] count] + 1; // Add one for the chapter header
}
-(NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
NSDictionary *chapterDict = [[tableOfContentsDictionary objectForKey:@"chapters"] objectAtIndex:section];
return [chapterDict objectForKey:@"name"];
}
-(NSArray *)sectionIndexTitlesForTableView:(UITableView *)tableView {
return [tableOfContentsDictionary valueForKeyPath:@"chapters.label"];
}
-(NSInteger)tableView:(UITableView *)tableView sectionForSectionIndexTitle:(NSString *)title atIndex:(NSInteger)index {
return index;
}
Most of these methods just access the data in the property list, and return that to the table view. They are fairly self explanatory, though there are aspects that differ from the NSTableView objects you find on the Mac. For example, table views on the iPhone can have sections. I have used a section for each chapter in the book reader. Another example of sections is the Contacts app list, which has one section per letter of the alphabet.
Cells Are Not What You Think
One of the most important table view data source methods is tableView:cellForRowAtIndexPath:. This method actually has to return the view that is used to display a given row.
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = @"TableOfContentsCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithFrame:CGRectZero reuseIdentifier:CellIdentifier] autorelease];
}
NSDictionary *chapterDict = [[tableOfContentsDictionary objectForKey:@"chapters"] objectAtIndex:indexPath.section];
if ( indexPath.row == 0 ) {
NSString *s = [NSString stringWithFormat:@"%@. %@",
[chapterDict objectForKey:@"number"],
[chapterDict objectForKey:@"name"]];
cell.text = s;
}
else {
NSDictionary *sectionDict = [[chapterDict objectForKey:@"sections"] objectAtIndex:indexPath.row-1];
NSString *s = [NSString stringWithFormat:@"%@.%@ %@",
[chapterDict objectForKey:@"number"],
[sectionDict objectForKey:@"number"],
[sectionDict objectForKey:@"name"]];
cell.text = s;
}
return cell;
}
This code can be difficult to comprehend the first time you see it if you have any experience developing on the Mac. The first thing to remember is that the cells referred to here have basically no relation to the cells you use for drawing with NSTableView. Cells on the iPhone are true views — UIView subclasses — and remain onscreen as long as needed. On the Mac, cells are just a lightweight drawing class, and do not remain on screen or respond to events.
To prevent too many cells being created, and causing memory problems, each table view maintains a queue of reusable cells: When a cell goes offscreen, it is kept alive in the queue, and reused when a new cell is needed. The following code checks if there is a cell available to reuse, and if not, creates a new one.
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithFrame:CGRectZero
reuseIdentifier:CellIdentifier] autorelease];
}
The rest of the tableView:cellForRowAtIndexPath: method populates the cell with data. In this example, only the cell’s text property is set, but in other cases, you might set the image property, or even create a custom cell with many text and image elements.
Working with UIWebView
The BooksViewController takes care of loading the PDF document into the UIWebView, and navigating it. The viewDidLoad method locates the PDF, creates an NSURLRequest, and uses the loadRequest: method to load it into the view.
// Load PDF
NSString *path = [[NSBundle mainBundle] pathForResource:BKPDFFileName ofType:@"pdf"];
NSURL *url = [NSURL fileURLWithPath:path];
NSMutableURLRequest *urlRequest = [NSMutableURLRequest requestWithURL:url];
[webView loadRequest:urlRequest];
I couldn’t find any simple way to scroll to a particular page. (If anyone knows one, please post a solution in the comments.) The rather ugly solution I found was simply to use trial and error to determine the length of a page, and use javascript to scroll to a particular coordinate.
-(void)updateView {
[webView stringByEvaluatingJavaScriptFromString:[NSString stringWithFormat:@"window.scrollTo(0, %d);", self.scrollPosition]];
}
This code runs a one line Javascript script that requests the window to scroll to a particular coordinate. The value stored in the scrollPosition property is determined using this formula
-(NSInteger)offsetForPage:(NSInteger)pageIndex {
return (pageIndex - 1) * BKPixelsPerPage + 10;
}
with the BKPixelsPerPage constant determined by trial and error.
Do It Yourself
That covers the core functionality of the app. If you want to know more detail, I suggest just getting your hands dirty in the source code.
I’m releasing this source under the BSD license, which means you can pretty much do what you like with it, as long you attribute the code to me. (See the license file in the Xcode project.) If you decide to publish your own book using the source code, here is what you will need to do:
- Create a document like the one I made in Pages, with the same or similar page layout and text formatting.
- Generate a PDF from the document, and include it in the Xcode project.
- Change the value of the
BKPDFFileNamevariable inBooksViewController.mto point to your PDF file. - Change the data in the
TableOfContents.plistfile so the table of contents is correct. - Double click the ‘Python’ target, and change the name to something suitable for your book.
- In the Build settings of the target, change the ‘Product Name’ setting.
That should cover most of it. If you do actually publish something on the App Store, let us know so we can see if it is appropriate for listing on MacResearch.



Comments
Thanks
I did something similar when I wrote LaTeX Help.
This post gives me some good ideas on how I can expand it. :)
Thanks for the lesson Drew!
Excelente post
These has just give me some good ideas i have 2 pojects in my head one a reader for the canonic book from the LD Saints and other for the guttenber project. Both in spanish. Thanks. =D
I will use it...
Hello,
I am a french book writer about Mac OS X Server administration and I was looking for a way sell it on iPhone. Your framework is exactlly what I was looking for. Thanks for your job.
Concerned about profile expiration date.
My brother is an electrician and has been wanting to have his electrical code book on his iphone for the longest time (for personal use and not for the appstore... yet.). I am now able to do this easily with your app. I was worried about the expiration date for the provisioning profile. It's provisioning lifespan appears to be 90 days. Is there anyway to extend this? I don't understand why this would be. I'd hate to have the profile expire in less than 3 months when all the other apps I've provisioned have a one year provisioning profile lifespan. I'd really like to know soon as I'm doing this for his birthday on Thursday. Thank you in advance for the cool lesson and code. I really have learned alot.
PS - I've been looking at making an app to put in the appstore for diabetes fact and fiction and helpful hints, tips and maybe healthy recipes along with A1C information. I'd like to be able to place this app with a longer expiration date.
Re: Provisioning
Hi Lionel,
I don't think there is anything in the app that would make it expire.
You probably want to build a version for 'Ad Hoc' distribution. For that, you should create and download the provisioning files from iTunes connect. Note that this is not the same as the development provisioning.
You should then create a new 'Ad Hoc Distribution' configuration (begin by duplicating the App Store Distribution config), and configure it to use the Ad Hoc provisioning file.
Drew
---------------------------
Drew McCormack
http://www.maccoremac.com
http://www.macanics.net
http://www.macresearch.org
Title in table
Thank you very much for your tutorial. Everything works fine. I am a general practioner in the Netherlands and like to make ebooks for students.
The only thing I couldn't solve is:
How can I change the title above the table of contensts (Scientific Scripting wit...). I spent hours to look in the code but couldn't find the solution.
Thank you very much for your help.
Regards
René Fonville
Hi Rene
I'm sure you've already found this out for yourself, but I'll say it anyway.
First, open the MainWindow.xib and click on the Table Of Contents View Controller.
Second, open Inspector and click on Attributes.
Finally, you'll see the title to change.
This is by far my favorite source code. I've studied every line, as I'm sure you have by now, as well.
Hope this helps.
Landscape rotatable
Thank you for your explanation. Indeed I found it myself.
Another question:
is there a way to make the screen rotatable on your Iphone. I already made the Bookview controler rotatable but the table of contents stays in upright mode.
Thank you for the support.
Best regards
René Fonville
Re: Landscape
You just need to override a method in the view controller and return YES. The method is something like 'viewControllerShouldRotateToOrientation:'. Not sure exactly.
Drew
---------------------------
Drew McCormack
http://www.mentalfaculty.com
http://www.macanics.net
http://www.macresearch.org
Table Of Contents Issue
First I would like to say thank you so much for the tutorial being a newbie to scripting in Xcode and Cocoa any and all resources are greatly appreciated.
I am having a minor issue though everything works except , when building the table of contents in TableOfContents.plist I put in the page number and when I run a test and click on the Table of Contents and the page I wish to view it goes to the incorrect page usually about 30 pages more than I need so if I want page 16 and I select it it will take me to page 42 am I doing something wrong? Please help I am desperate to try and get this to work. Thank you again for the tutorial and any and all help.
Chris
Wrong Page
The paging is very primitive. It just jumps a multiple of a constant value in the code. You need to play with the constant value in the code until the paging works for your manuscript.
I think the setting is 1700 and something at the moment. Adjust that figure until the TOC jumps to the right page. You may be able to estimate what the value should be by taking the ratio of the page size in my code, and your own page size, and multiplying that ratio by the value in the code.
Hope this helps.
Drew
---------------------------
Drew McCormack
http://www.mentalfaculty.com
http://www.macanics.net
http://www.macresearch.org
Question RE: Building for OS 2.2.1 against SDK 3.2.1 SnowLeopard
I had some major issues a while back when I upgraded to Snow Leopard and then I installed the 3.2 SDK. The lowest iPhone OS that I can build for now is 2.2.1 under Device OS. What I'm worried about is IF I do upgrade to the 3.2.1 SDK will I still be able to build for the OS 2.2.1? I've found that if I build for ANY setting higher than that the Table of Contents don't load correctly. The booksView loads in the correct landscape mode, but the TableOfContentsView loads in portrait mode. I've looked for the 'viewControllerShouldRotateToOrientation:' that you mentioned in the post above but couldn't find anything like that. I'd like to be able to upgrade to the new SDK but will the TableOfContentsView load in the right way? Has anyone else had this problem? Any help would be greatly appreciated.
Re: Orientation
I think you should be able to upgrade and still target SDK 2.2.1. I Don't think it will be removed. It should just remain as an option.
If there is no viewControllerShouldRotateToOrientation: method, just add one, and do a test to make sure the new orientation is landscape. Something like this
---------------------------
Drew McCormack
http://www.mentalfaculty.com
http://www.macanics.net
http://www.macresearch.org
Table of Contents Question
How would you make the Table of Contents like a drill down list? To go from main chapters to sub-chapters to avoid clutter.
Table of contents title
Hi, this tutorial is great thank you. I'm beginning with iPhone development and I can't figure out how to change the navigation bar title displayed for the Table of content. I looked in the plist and TableOfContentsViewController.m and I can't find it. Please help! Thank you.
Edit : found it sorry.
TOC linking to HTML page instead of PDF
Good Morning Sir
First I wanted to say thank you so much for the information regarding the building of this app I tweaked it a bit by adding the function of note taking and copy and paste, but tweaking it has brought up a whole new issue lol, now since I am using a HTML page instead of using pdf I am having the issue of the TOC not linking to the correct page
i was curious to know that if I used id="tag"above each main header of the each page is there away to set the plist to link to that tag and thus when the user selects the desired chapter they are brought to the correct chapter. any and all help would be greatly appreciated thank you.
Chris
Love your latest update to Scientific Scripting with Python!
Will you be posting the source code for this new version any time soon? I'd love to see how you implemented the use of bookmarking, being able to edit the title of the bookmarks, as well as the other great features you've added. I think it would be greatly appreciated. Love your work! Keep it up.
Added note taking?
Would you be willing to share your addition of note taking with others? I think that's a great idea.
Bookmarking
Unfortunately, I wrote the bookmarking for a client, so I can't release the code.
Sorry.
Drew
---------------------------
Drew McCormack
http://www.mentalfaculty.com
http://www.macanics.net
http://www.macresearch.org
Page position when rotated
Thanks for the code. It works very well. I made it autorotate. When I am in portrait view I can go to the TOC and it goes right to the page, but if I rotate to landscape it loses it's place. If I double tap before I rotate, it keeps it's current place, and their is no problem. How can I add some code for it to be able to keep the current page when rotated to portrait or landscape. Also is their any way to make it automatically fit to screen when rotated? Thanks for the great code.
I also get 2 warnings "set text is deprecated"
cell.text = s;
I am building with SDK 3.13
Page position when rotated
Hi Drew,
I have the same problem after rotating. Is there a solution?
Issue with provisioning profile
I am fairly new at this provisioning profile stuff, so bear with me. I downloaded you source code, which is very helpful by the way, and I tried to install it on my device, but I got an error.
The error says: Code Sign error: Provisioning profile '9A864664-AC36-4D6A-A86A-43BB75D10BDB' can't be found.
I went into the build settings in the Book target and changed the signing identity from your name, Drew McCormack to me, and I also installed a whole different developer profile, which is why I am confused that I am getting this error. I am assuming that the profile identifier listed as missing, is your developer profile identifier, but again, it doesn't make sense that it would be asking for that. Do you have any thoughts? Please let me know. Thanks