March 23, 2009

TableFormEditor Example

I have been asked to provide a more concrete example on using the TableFormEditor. I will provide an Xcode project with a little app that demonstrates the TableFormEditor in action. The project can be downloaded from here. What this example does is display three fields of data and a button. When the button is pressed, the user either is creating a new record or editing an existing record. This example also has a new version of the TableFormEditor which addresses a couple bugs. One bug was that if you have a translucent navigation bar, then the editor puts the first field under the nav bar. The other bug is that the alert that pops up when you hit the delete button assumes you have a tab bar. If you don't it crashes. So this version fixes that assumption.

March 18, 2009

Copying pitfalls

Copy semantics are a little different in Objective-C than in Java and C++. Say we have an array:
NSArray* arr1 = [[NSArray alloc] init];
And you have some data in it. Most people realize that the following assignment
NSArray* arr2 = arr1;
doesn't make a copy of anything except the pointer. What you end up with is two pointers pointing to the same array. To make a shallow copy of arr1, you can do something like this:
NSArray* arr2 = [[NSArray alloc] initWithArray:arr1];
This is a "shallow" copy because although it creates a new array, all its contents are pointers which point to the same contents of arr1. The Objective-C frameworks provide a copy method which you would think would work, but it too creates a shallow copy. The following is just another way of saying the above:
NSArray* arr2 = [arr1 copy];
This will also allocate a new NSArray object (and retain it) and put in the same pointers from arr1 into arr2. To get a deep copy of arr1, meaning not only a new array but a new copy of each element in the array, you need something different:
NSArray* arr2 = [[NSArray alloc] initWithArray:arr1 copyItems:YES];
NSDictionary objects also provide this form of initializer to allow deep copying of a dictionary container. All of this is fairly muddy, but when we start talking about mutable containers, it gets worse. Consider the following code:
NSMutableDictionary* firstDict = [[NSMutableDictionary alloc] initWithCapacity:10];

// add some data to the dictionary
[firstDict setObject:@"hi" forKey:@"one"];

// now make a copy of it
NSMutableDictionary* secondDict = [firstDict copy];

// add some more data to the second dictionary
[secondDict setObject:@"there" forKey:@"two"];

// BOOM!

The "BOOM!" above means an exception is thrown. Why? Because the dictionary secondDict, although declared as an NSMutableDictionary and copied from another NSMutableDictionary is actually an NSDictionary. It is reasonable to think that copying a mutable into another mutable would create a mutable copy, but it is not so in Objective-C. The problem is that the copy method creates an immutable object. The solution here is to use the mutableCopy method, which is a special copy method just for mutable containers. So the line above should be replaced with:
NSMutableDictionary* secondDict = [firstDict mutableCopy];
So what if you want a deep copy of a mutable container? In this case, you can use the special NSDictionary initializer that takes the copyItems parameter:
NSMutableDictionary* secondDict = [[NSMutableDictionary alloc] initWithDictionary:firstDict copyItems:YES];
(Note that to use copy, mutableCopy, or the initializers that take copyItems, the objects that are contained in the array or dictionary must conform to the NSCopying and/or NSMutableCopying protocols.) What these examples show is that the initializers behave as expected, but the copy method is only for immutable containers and the mutableCopy method is for mutable containers. Because of this confusion, I prefer to use the initializer for both my shallow copying and deep copying; simply change the copyItems to NO for a shallow copy.

March 17, 2009

Book Review: The iPhone Developer's Cookbook

