Posts Tagged ‘xcode’

Striking the Balance: Interface Builder vs. Code

In my last post here, I touched a little on the need for a balance between building user interfaces in code and using IDE tools like Interface Builder.  Today I would like to develop the importance of that concept a little further.  People tend to become comfortable with one method or the other after they’ve been developing applications for any period of time, and they start to gravitate heavily that direction.  This can often lead to complex code or bloated NIB files in order to accomplish a task, simply for the sake of using the developer’s favorite tool.

IB vs. Code

I have cooked up a small list of advantages/disadvantages for IB and code-based UI layout:

  • Interface Builder is GREAT For
    • Quick and Precise static layout of subviews in containers
    • Easily applying parameters to views (styles, fonts, scroll parameters, etc.)
  • Code Layout is GREAT For
    • Dynamically adding/removing/moving views around in a container
    • Creating conditional view layouts based on user decisions

Building iPhone UI in a balanced fashion involves exploiting each of these strengths through some simple rules:

  1. Static element properties are easier to SET and to READ LATER in Interface Builder
    • Elements such as pickers, buttons, text fields, etc. will have parameters that do not change; such as (usually) font, backgrounds, and even frame size.
  2. Use NIB files as archives for elements that you need to lay out dynamically, but don’t want to waste the time defining in code.
    • Use the ease of IB to create, size up, and define properties for each element.
  3. Code isn’t as scary when you need only add/remove/relocate/resize
    • Many people shy away from code layout when the volume of code required looks daunting.
    • If IB does all the creation work, a few functions like addSubView, removeFromSuperView, and setFrame can get you pretty far.

Simple Example

In this example, we have a simple view controller that includes a grouped table view.  This table view needs to have a UIButton inserted at the bottom of it to perform an action associated with this view.  In addition, a picker view must be defined that will slide into place when the user needs to make a change in one of the cells.  There are two primary issues with this layout that make it difficult to do strictly in IB:

  1. UITableView is laid out with help of the delegate methods at runtime.  A button cannot just be dropped into the NIB at the table footer.
  2. The UIPickerView is a temporary visitor to this view during editing, it cannot live permanently as part of the view outlet

Some might say that these two issues require you to do the bulk of the work in code.  You could…but maybe there’s a balance that can save us some time.

With the simple rules in mind, let’s start in Interface Builder:

We want to leverage IB as much as we can for doing the things it is best at.  So we lay out the main view, which is a complex view including both our UITableView and a UIScrollView that might serve some other purpose.  Each of these is to be laid out so they fill a portion of the main screen.

Inside the NIB archive (but not the main view), we also have our picker element (a UIDatePicker) and our UIButton.  The UIButton has been wrapped in a UIView to make it easier to fit in the table’s footer.  The button we want is only 280×37 pixels, and we wrap it in a UIView that is 320×44 pixels so it fits nicely and centered once added to the table.

We also make the necessary property changes here to each element.  The UIButton is given text, and its frame’s background cleared.  The UIDatePicker is set to report only month/day/year information, and all delegates/targets/actions are connected as outlets.

All of these actions are very easy to do in IB, so we leverage its power to do them. This format makes the properties easier to set, and to read later when we have to figure out what the heck our past ‘selves were thinking!

After building the interface elements, we can do the dynamic parts in code…and the code is extremely simple. The relevant methods are shown below:


1
2
3
4
5
6
7
8
9
10
@interface BlogPostViewController : UIViewController {
UITableView *theTable;
UIDatePicker *thePicker;
UIView *buttonFooter;
}
-(IBAction)buttonAction;
@property (nonatomic,retain) IBOutlet UITableView *theTable;
@property (nonatomic,retain) IBOutlet UIDatePicker *thePicker;
@property (nonatomic,retain) IBOutlet UIView *buttonFooter;
@end
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@implementation BlogPostViewController

- (void)viewDidLoad {
    //Using code to link the IB button into the table view
    [theTable setTableFooterView:buttonFooter];
}

- (IBAction)buttonAction {
    //Using code to dismiss the picker as an editor
    [thePicker removeFromSuperview];
}

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
    [tableView deselectRowAtIndexPath:indexPath animated:YES];
    //Using code to display the picker as an editor
    [self.view addSubview:thePicker];
    CGRect screenRect = [[UIScreen mainScreen] applicationFrame];
    CGSize pickerSize = [thePicker sizeThatFits:CGSizeZero];
    CGRect destinationRect = CGRectMake(0.0, screenRect.origin.y + screenRect.size.height - pickerSize.height,
                pickerSize.width, pickerSize.height);
    [thePicker setFrame:destinationRect];
}

@end

Now we only needed one line of code to attach the UIButton, which is used to dismiss our picker, to the table footer. The removal action of the picker is also a single line of code. The example brings up the picker when a user selects any table cell. This code is also very simple, with the most complex part being the calculation of the y-coordinate to use for placing the picker.

