Channels ▼


Developing for the iPad

Don't Trust the Simulator

What's the first and possibly most important rule of iPad/iPhone development? Don't trust the simulator. The simulator is a fantastic piece of work, and I still use it for most of my work because it loads and runs quickly. However, it is this speed and access to lots of memory that can be deceptive.

When using the device, I didn't notice a pause during background saving, but the actual sketching was not very smooth. The device was doing a lot of work to render the drawing path and composite it with the other layers in the sketch during every screen update. I am still in the process of optimizing the drawing code.

However, I found that, even worse than slow drawing, sometimes the application would corrupt a saved sketch. It didn't take too long to find the problem. When the iPhone OS tells your application to shut down, it isn't sending a polite request, it is informing your application that doomsday is nigh. If your application does not shut down within a few seconds (Apple doesn't specify the exact time limit), it will be forcibly killed.

On the iPad, my saving code did not always complete within the timeout period. Because I used libpng directly to save the files, the application would open the file, write out some data, and then the process would be killed mid-save. When the user returned to the application, the sketch would be corrupt and fail to load.

There are several possible solutions to this issue. The first, immediate solution that I implemented was to do all file I/O on a temporary file so that sketch files would not become corrupt. When the sketch was fully saved, the application then deletes the old file and renames the new file to the sketch file. This largely solves the file corruption issue, but the application still has a problem in that it can lose data if the user has been editing the sketch and shuts down the application before the 15 second save window occurs.

I can reduce the impact of this further by reducing the length of the save window, at the expense of potentially more frequent pauses in the application during use, however, this isn't very palatable either. I can further reduce the impact of the background save by implementing a more granular locking system on the sketch. If a sketch can be locked for a very short window and a read-only copy made of it, then the save can proceed in the background while leaving the sketch available for editing by the user.

However, I think the real solution is to use a native iPhone OS saving solution rather than my custom PNG solution with libpng. Saving to a PNG for export is still a good idea, but there is no reason that the internal format should be PNG. The sketch can be saved using Core Data or SQLite which are both very fast and can be implemented incrementally much more readily than writing to a large file on disk. A third solution would be to break down the large PNG file into several smaller files that are read in sequence to reconstruct the sketch data. The sketch would be stored as a directory of files and potentially a simple SQLite database or even a binary plist to reference the files. I will be experimenting with different solutions and improving Ideate's file management in an upcoming release.

Undo Support: Correcting Mistakes

For many applications, allowing users to undo their work is a crucial piece of functionality. For Ideate it was essential. The process of sketching out an idea is iterative and full of false starts. When I began to think about implementing undo, my mind swam with visions of Commands stacked into an undo and a redo stack, and the frustrating process of wrapping all user interactions into these reusable nuggets of state-changing operations.

However, thankfully for me and early Ideate users, Apple has already implemented the necessary functionality and provided it since the iPhone 3.0 SDK. This comes in the form of the NSUndoManager and it's a wonderful helper class. NSUndoManager supplies an easy interface to managing undo and redo. First, you create a new manager for each "document object" in your system. For Ideate, this is the Sketch object. In the init method of Sketch, I did that:

undoManager = [[NSUndoManager alloc] init];

The next step is to tell the undo manager that a new operation has occurred that can be undone. There are two ways to do this, but the first way, explained below is a little more straightforward and works great as long as the operation to undo only requires a single parameter. Inside a Sketch, there is a method that adds the newly sketched path to the array of paths. This code looks something like this:

- (void) appendPath:(DrawPath *)path {
	[paths addObject:path];
	[self setNeedsDisplay];

This allocates a new DrawPath and appends it to the list of paths to draw. Removing a path is also simple and the code is contained in the removePath method:

- (void) removePath: (DrawPath *)path {
	if ([paths containsObject:path]) {
		[paths removeObject:path];
		[self setNeedsDisplay];

This removes the requested path and marks the view as needing to redraw itself now that it has changed. These two operations are inverses of each other, and are exactly what we need to implement undo using the NSUndoManager. Here is the updated appendPath method with the undo functionality added to it:

- (void) appendPath:(DrawPath *)path {
	[paths addObject:path];
	[undoManager registerUndoWithTarget:self 
		selector:@selector(removePath:) object: path];
	[self setNeedsDisplay];

The registerUndoWithTarget: selector: object: message gives the NSUndoManager all the information it needs to undo the current operation. The target is a pointer to the object and the selector is the name of the message to send to the target. A selector is really just the name of the message -- it isn't equivalent to a function pointer in C. The object parameter specifies the single parameter to pass along with the message.

After this has been called, the operation can be reversed simply by sending undo in response to the user pressing the button in the UI:

-(IBAction)undoButtonPressed:(id)sender {
	NSLog(@"undo button pressed");
	[sketch.undoManager undo];

If your application supports redo, the NSUndoManager will automatically build a redo stack when it processes the undo. Any call to the NSUndoManager during an undo will add to the redo stack and a call to NSUnderManager redo will call those operations. Adding undo/redo support to removePath: looks like this:

- (void) removePath: (DrawPath *)path {
	if ([paths containsObject:path]) {
		[paths removeObject:path];
		[undoManager registerUndoWithTarget:self 
			selector:@selector(appendPath:) object:path];
		[self setNeedsDisplay];

Related Reading

More Insights

Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

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

Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.