I purchased Erica Sadun's "The iPhone Developer's Cookbook" at the same time as Mark and LaMarche's "Beginning iPhone Development" (reviewed here). One would expect a lot of common ground and duplicated information in these two books, but that is not the case. Sadun takes a different tack with "Cookbook". "Cookbook" is just that: a book of "recipes" on how to get specific tasks done on the iPhone. "Beginning iPhone Development" introduces topics, explains them in great detail, and provides an example project that demonstrates the topic. But "Cookbook" takes this approach: "You want to do X? Here is some code that does X." Sadun's presentation is terse and to the point, with little explanation of how or why. Given the nature of this book, this approach is not at all bad. Sometimes I don't care about the nitty gritty details; I just need to know how to do X. In this context, "Cookbook" is perfect, and it has served well as a reference. The recipes presented are useful, some are advanced, and there doesn't appear to be much overlap with the content provided by "Beginning". However, don't expect to take the example projects and build them into full-fledged, polished applications without a lot of work. The projects provided build upon the initial hello world project. They are simply there to demonstrate the code, not to serve as a springboard for applications. The code snippets that accomplish X are important, not the projects. One thing I noticed between the two books is "Cookbook" appeared to use an older version of the SDK (pre 2.0?). "Cookbook" also occasionally steps into "unofficial" SDK territory, using undocumented APIs. While these are pointed out, I think a book needs to decide whether it wants to document usage of the official Apple SDK or whether it wants to cater to those jailbroken iPhone developers. All in all, I felt that "Cookbook" complemented "Begining" well with additional advanced topics in a no-nonsense approach. However, "Cookbook" is a relatively short book, considering the magnitude of the topic, and I felt the material could have been beefed up. This may have been a result of getting the book out fast. Certainly, its timeliness was a great help to me at the time, but "Cookbook" will have trouble competing with more polished, up to date, and comprehensive books as time goes on. Hopefully Sadun will provide revised editions. With the OS 3.0 coming out soon, all the old books may be obsolete, or at least incomplete.

March 13, 2009

Book Review: Beginning iPhone Development

Back in December, when I jumped into iPhone developing, I searched for books covering this topic. On Amazon, there were only two that addressed the official Apple SDK: Erica Sadun's "The iPhone Developer's Cookbook" and Dave Mark and Jeff LaMarche's "Beginning iPhone Development". I ordered both, with low expectations. Why low expectations? I knew Apple had only recently rescinded their NDA and that would open the floodgate of iPhone development books. I expected a great race to be the first book out, and I assumed this race would result in a shoddy, rushed, poorly edited book. I was very surprised with "Beginning iPhone Development". I was also surprised by Sadun's book, reviewed here. For me--an experienced software engineer with zilch experience with Xcode, Cocoa, and Objective-C--the level at which "Beginning" delivered its content seemed about right. I never felt lost in their explanations, nor did I feel being talked down to. I also found the writing style upbeat but not annoying. I could detect two different writers, but this was not distracting. Overall, the delivery of the material was A+. One thing I noticed immediately was that the usage of Interface Builder throughout the book. It doesn't dominate the book, but most of the coding projects start off with it. I understand that most developers either love IB or they hate IB. Just be aware that this book uses it. It is possible to use the book without using IB, but you will have to start up some of the projects on your own and skip ahead to the meat of whatever the topic was. I think the authors would do their readers a great service by also showing how to build an application from the ground up, without using IB. This really helps one understand how iPhone applications work. There is a good deal of sample code and descriptive text. I have seen books that tipped the scales favoring one or the other, but "Beginning" found the perfect balance. Although the book provides all its source code online (thanks!), it was still helpful to see the code, even only snippets, interspersed with the context of the text. The book has proven wonderful to read straight through as an introduction to iPhone development, but it is not meant as a reference. Having chapter titles like "Whee!" and "More User Interface Fun" do not aid in locating topics quickly. Luckily the index is more serious. Overall, the tone of the book is that of a tutorial, and it does that very well, but this tone does not lend itself well as a reference guide. Understandably, addressing this would greatly increase the size of the book. The breadth and depth of the material is well balanced for a beginner's book. I had no idea developing for the iPhone was so vast and complex. Information on table views and table controllers can easily take an entire book, so I was not surprised to find that material a little light, even though two chapters were dedicated to it. The samples and discussions don't go much into "advanced" territory, but the book can't be dinged on this because, after all, the title of the book begins with "Beginning". I would love to see a follow-up book from these guys covering advanced topics. For quality, I would have to give another A+. I have not found many typos, grammatical problems, or source code issues. This is amazing considering the quality of most software books. If these two guys were in a hurry to get their book out, it doesn't show in the quality. This book is a must-have for anyone serious about developing iPhone apps. However, reading this book alone will not make you an expert iPhone developer. You still need to dig into other resources to fill in the gaps, but "Beginning" provides a stellar introduction to the basic concepts.

March 11, 2009

Trapping the UINavigationBar Back Button (part 2)

