Archive for March, 2010

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.

UIScrollView has a Secret

Sometimes, it’s not always clear in iPhone development when one should create their interface elements using Apple’s Interface Builder (IB) application, or directly in application code.  Oftentimes, with more complex view issues, the answer tends to be a combination of both.  When doing this, however, it is not uncommon for little traps to come out of hiding that can be hard to trace down.

One such instance lives within the UIScrollView element.  The developer may choose to lay out the element in its superview using IB to ensure proper placement and sizing relative to the rest of the view.  However, because of the dynamic nature of content placed within UIScrollView, the population and sizing of the content view may take place in code.  When using IB to lay out a UIScrollView, the Attributes Inspector will give the developer options similar to what is shown on the right.

The Scrollers Are Hiding

I want to draw your attention to the checkboxes named “Horizontal” and “Vertical” by Scrollers heading.  As many of you may know, these options turn the scroll bars that appear on the sides of the view during a scroll operation ON and OFF.  By default, these boxes are checked and the scroll bars are active.

Now for the secret: When those scrollers are active, the unarchived version of your object will have two extra UIView objects in your subviews array…one for each scroller bar.  If you were to look at the object in the Xcode Debugger, you would see that the type of these objects is actually _UIStretchableImage (which is a private UIView subclass).

This means that, if you plan to iterate over the subviews array at any time, your code may throw an exception on unrecognized selector or some other method because it is accessing objects in that array you may not have known existed (and don’t understand your custom message).

Secret #2: _UIStretchableImage is also a subclass of UIImageView, or at least to the point that it will return YES from a call to [view isKindOfClass:[UIImageView class]].  Therefore, if you like to use UIScrollView for images and think you can outsmart this by checking the type of each subview object, you’ll have to use another method.

This can also have consequence if you plan to loop through the subviews array to clear the content with [view removeFromSuperview], so be aware of that as well. You may just lose your scroll bars without realizing why!

Editorial: Your Reviews May Be Hurting You

Normally, I target developers in my posts, but today I had a special message that I wanted to send out to the user community.  I’ve noticed a trend over the past year that troubles me some about the way users are using their reviewing power in places like the App Store or the Android Market.

Users, the power you’ve been given to review mobile applications carries more weight than I think many of you give it credit for.

The consequences of an application review should not be taken lightly.  Believe it or not, other users do look at an app’s comments (or at least the top five at the time) and it’s current rating.  This can be a good thing, but it behooves me to point out that there are certain things that do not belong in reviews as they can prematurely cripple and application’s, or its developer’s, success.  Flagrant user of 1 star ratings for certain reasons will end up hurting the user community if used improperly, because many of the developers will choose not to develop more apps or make updates to those they’ve already published.

Here is a list of things that I believe low ratings and review comments SHOULD be used for:

  • Lack of developer feedback
    • I 100% agree that if you contact a developer with a question on an application, and get no response within a few days, you have the right to rate low.  Active applications need developer support.
  • Poor application quality
    • Try to distinguish poor user experience from a missing feature.  Just because an application doesn’t have a feature you think would be cool, doesn’t mean it is somehow broken.

Which brings me to things low ratings SHOULD NOT be used for:

  • Feature requests
  • Bug reports

A good developer enjoys hearing feedback about their application, and ways that they can improve the user experience in future releases.  This is why application markets provide developer contact information, so you may contact them directly with this type of feedback.  The salient point here is to give the developer(s) an opportunity to support you first.  Your helping them by letting them help you.  Often, a missing feature isn’t missing…just hidden.  By contacting the developer, you’ve learned where the feature is, and they’ve learned it needs to be more obvious where to find it next time.

The same goes for bugs.  App development is a small-scale business oftentimes, and developers can’t be as thorough with software testing as we all would like.  So, sometimes the user community ends up becoming beta testers on the first 1-2 releases of an application.  Again, direct contact will get these issues solved more quickly.  You will most likely be waiting longer if you post a bad review and expect that the developer saw it.

So, I guess the bottom line here is open dialogues with the app developers and, in the long run, you’ll be glad you did.  If the developer ignores your requests, then use that veto as it was intended.  Developers who ignore their users don’t have a place in this community anyway, in my humble opinion.

App Released: Ohm’s Best

Ohm's Best

Today Wireless Designs released Ohm’s Best for the iPhone, available for download on the App Store.

Ohm’s Best is an electronics calculator application that allows electronics technicians and electrical engineers to do circuit calculations using real component values that one can actually find and purchase.  This functionality puts reduced design time and frustration right into your pocket.

Check out the full press release at iPhoneAppReview.com

If you download the app, please send us feedback via email or twitter.  We love to hear how we can improve things to help you better!

What’s awakeFromNib for?

Sometimes traversing through the life-cycle callbacks of view loading on the iPhone still gives me a headache.  Especially when using Interface Builder (IB) as part of the UI development, finding the right method to override in order to get your custom drawing/init/etc. done can be more difficult than it may seem.  Recently, I was lost again in the quagmire of methods to choose from when building a custom UIView subclass that provided some basic look-and-feel customization over the standard view structure  The answer that was revealed to me is today’s tip.

awakeFromNib

The commonly recommended place to do custom initialization for custom views is in the

1
initWithFrame

or

1
initWithCoder

methods.  When you are defining parts of your UI in IB,

1
initWithCoder

is the one of these that is called by the nib-loading code.  However, it is usually the case that any work done with members declared as IBOutlet are still nil at this point, so your custom changes will fall on deaf ears.  This drives many developers to put custom initialization code in the

1
viewDidLoad

method of the view controller…because it works.  The problem here is a conceptual one.  You are no longer encapsulating all of the custom functionality for your view in the view class, some of the required custom code is leaking out into the controller.  You are now relying on code in the custom view’s container to provide some of the needed functionality to properly initialize the view, which makes the code brittle.

Enter

1
awakeFromNib

; a UIKit addition to NSObject that receives a message from the nib-loading code after all the outlets and actions have been connected for all objects inside the nib file.  Apple states that “When an object receives an

1
awakeFromNib

message, it is guaranteed to have all its outlet and action connections already established.”  This means you can now comfortably make some changes to properties that would have otherwise been nil during the init phase.  This allows you to put that custom code back into the view subclass, properly encapsulating the functionality.