This example is not production-ready.  In most cases you would add the subview (like the picker) off-screen (make origin.y larger than the screen height), and then animate a frame change bringing it into view.  But the basics are here to show how simple the code can be when IB handles all the setup work.  Conversely, it would have been extremely difficult to try and lay out the button and picker to be dynamic using only IB as our UI tool.

Don’t Get Lost! Tag your Comments!

Project Management, like version control, is something every developer should always be doing at some level in their work…period.  Regardless of the size and scope of a project, basic task management keeps things moving in the right direction.  However, I’m not saying that this should require some elaborate process either.  Oftentimes, a complex process can drive efficiency into the floor.  There is a common standard among major Integrated Development Environments (IDEs) that allows a developer to do a little bit of their own “in situ” project management using code comments…and I believe that this feature is drastically under-utilized.

These comment tags are recognized by IDEs and can be used to quickly and effectively build simple To-Do lists to help you complete all your objectives, or even just place markers in the source so you remember where to go back to.  Because of the scope of my recent work, and of this blog, my examples will focus on Xcode and Eclipse.  However, the general principle applies to many others out there.

Creating Comment Tags

There are two common keywords that work across IDEs: TODO and FIXME.  By placing comments like the following in your code, your on your way to managing what you need to do next, and where you need to do it:

// TODO: Add a method to handle incoming floats
// FIXME: The app crashes here when releasing the object

Placing these at the location where they are relevant will save you time and headache in situations like these:

  • Picking up where you left off the day before
  • Marking a location in a file when you have to move to another while tracing
  • Fleshing out stubs (start with a list of TODO comment before you write any code)

While this is a nice and simple procedure to follow, its the IDEs reaction to these that really makes them useful…

Xcode

Xcode recognizes the following tag styles in comments:

// TODO:
// FIXME:
// ???:
// !!!:

Note the colons on the primary tags, because Xcode needs them.  Notice also that there are two more generic tags that can be used to further subdivide your task listings (??? and !!!).  With these tags in your code, Xcode helps you out by placing markers in the file listing (where you also find your #pragma mark statements).

Each tag is organized underneath the method where it is located.  You may also choose to group your tags at the beginning of the file, as was done in the example above.  This cleans up the listing and puts all the tags in one place, but you don’t gain the ability to click and be transported to the relevant location.  It’s up to you!

Eclipse

Eclipse recognizes the following tag styles in comments:

// TODO
// FIXME
// XXX

Note that Eclipse provides one more tag to further assist you in subdividing your tasks (XXX, which must be capitalized).  With these tags placed in comments, Eclipse does three things for you:

  • Include a blue marker on the scroll bar in the source view (in the same location as yellow markers for warnings and red markers for errors)
  • Include a clipboard task marker on the left bar in the source view
  • Provide a Tasks View where you can view all active tags in all the files of your project at one glance.

The scroll bar marker feature is what makes using TODO or XXX tags as placeholders useful.  You can tag a location, move down to a method you need to finish, and then pop back to where your tag is.

The Tasks View may not be visible by default in Eclipse, but can be brought up from the Window -> Show View -> Other… menu (there is an option called Tasks).  Here you will be presented with a table that points out each tag and its location.  FIXME tags are marked with the exclamation point by default as well.

For the same reasons that single developers should always use version control, basic management of your tasks is crucial to happy and sane development.  In my opinion, this is a great start and it doesn’t get any simpler than this.

Adding Core Data Existing iPhone Projects

Core Data is a very powerful framework that Apple provides to developers for persisting data in their applications.  The primary advantage that is provided by Core Data is the ability to leverage efficient data storage technologies like SQLite, without forcing the developer to think in terms of query language; Core Data allows a developer to work with the data model in terms of objects.

There are many great books and articles on the proper use of Core Data; including Apple’s Core Data Tutorial.  The purpose of this article is simply to step the reader through adding the necessary piece to get Core Data into a previously created project; I defer to those other writings to describe the best techniques for Core Data use.

Apple has made the process of using Core Data in new projects very simple.  From the New Project… screen, many project templates include a checkbox titled Use Core Data for storage that tells Xcode to build the basics for Core Data into the project you create.  But what happens if you decide that Core Data’s advantages are going to help you, but you have already have your project set up in Xcode?  Where’s the button in the IDE to add Core Data to an existing project?

Below is a set of instructions on creating the context necessary to start using Core Data in your existing app.  In the code blocks, bold text is code that should already exist in your project.

Add the Missing Files

There are two files that must be added to your Xcode project in order to use Core Data; CoreData.framework and the .xcdatamodel

  • CoreData.framework
  1. From Xcode, control-click on the Frameworks folder inside the Groups & Files pane
  2. Select Add -> Existing Frameworks…
  3. Locate CoreData.framework, select it, and click the Add button
  • .xcdatamodel
  1. From Xcode, control-click on the Resources fold inside the Groups & Files pane
  2. Select Add -> New File…
  3. Select Resource from the iPhone OS group
  4. Select Data Model and click Next
  5. Give the file a name (the project name is a good choice) and click Next
  6. If your project does not have existing model classes that you would like to put into the data model, skip to step 7.  Otherwise, you may do so by selecting those classes on this screen and clicking Add to place them in the Selected Classes pane.
  7. Click Finish

Create the Missing Objects

Once all the necessary files are put into the project, the appropriate objects and methods need to be added to the application.

AppDelegate.h

Declare three new objects in the application delegate’s header file for the ManagedObjectModel, ManagedObjectContext, and PersistentStoreCoordinator. A convenience method, applicationDocumentsDirectory, is also defined to return the location of the application’s data files:

@interface AppDelegate : NSObject <UIApplicationDelegate> {
  NSManagedObjectModel *managedObjectModel;
  NSManagedObjectContext *managedObjectContext;
  NSPersistentStoreCoordinator *persistentStoreCoordinator;

  /* (...Existing Application Code...) */
}

@property (nonatomic, retain, readonly) NSManagedObjectModel *managedObjectModel;
@property (nonatomic, retain, readonly) NSManagedObjectContext *managedObjectContext;
@property (nonatomic, retain, readonly) NSPersistentStoreCoordinator *persistentStoreCoordinator;

- (NSString *)applicationDocumentsDirectory;

/* (...Existing Application Code...) */
@end

AppDelegate.m

Implement applicationDocumentsDirectory, and explicitly write accessor methods for each new property as opposed to simply using the @synthesize keyword.  Note in the persistentStoreCoordinator accessor there is a location where you must name the SQLite file used for the store; this should most likely be your project name. Remember to properly release each object in dealloc:

@implementation AppDelegate
//Explicitly write Core Data accessors
- (NSManagedObjectContext *) managedObjectContext {
  if (managedObjectContext != nil) {
    return managedObjectContext;
  }
  NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
  if (coordinator != nil) {
    managedObjectContext = [[NSManagedObjectContext alloc] init];
    [managedObjectContext setPersistentStoreCoordinator: coordinator];
  }

  return managedObjectContext;
}

- (NSManagedObjectModel *)managedObjectModel {
  if (managedObjectModel != nil) {
    return managedObjectModel;
  }
  managedObjectModel = [[NSManagedObjectModel mergedModelFromBundles:nil] retain];

  return managedObjectModel;
}

- (NSPersistentStoreCoordinator *)persistentStoreCoordinator {
  if (persistentStoreCoordinator != nil) {
    return persistentStoreCoordinator;
  }
  NSURL *storeUrl = [NSURL fileURLWithPath: [[self applicationDocumentsDirectory]
                      stringByAppendingPathComponent: @"<Project Name>.sqlite"]];
  NSError *error = nil;
  persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc]
                                 initWithManagedObjectModel:[self managedObjectModel]];
  if(![persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType
                                 configuration:nil URL:storeUrl options:nil error:&error]) {
    /*Error for store creation should be handled in here*/
  }

  return persistentStoreCoordinator;
}

- (NSString *)applicationDocumentsDirectory {
  return [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
}

/* (...Existing Application Code...) */

- (void)dealloc {
  [managedObjectContext release];
  [managedObjectModel release];
  [persistentStoreCoordinator release];

  /* (...Existing Dealloc Releases...) */
}
@end

ViewController.h

The following code needs to be added to the interface of whatever view controller will interact with the Core Data objects.  The FetchedResultsController and another instance of the ManagedObjectContext:

@interface ViewController : UIViewController <NSFetchedResultsControllerDelegate> {
  NSFetchedResultsController *fetchedResultsController;
  NSManagedObjectContext *managedObjectContext;

  /* (...Existing Application Code...) */
}

@property (nonatomic, retain) NSFetchedResultsController *fetchedResultsController;
@property (nonatomic, retain) NSManagedObjectContext *managedObjectContext;
@end

ViewController.m

Don’t forget to synthesize the new properties for their accessor methods:

@implementation ViewController
@synthesize fetchedResultsController, managedObjectContext;

/* (...Existing Application Code...) */
@end

Add Links and Use Core Data

The managedObjectContext object has now been created as a property in both the AppDelegate and ViewController classes.  The declaration of managedObjectContext in the ViewController must reference the AppDelegate, which is the only place that object should be allocated.  Something like
ViewController.managedObjectContext = self.managedObjectContext;
should exist in the applicationDidFinishLaunching method of the AppDelegate to accomplish this.

Conclusion

Your project should now include all the necessary objects to perform Core Data operations within the application.  You can create the data model using Xcode’s built-in editor to define entities, properties, and relationships.  Then, work with the Core Data framework to store and fetch your persisted object data.