After posting the first part of this article, it was pointed out to me that there is an alternative solution: handle the viewWillDisappear message. This is certainly a more elegant solution, but it does have its drawbacks, which is why I did not use it in the first place. However, it is a valid solution in some cases and can be of use to some people, so I will describe it here. There is a set of messages that get sent to your UIViewController subclass during various events:
- (void)viewWillDisappear:(BOOL)animated - (void)viewDidDisappear:(BOOL)animated - (void)viewWillAppear:(BOOL)animated - (void)viewDidAppear:(BOOL)animated
These are called for the event described in the method name. For example, the viewWillDisappear is called just before the view disappears. Now for my situation, I needed to trap the event of my controller being popped off. The problem with using viewWillDisappear (or viewDidDisappear) is that it is called in different scenarios. When a controller is pushed on top of yours, the viewWillDisappear method is called. And when your controller is popped off, the method is called again. The problem for me is the event is too general. Your view is disappearing, but the reason why it's disappearing is not provided. In my situation, my controller can push other controllers, but I only wanted to know when I was popped off, not pushed on top of. In order to differentiate between the two scenarios, I need additional intelligence in my code to determine which situation I am in. Basically, whenever I am about to push on a new controller, I have to set a flag to ignore the next viewWillDisappear message. Then I need to handle the viewWillAppear message to clear this flag. Not a big deal, but seems like a hassle to handle something fairly basic. Here is the framework needed to support this:
// ---------------------------------------------------------
//
// YourController.h
@interface YourController : UIViewController
{
 BOOL ignoreDisappear;
}
@end

// ---------------------------------------------------------
//
// YourController.m

// this helper method will push a new controller onto the navigation stack
- (void) myPushController:(UIViewController*) newController
{
  // first, set our flag to ignore the next viewWillDisappear message
  ignoreDisappear = YES;

  // now do the push
  [self.navigationController pushViewController:newController animate:YES];
  [newController release];
}

- (void) viewWillDisappear:(BOOL) animate
{
  // do we care about this event?
  if(ignoreDisappear == NO) {
     // this is your back button handle logic
     //...
  }

  [super viewWillDisappear:animate];
}

- (void) viewWillAppear:(BOOL) animate
{
  // clear the flag
  ignoreDisappear = NO;

  [super viewWillAppear:animate];
}
But that was my situation. If your controller is at the end of the UINavigationBar hiearchy, meaning you cannot have another controller pushed onto yours, then you are safe assuming that the viewWillDisappear is being called for the scenario where your controller is being popped off. Take caution, though. Should you later change the behavior of your code to allow additional controllers to be pushed, your viewWillDisappear will get invoked in what may be an inappropriate time. One other thing I should mention. There is this warning in the Apple reference in each of the four methods listed above:
Warning: If the view belonging to a view controller is added to a view hierarchy directly, the view controller will not receive this message. If you insert or add a view to the view hierarchy, and it has a view controller, you should send the associated view controller this message directly. Failing to send the view controller this message will prevent any associated animation from being displayed.
I'm not real clear on what the phrase "added to a view hierarchy directly" means, but there are numerous reported problems related to this. I don't have these problems myself, but they are reported here and here.

March 10, 2009

Enhancing the standard NSXMLParser class

Cocoa offers a "complete" XML parser with the NSXML family of classes. This tree-based parser is fairly sophisticated but it is not included in Cocoa-Touch. In the spirit of small and simple, iPhone developers have the NSXMLParser class to use. This is an event-driven parser, which calls methods on a delegate to handle "events" as it parses through the XML. The basic operation of the NSXMLParser class will not be covered here. This is well documented in the Apple Programming Guide "Introduction to Event-Driven XML Programming Guide for Cocoa". What I would like to introduce is an enhancement to this parser. For very simple XML parsing purposes, NSXMLParser is probably adequate, but it is not going to handle all your XML parsing needs. The biggest problem with this type of streaming parser is that you lose the structure of your data; your call-backs do not provide any context or hierarchy. Thus your parser:didStartElement and parser:foundCharacters methods tend to be very messy with lots of if/else if statements, and you have to keep track of your own context via instance variables.

A better way: a Tree

One solution is to use the parser to build a tree first, then your application can access the tree afterwards. Even the above referenced document alludes to this solution. Below is such a solution, an implementation of the XMLTreeParser and XMLTreeNode classes. The tree that is built uses nodes of type XMLTreeNode. The node is defined this way:
@interface XMLTreeNode : NSObject {
   XMLTreeNode* parent;
   NSString* name;                  // element name
   NSMutableDictionary* attributes;
   NSMutableString* text;           // from foundCharacters()
   NSMutableDictionary* children;   // key is child's element name, value is NSMutableArray of tree nodes
} 
Most of these properties are obvious. The text property is a concatenation of all the free-floating text that were inside an element. For example, the text "Hello World" would be stored in the text property:
<elementName>Hello World</elementName>
The choice of using a dictionary for the children property might be a curious one. The key of the dictionary is an NSString, which is the element name. The value of the dictionary is an NSArray of XMLTreeNode objects. To illustrate, here is some sample XML:
<stuff>
<item attr="value1"/>
<item attr="value2"/>
<note>TEXT</note>
</stuff>
Parsing this would create a root node with one child whose name is "stuff". The "stuff" node has two entries in its children dictionary: one entry for "item" and one for "note". The value for these entries is an NSArray of XMLTreeNode objects. The "item" array will have two nodes, and the "note" array will have only one. The use of a dictionary allows us to quickly search for children, as we'll see later. The downside of this is that the order of the "item" and "note" child nodes for "stuff" will not be retained. This is a side-effect of dictionaries not preserving the order. However, proper XML should not care about the order of the elements, only that the hierarchy is correct. I should note that the order of children of the same element is preserved. For example, when you query the tree, you have no idea if the "item" children will come before the "next" child or not. What you do know, though, is that you will get the item with "value1" before the item with "value2". This is important for the indexing scheme described below.

Building the tree

To create a parser, simply instantiate the XMLTreeParser class:
XMLTreeParser* parser = [[XMLTreeParser alloc] init];
This is a very simple implementation, so as it currently stands, an instance can only parse one XML input. There is no way to reuse a parser instance to parse some other XML. But that would be an easy exercise for the reader to address. Now to start parsing, call the parse method and provide the XML data:
XMLTreeNode* root = [parser parse:xmlData];
Again, this simple implementation only handles XML passed directly to it, not indirectly through a filename. When this returns, your XML has been completely parsed. The beautiful part is you don't have to write all those event handlers. What you get back is the root node of the tree. This node does not have any useful data other than its children. This return will be nil if there is any problem parsing the supplied XML.

Querying the tree

Now you could traverse through the tree manually looking for things. A good demonstration of how to do this is in the XMLTreeNode method "description". This method will recreate the XML from the tree; unparse it, if you will. It will dump out the element name and any attributes. Then it will iterate over all the children and recursively call description on all of them. Please take a look at the implementation of -(NSString*) description in the XMLTreeNode class to see this. For simpler queries, the XMLTreeNode class offers methods to find child nodes. These are the find* methods.

Do you know where your children are?

The most basic find method is findChildren. This simply returns an array of children that match the given element name. Referring back to the simple XML above, the statements below will pull out a list of XMLTreeNodes for the "item" elements:
NSArray* stuffs = [root findChildren:@"stuff"];
XMLTreeNode* stuff = [stuffs objectAtIndex:0];
NSArray* items = [stuff findChildren:@"item"];
OK, this is great, but a little cumbersome. If you know that there is only one "stuff" element, it's a shame you have to get back an array of them. So there is the findChild method, which returns a single XMLTreeNode object:
XMLTreeNode stuff = [root findChild:@"stuff"];
What if there are more than one "stuff" elements? This version of findChild: always takes the first element in the array. If you want a different element, you must use findChild:at:
XMLTreeNode* item2 = [stuff findChild:@"item" at:1];
This will return the second "item" node.

Getting deeper

Pretty cool, but this is still a little cumbersome. What if you have XML with elements 10 layers deep? That means at least 10 separate calls to findChild:. The findChild: method supports paths. Here is an example to get the first "item" node in one call:
XMLTreeNode* item1 = [root findChild:@"stuff/item"];
This basically combines two searches into 1. Now, for your 10-deep XML, you could use something like this:
XMLTreeNode* deep = [root findChild:@"stuff/items/item/something/lists/list/test"];
That's 7 searches in 1.

Pinpoint accuracy

Note that for every step through the path in the above example, if there are more than one element with the same name, this will traverse the first one. If you want a different element besides the first one, then you would have to fall back on the single-step findChild:at: method. Or, you can specify an index:
// I want the 3rd doodad in the 4th thingamajig of the 2nd whatchacallit
XMLTreeNode* doodad3 = [root findChild:@"whatchacallit[1]/thingamajig[3]/doodad[2]"];
Remember that these indeces are 0-based, so they are one less than what you'd expect. Now you can dig around in your XML to your heart's content. Be aware that if the query fails anywhere along the path, it will return nil. So if you get nil back from a find method, you can assume that it didn't find what you were looking for.

Optmization

If you find yourself traversing over the same ground over and over, you can move your starting point. There is no need to always start at the root node. Each node has these find methods, so you can get a new starting point and search from there:
XMLTreeNode* searchRoot = [root findChild:@"stuff/same/old/path"];
XMLTreeNode* newThing = [searchRoot findChild:@"cool/new/thing"];
Think of this as changing your current directory so you don't always have to type in the full path.

Room for improvement

That's it for now. This simple implementation could have a lot more features added. For instance, you could pluck out attributes from a path like this:
NSString* value = [root findChildAttr:@"stuff/item[1].value"];
This would get the attribute "value" from the second "item" element under "stuff". But what it does now is all that was required for my current project. Other enhancements could follow as the needs arise.

Download

You can access the source code to XMLTreeParser and XMLTreeNode here.

March 7, 2009

Trapping the UINavigationBar Back Button

I recently had a need to trap the back button tap on the navigation bar in order to do something before popping to the previous controller. However, I soon discovered that this was not possible. The standard back button is a "special" button that developers cannot override, at least not with the current public SDK.
The only thing you can change on the standard back button is the text. By default, this is the title of the controller. You can change it with code like this:
[self.navigationItem.backBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"New Title" style:UIBarButtonItemStyleBordered target:nil action:nil]; 
The important but subtle thing about a controller's backBarButtonItem property is that it is not the back button displayed when that controller is on top; it is for other controllers that are pushed on top of it. In other words, if you have a "root" controller whose title is something very long, you set its backBarButtonItem title to something shorter. Then when other controllers are pushed on the stack, they will use this back button item as their back button. This is unintuitive at first glance, but makes sense when you think about it: you can redefine the back button title once, and all other controllers that are pushed on top will use this back button.
Even though there are four parameters to the initializer, only the title is used for this "special" back button. The style specified seems to be ignored, as are the target and action parameters. Because of this, you cannot insert your own event handler for the back button, which is what I needed to do.
After some digging and research, I have a solution. It is not a pretty solution, but it's good enough.
The navigationItem property of a UIViewController has another property called leftBarButtonItem. This, like the backBarButtonItem, is a UIBarButtonItem object. If you set this to something other than nil, then this new button will replace the standard back button item. So now, I can get my custom back button event handler to work, because this one honors the style and the target/action parameters:
- (void) viewDidLoad
{
   // change the back button and add an event handler
   self.navigationItem.leftBarButtonItem =
   [[UIBarButtonItem alloc] initWithTitle:@"Pages"
                                    style:UIBarButtonItemStyleBordered
                                   target:self
                                   action:@selector(handleBack:)];
}

- (void) handleBack:(id)sender
{
    // do your custom handler code here

    // make sure you do this!
   // pop the controller
    [self.navigationController popViewControllerAnimated:YES];
}

If you still want this button to behave like a back button, it is important to put the popViewControllerAnimated: call in your handler. I should also say that this leftBarButtonItem should be set on the controller who will actually show the back button, unlike the backBarButtonItem. Now the reason I stated this wasn't a perfect solution is the following: The "special" back button looks different. Here is what the standard backBarButtonItem looks like:
Yet, the leftBarButtonItem looks like this:
I have been unable to force my custom back button to have the different shape. This is another indication that this back button object is treated specially that is not exposed in the SDK. Other than this little aesthetic discrepency, this approach met my needs. Hopefully it will meet yours as well.

March 6, 2009

A General Purpose Table Form Editor

Note: You may have come here from another link. Be aware that I have updated this package and can read about it here. Sometimes your iPhone applications need the ability to enter or edit data on a form. For example, here is a screen from the contacts app when adding a new contact:
If you have ever coded up the UITableViewController class to do this sort of the form editing, you know that this coding task is very tedious and time-consuming. Wouldn't it be nice if there was a general purpose, reusable class for doing this? Here I present such a class.

Features

The TableFormEditor class is intended to be used within a UINavigationController. In other words, you allocate a TableFormEditor instance, configure it, then push it on top of the navigation stack. The TableFormEditor will create a grouped table and the first section will contain rows with UITextField objects to display and allow editing of data. These rows can optionally contain labels that identify what each row represents, or you can have no labels as in the above example. Optionally, you can configure a delete button to appear at the bottom of the form. This is intended to allow the user to delete the record. The text on the button is configurable. The action of the delete button will ask for confirmation before continuing.

Basic Usage

Here is how the TableFormEditor works:
  1. You allocate and initialize a TableFormEditor object.
  2. You configure various other aspects of the object, such a your fields names and what your data is.
  3. Then you push the object onto the navigation controller stack. At this point, the TableFormEditor is in control.
  4. You can bounce around the fields and edit to your heart's content. When you are done, you hit the "save" button. This will pop the controller off the navigation stack and send a message to the delegate you provide, passing the new data. It is up to your delegate to do something with this data.
  5. If you decided to hit "cancel" instead, the controller is popped off the navigation stack and a message is sent to the delegate.
  6. If you have a delete button enabled, and this button was selected, then the controller is popped off the stack and a different message is sent to the delegate.
Each of these steps is discussed in greater detail below.

Initialize

There are two schools of thought concerning initializers. Some like to force the user to pass all required parameters, often resulting in very long initializer statements. The other side prefers simple initializers, and the remainder of the parameters can be set as properties. The benefit of the former is that there will be no forgetting to set a required parameter. However this approach is not very flexible; any new additions or changes to the class often require the initializer to be changed. Very specialized initializers create fragile code. Therefore I have opted for the approach of simple initializers and setting properties. True, some properties must be set, otherwise the resulting code will be next to useless. However, I will document clearly which properties are mandatory and which are optional. There are two modes the TableFormEditor can be in: edit mode or add mode. In edit mode, you are editing/viewing existing data. In add mode you are adding a new record. What mode you're using has subtle differences in the default behavior. For instance, in add mode there isn't a delete button displayed, since there is no record yet to delete. To initialize for add mode, use the initForAdd: method:
TableFormEditor* add = [[TableFormEditor alloc] initForAdd:self];
All you provide is the delegate, typically self. The delegate must conform to the protocol TableFormEditorDelegate:
@protocol TableFormEditorDelegate
@optional
- (void) formDidCancel;
- (void) formDidSave:(NSMutableDictionary*) newData;
- (void) formDidDelete;
@end
All these methods are optional, but obviously without the formDidSave: you don't have very functional software. To initialize for edit more, use the initForEdit: method.
TableFormEditor* edit = [[TableFormEditor alloc] initForEdit:self];
Just as with the add mode, you pass in a delegate to handle the same protocol.

Configure

Now we'll discuss all the various properties you can and must set to configure the form editor. Note that even though some of these are labelled as "required", that doesn't mean your program will crash without them. But you may not see very interesting results.

Required

NSArray* fieldNames
This is a list of NSString objects which are the field names. Each field equates to a row in the form. This list serves several purposes:
  1. It determines how many rows to create in the table,
  2. It provides the labels to preface each row with,
  3. It provides the order to present the fields in the form, and
  4. It provides the key names to be used in the data dictionary.
Note that the data you provide in the data dictionary (described next) must use keys that match these names.
NSMutableDictionary* fieldData
This is the data. Depending on the mode the object was initialized with, this data has different purposes. If initialized in edit mode, then this data represents the current data. The keys are the fieldNames given above. The values are the data. Both of these are assumed to be NSString objects. If the form editor was initialized in add mode, then this data represents the placeholder text to put in each row. The example above shows placeholder text on each row. This text gives the user a hint what's to go in that field, and it disappears as soon as you start entering text. If you don't want placeholders, don't set the data. This dictionary is copied (deep copy), because TableFormEditor may modify the values.

Optional

id <TableFormEditorDelegate> delegate
This is the delegate to handle the callbacks when the user wants to exit the form. This is set up in the initializer, but if you ever need to change it, you can via this property.
BOOL showLabels
When the form rows are displayed, by default each row has the field name displayed on the left as a label. This may be overkill, especially if you use placeholders, so you can turn off the labels by setting this property to NO.
BOOL allowDelete
In edit mode, by default, this is set to YES. When YES, a delete button will be displayed after the form. Set to NO if you don't want to see the delete button. If you are in add mode, and for some reason want the delete button, you will have to force it by setting the property to YES.
NSString* deleteButtonLabel
If a delete button is displayed, this is the text used. If unset, it will use "Delete".
NSString* saveButtonLabel
The TableFormEditor puts a button in the right position of the nav bar. This button is for saving your changes and exiting the form. By default, the button text is "Save", but this can be modified with this property.
NSString* cancelButtonLabel
The TableFormEditor also puts a button in the left position of the nav bar. This is for canceling any edits made and exit the form. By default, the button text is "Cancel". Modify with this property.
NSInteger firstFocusRow
By default, the form editor will place the focus (first responder) on the first row (row 0). This action brings up the keyboard automatically. If you wish to have the focus placed somewhere else first, set this property to the row you want. If you don't want any row given focus (and thus no keyboard will come up automatically), then set this property to -1.
NSString* title
This is the title string placed in the middle of the nav bar. Default value is blank.

Future

In future releases of TableFormEditor, I will be adding more configuration options, like options to configure the keyboard and how the text fields behave.

Download

The source code to the TableFormEditor class can be freely downloaded here: version 1.0 . This software is covered under the MIT License.

March 5, 2009

Tab Bar Icons (part 2)

In part 1 of this article I talked about general guidelines for the icons in a tab bar view. In this part, I will go into specifics as to how to actually create tab bar icons. One thing I noticed is there is little documentation or help out there on the Web regarding tab bar icons, so hopefully this information will be beneficial. Since I wrote part 1, I have found that the Apple documentation may be incorrect when it states that the icons should be 30x30 points. By making icons exactly that size, I found the icons were too big and they were truncated. I have since changed over to icons the size of 30x30 pixels and that seems to work well. In fact, the iPhone Human Interface Guidelines does specify "about" 30x30 pixels. There definitely is extra space to grow, but 30x30 pixels will be my starting point and only increase in size if I need the extra space. As a reminder, we're looking to create these type of icons:
  1. PNG
  2. 30x30 pixels
  3. no color, as alpha values determine the image and shading
  4. transparent background
Drawing without color is basically impossible, so I use black & white. Even the black and white colors are thrown out since the tab bar just uses the alpha channel data. By using black and white, it allows me to draw without fussing with colors. One important thing to realize is that tab bar icons are inverted. Things that you draw black come out white; things that you draw white are black. If you're old enough to remember film negatives, it is the same concept. You might be tempted to make your icons already inverted so that they look "normal" in the tab bar, but I believe this would be a mistake. Judging by the Apple application tab bar icons (You Tube and iPod), it is expected for the icons to be negatives. Another important point is that when you draw a black line, you are not applying a black color on top a white canvas. Drawing with alpha channels is a bit of a paradigm shift. Think of this way: you have a black canvas. You draw on this canvas using white paint. If you use really thick paint (a high alpha value), then this will show as bright white. If you thin your paint (a low alpha value), then some of the black from the background will bleed through, making is less bright and more gray. This is exactly what an alpha value is: it is the degree of transparency. A low alpha value means it is completely transparent. Using an alpha value of 0 means none of the white shows. As you increase the alpha value, the white color becomes less and less transparent, thus becoming brighter. When the alpha value is at its max of 255, it is completely opaque and you cannot see through the paint at all. Thus it shows up as white. Hopefully you aren't totally confused by now! An example will help demonstrate some of these concepts. Let's say we want to create a typical "documents" icon, three pages of paper stacked on one another. The top page could have some super tiny text on it. A "normal" icon for this would look like this:
When this is displayed in the tab bar, it looks like this:
See how this is a negative of our original icon? Everywhere you see white means those lines have an alpha value of 255. If this negative effect drives you crazy, you probably could draw using white paint on a black background, and then remove the background just before exporting the image. Again, the black and white colors are meaningless here; you could use purple and pink if you wish. It is the alpha values that are important, so make sure your white paint (or pink) has a high alpha value. So, how do we make such an icon? Icons can be created with numerous tools, but I will focus on one that I feel is best suited for this task, and the price is right (free). Inkscape is a Windows, Linux, and Mac OS vector graphics editor. It is freely available and can be found on the web here: Inkscape homepage. If you are installing Inkscape on Leopard, you may have a problem with it hanging while caching fonts. This is a bug that is easily fixed by going into the terminal and typing this:

$ mkdir ~/.fontconfig

After this, it should start normally. I didn't experience this when I installed version 0.46, so this may be fixed already. When you fire up Inkscape, you see a blank canvas that looks like this:
Edit the properties of this to get what we need. Select the menu File>Document properties... Set the size in the "Custom size" box. Make sure the units is "px" (pixels) and the width and height both to 30. You might also want to disable snapping, which I find annoying when trying to position drawings exactly where I want them. This is the "Enable snapping" checkbox on the snap tab. Close the properties window. Now, your canvas is probably really small, so select the menu option View>Zoom>Page to zoom the canvas to fit the window. To get into black and white mode, select grayscale from the color palette on the bottom. You can bring up the color palette window by clicking on the little triangle I circled in red in the lower right of the picture above. Inkscape automatically has a transparent background, so there is nothing more you need to do there. The area inside the gray border is your canvas. Even though this is white, remember it will be black on the tab bar. So the first thing we want to do is create the top page. Choose the "rectangle and square" tool on the left tool bar, or hit f4. Click where you want the top left corner and drag. Put the rectangle a little low and to the left to leave room for the other pages. We can center everything later. Once you have your rectangle, we need to fix it up. Select menu item Object>Fill and Stroke The fill is what is inside the object, and the stroke is the outline around the edge. What we want is a stroke with a high alpha (255) so that the border of the page shows as white, but the fill should be black, meaning an alpha of 0. The diagram below shows setting the fill alpha to 0:
Alpha values are the A at the bottom of the RGB tab. At the far left of the alpha scale it shows a checkerboard pattern. This indicates transparency. As you go higher, the checkerboard disappears and black replaces it. You could also hit the "X" button to remove the fill altogether, which will result in a transparent fill. Now we show the properties for the stroke paint. We want the stroke to have a high alpha value so it looks white in the tab bar:
You can also go to the "Stroke style" tab and play around with the line thickness and line pattern. Now we need to put pages in the back. You might be thinking that since this editor can do layers, you can simply copy the top page, shift it over some, and then put that layer underneath. Well, that won't work (at least I don't know how to make it work). Because the top page has a transparent fill (alpha of 0), the border of the bottom page shows through. What you end up with is this:
This is not what we want. Here lies a problem with using alpha values. It makes it very difficult to layer objects. It's best to think of creating a "flat" picture. All impressions of depth are fake. So instead of layering another page below the first one, we cheat and just draw the outline of the page edges next to the top page. When you're working with icons, it's also best to turn the grid on, which makes drawing lines easier. View>Grid. Use the Bezier curve tool to draw straight lines. Click once to select the starting point and move your mouse (don't hold the mouse button down). When you want to change direction, click to set another point. Double click to create your final point. For the right angle shown below, it consists of:
  1. click to start, then move mouse right
  2. click to form angle, then move mouse down
  3. double click to end
Draw the third page the same way. Or to make it easier, duplicate the second one via ctrl-D, then drag the third one into place:
I will leave the "text" on the top page as an exercise to the reader. (Hint: use the bezier curve tool to draw lines, then adjust the stroke style of each line to be dotted.) Let's save it and try it out. To save as a PNG file, it is a bit non-intuitive: File>Export bitmap... select "Page" for the Export area select "Browse" button and choose location to put file. Find folder and type in filename. Inkscape will provide the .png extension for you. Hit "Save" This doesn't actually "Save" the file. You then need to hit "Export" button What you have is an icon that looks like this:
When I insert this into my tab bar, I get this result:
Notice how the large icon doesn't look like much, but when it is shrunk down to icon size, it doesn't look too bad. You even can see the 3-D effect that is not apparent in the full-size version. This is due to the loss of detail when it gets smaller, and your eyes/brain fill in the gaps. Think of it as the "Monet affect", where his paintings just look like a bunch of dots up close, but as you step back, you lose detail and the picture morphs into something beautiful. To help you visualize your icon as you are drawing your image, Inkscape has an icon preview mode which shows your current drawing in various icon sizes. Select menu View>Icon Preview... to bring up that window. That should be a good start to get you going on creating your own tab bar icons. I strongly urge you to learn more about Inkscape; it is a very powerful tool and can also handle all your other icon needs. Good luck and create something beautiful!