Posts Tagged ‘UIKit’

Signing Up UITableView


Thanks mainly to Apple’s design choices when building the Settings app for iOS devices, the Grouped-Style UITableView has become the de facto standard UI for app developers to use when creating settings or account signup screens within their own applications.  In many ways, this is a very helpful standard to follow due to the amount of styling that we developers get for free.  However, regardless of styling, UITableView is a widget designed to provide a memory-efficient and user-performant method of displaying large sets of data in a scrollable list.  The primary technique behind this design is the reuse, or recycling, of the UITableViewCell objects that displays the data to the user.

Essentially, UITableView will only create enough cell objects to fill the screen.  Then, as rows scroll off the screen, those cells get reused to present the data for the next item scrolling on-screen.  For a standard 44px UITableViewCell, this means there are a maximum of about 10-11 cells in memory at any one time.  Despite the performance benefit this creates for large data sets, it creates some unwanted side-effects when developers use UITableView to create interactive elements for data entry.

If you design signup tables like the screenshot, in which the table is never larger than the content view, even with an input view visibile.  These issues have likely never plagued you since UITableView loads all the cells your table requires and has no need for reuse.  However, as soon as enough cells exist to be covered and require scrolling, the games begin.

Headache #1: Minor annoyance

The first issue this creates may not always be a problem.  In fact, some may argue this should never be a problem and its presence is a product of bad design.  Regardless, what I am referring to is storage of the data entered by the user.  If your view controller is designed in such a way that it will only read the values of each user entry field when the user hits a “Save” button, you will find that it is difficult to read the value of a widget (such as UITextField) that has scrolled off-screen and was reused.

The solution to this problem can be simple enough without messing with UITableView; simply storing the values in some sort of model any time the user focus changes is usually sufficient.  This also allows the data to be reinserted in the cell if it leaves the screen and then later returns, or if a low-memory situation causes the entire view to be temporarily unloaded.  Nevertheless, it is an issue that would otherwise not exist without cell reuse.

Headache #2: More of a migraine

The more important issue when creating interactive tables is the need to transfer the firstResponder responsibilities from one cell to the next as the user either presses a key on the input view, or taps another cell of their choice.  This issue can be even further compounded if not all the input views are a UIKeyboard, and you want the user to enter some data into fields through a UIPickerView or something more custom.  Prior to iOS 3.2, managing those custom views had to be done manually (adding the subview and animating it into view) in tandem with properly telling other text fields to obtain and resign firstResponder status.

A prime example of this problem comes with the following scenario: the user taps the first field in your table, which happens to be a text entry cell so the keyboard is displayed.  Now the user scrolls down to the bottom of the table (making the active cell scroll off-screen), and selects a second cell where you have attached a picker as the input view.  In order to dismiss the keyboard, you need to tell the original text field to resignFirstResponder.  However, good luck finding the field, or the cell that contained it for that matter…they’ve been recycled.  If you’re lucky the user will hit the return key, which will pass the active text field to textFieldShouldReturn:, allowing you to send the resignFirstResponder message to the right object.

As the input mechanisms created around your signup table get more complex, so does the probability that you will create situations where input views will be left dangling and can’t be properly dismissed.

A Solution

A solution to this problem is to say that table cell reuse on a small and finite set of data, such as a handful of entry fields, does more harm than good.  In these situations, we should let UITableView load the entire table into memory (as it would if the table were small enough to fit on the screen) and freely operate on all the cell objects without fear of losing them to recycling.  In order to do this, we simply set the frame of UITableView to match its contentSize parameter after the content calculations are made.  Once UITableView has had a chance to query its delegate for the number of rows and sections, plus the height of each, its contentSize it set to a value representing the entire length of the table content.  By setting the frame of the view to match, we increase the amount of “visibile” data and UITableView loads all the cells at once.

The side-effect of doing this is that UITableView will no longer scroll.  Just like any other UIScrollView, it will not scroll in either direction if contentSize is not larger than frame.  To combat this problem we wrap the table in a plain UIScrollView to handle the user interactions of scrolling instead.  Because of the hierarchy created here, it is not required to disable scrolling on the table (drag touches will be consumed by the parent UIScrollView), although you may do so if you prefer.  Below is a sample view controller to describe this further:

ViewController.xib

Notice the hierarchy of the UITableView as a subview of UIScrollView, which in turn is attached to the view outlet.

ViewController.h

#import <UIKit/UIKit.h>

@interface ViewController : UIViewController <UITableViewDataSource,UITableViewDelegate> {
    UITableView *theTable;
    UIScrollView *container;
}

@property (nonatomic,retain) IBOutlet UITableView *theTable;
@property (nonatomic,retain) IBOutlet UIScrollView *container;

@end

ViewController.m

#import "ViewController.h"

@implementation ViewController

@synthesize theTable;
@synthesize container;

- (void)dealloc
{
    [theTable release]; theTable = nil;
    [container release]; container = nil;
    [super dealloc];
}

#pragma mark - Private Methods

- (void)scaleTableToContents {
    [theTable setFrame:CGRectMake(theTable.frame.origin.x,
                                  theTable.frame.origin.y,
                                  theTable.contentSize.width,
                                  theTable.contentSize.height)];
    [container setContentSize:[theTable contentSize]];
}

#pragma mark - View lifecycle

// Implement viewDidLoad to do additional setup after loading the view, typically from a nib.
- (void)viewDidLoad
{
    [super viewDidLoad];
    [theTable setBackgroundColor:[UIColor clearColor]];
}

/*
- (void)viewDidAppear:(BOOL)animated
{
    [super viewDidAppear:animated];
    if(animated) {
        [self scaleTableToContents];
    } else {
        [self performSelector:@selector(scaleTableToContents) withObject:nil afterDelay:0.3];
    }
}
*/


#pragma mark - Table Callback Methods

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
    return 1;
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    return 15;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *CellIdentifier = @"Cell";
   
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    if (cell == nil) {
        cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
    }
    NSLog(@"Fetching cell for row %i",indexPath.row);
    [[cell textLabel] setText:[NSString stringWithFormat:@"Table Row %i",indexPath.row]];
    return cell;
}

@end

With the viewDidAppear: method commented out as it is above, the table behavior will be the default behavior.  You will see NSLog statements every time a row scrolls on-screen and needs to be fetched again.  By removing the comment block from viewDidAppear:, the sample code will scale the UITableView and its UIScrollView container to fit the entire table contents.  In this case, you will see NSLog statements for all table cells at once; nothing is recycled as the table is scrolled.

- (void)viewDidAppear:(BOOL)animated
{
    [super viewDidAppear:animated];
    if(animated) {
        [self scaleTableToContents];
    } else {
        [self performSelector:@selector(scaleTableToContents) withObject:nil afterDelay:0.3];
    }
}

You May Have Noticed a Caveat

The key to making this work lies in something I briefly mentioned earlier, but will reiterate here. UITableView must have had a chance to inspect the delegate for the information necessary to calculate its contentSize before you can scale the views. In cases where your view controller is presented in an animated fashion, ample time has been allowed for this by the time viewDidAppear: is called. However, in instances where animation did not take place (like if this view controller is the initial view of your application) a slight delay is required; I use the typical animation duration default of 0.3 seconds.

All in all, this is a very simple solution to a specific problem. But as in-app account management becomes more and more common, it is a problem you will find yourselves needing to solve regularly.

Processing UIImagePickerController

The UIImagePickerController class has been the developer’s best friend and worst enemy since its introduction into the iPhone SDK in iOS 2.0. For those who have not used it, this class provides a way for an application to have the user select and return an image, whether it be existing from the device’s library or fresh from the camera; this image may then be included into the application in some manner. Today we are going to discuss a problem that is almost as common as the use of the class itself! I’m talking about memory warnings and crashes.

Images on Display

I think one of the most common issues stems from developers trying to do something akin to the following code:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
- (void)someActionMethod {

  UIImagePickerController *controller = [[UIImagePickerController alloc] init];
  [controller setDelegate:self];
  [self presentModalViewController:controller animated:YES];
  [controller release];

}

- (void)imagePickerController:(UIImagePickerController *)picker
                    didFinishPickingMediaWithInfo:(NSDictionary *)info {

  [[picker parentViewController] dismissModalViewControllerAnimated:YES];
  UIImage *image = [info objectForKey:UIImagePickerControllerOriginalImage];
  [myImageView setImage:image];

}

Translated into plain text, “as soon as the user selects an image and we get a callback, display that image in some view and move on”.  Eek!  Most often (even on the high-powered iPhone 4), this will result in one of the two following behaviors:

  1. Your app will crash…hard.
  2. After the user dismisses the picker, you get a memory warning, and the view controller that presented the picker looks as though it has been reloaded from scratch (…because it has)

This will happen most often with images from the camera, but as images stored in the library get larger and larger you will see it happen there as well.  The issue is a combination of memory recovery behavior and processing power.

My Brain Hurts

The first thing many developers notice is the memory warning that arises in the console circa the time of the failure…and so they tend to focus on it.  Because it’s the only visual clue they have to start diagnostics from.  The memory warning is usually marked with a “Level=1″, which indicates it’s a warning and not critical or some other level.  Even when your application is running perfectly, you will likely still get these warnings every time you use an image picker…and that’s okay!  However, what the warning does signal to you is that the wrong choice of your next move will result in one of the two behaviors above.

On an iPhone 3GS, for example, the camera is 3 megapixel.  UIImagePickerController gives you the full image back when you retrieve it in the delegate callback (that’s 2048 x 1536 pixels)!  Storing this much image data in memory is one thing, but forcing your UI thread to try and do something with it will only spell disaster.  iOS will try to first free up as much memory as it can by releasing view controller objects (symptom #2).  If it is unsuccessful, dire circumstances await (symptom #1).

What’s the Right Move?

What you do with the image (display it, save it to disk, etc.) depends on your application.  But there are two universal truths about the image data that comes back:

  • The image SHOULD be scaled down
  • The processing MUST not be done on the main/UI thread

Scaling

There is a really great piece of useful code in the community that I cannot take credit for, but I will post here for convenience.  This code uses UIKit methods to resize and scale down an image.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
- (void)useImage:(UIImage *)image {
  NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

  // Create a graphics image context
  CGSize newSize = CGSizeMake(320, 480);
  UIGraphicsBeginImageContext(newSize);
  // Tell the old image to draw in this new context, with the desired
  // new size
  [image drawInRect:CGRectMake(0,0,newSize.width,newSize.height)];
  // Get the new image from the context
  UIImage* newImage = UIGraphicsGetImageFromCurrentImageContext();
  // End the context
  UIGraphicsEndImageContext();

  [userPhotoView setImage:newImage];

  [pool release];
}

Even if you intend to save the full image to disk and you want to display a preview, that preview should not be larger than the viewport of the device (320×480 or 640×960).  Forcing UIKit to scale the image for you at runtime with a call to setImage: is a bad plan.

Backround Threading

Many of you may have looked at the previous method and asked why an NSAutoreleasePool was created; that is because of the next step.  Even if you have the presence of mind to scale down your image before displaying it, if you try to do so on the main thread you will end up with the same results as if you had left that job up to iOS. Because of this, the scaling method we have written should be dispatched into a background thread, and our delegate method gets modified like so:


1
2
3
4
5
6
7
8
- (void)imagePickerController:(UIImagePickerController *)picker
                    didFinishPickingMediaWithInfo:(NSDictionary *)info {

  [[picker parentViewController] dismissModalViewControllerAnimated:YES];
  UIImage *image = [info objectForKey:UIImagePickerControllerOriginalImage];
  [NSThread detachNewThreadSelector:@selector(useImage:) toTarget:self withObject:image];

}

You may also want to consider popping up a progress dialog or something during the operation (Apple does this as well, try taking a picture for your contacts), as it can take a few seconds.

Conclusion

There, now we have a working example of how to properly handle picked images that we want to display in our applications.  Hopefully this will keep some of you from going bald too soon.

A Short Note About Modal Presentation

iOS5 Update

New in iOS5, the use and meaning of many of the existing UIViewController properties has changed as part of the new container view controller framework that is being made available. As a result, some of the information in this article is no longer valid. In particular, the parentViewController property is no longer populated on a presented modal view controller, making the method suggested here to dismiss that way ineffective. Instead, applications should use the presentingViewController property to dismiss from within the presented context in iOS5, such as:

[[self presentingViewController] dismissModalViewControllerAnimated:YES];

There is also a new version of these methods that takes a completion block (presentViewController:animated:completion: and dismissViewControllerAnimated:completion:), which is the overall recommended method to use in the future.

Original Article

One of the most common design patterns used to transition from one view to another in iOS is the use of the modalViewController property of UIViewController.  New views are presented using the presentModalViewController:animated: method, and dismissed using the dismissModalViewControllerAnimated: method.  Presenting a new view as a modal view does a number of things that are not always obvious:

  • Creates a parent/child relationship between the two view controller objects
  • The new view controller object is retained by the parent
  • Displays the new view fullscreen (on iPhone), optionally in an animated fashion
  • If animated, the animation is taken from the UIModalTransitionStyle of the NEW view

I have highlighted the top two items because it is those two that lead to the primary issues of misuse with modal presentation: over retention and child dismissal.

Child Dismissal

Per the Apple documentation, “The parent view controller is responsible for dismissing the modal view controller it presented using the presentModalViewController:animated: method. If you call this method on the modal view controller itself, however, the modal view controller automatically forwards the message to its parent view controller.”

Apple has done the developer a disservice here by automatically correcting bad practice, but doing so doesn’t make it good practice.  Apple clearly states that presentation AND dismissal of modal views are the responsibility of the parent view controller.  dismissModalViewControllerAnimated: should never be called from the child (modal) view; despite the fact that iOS helps you out and autoforwards the request to the parent.  The reason is because the second sentence is ONLY true when the modal view is the final child of a modal view stack.  If a UIViewController has a modalViewController set, then a dismiss message will result in that controller dismissing it’s child.

Every view controller that has been presented in this way (it is a child) has a property called parentViewController that is set, making it very easy for the developer to pass the dismiss message on to the proper object, even if they are not in that context at the moment.  See the example below, where versions of the right and wrong way to dismiss from a child are displayed:

@implementation ViewControllerOne
- (void)showNewView {
  ViewControllerTwo *controller = [[ViewControllerTwo alloc] init];
  [self presentModalViewController:controller animated:YES];
  [controller release];
}

- (void)dismissRight {
  //Work is done...dismiss the child
  [self dismissModalViewControllerAnimated:YES];
}
@end

@implementation ViewControllerTwo
- (void)dismissWrong {
  //Work is done...dismiss me
  [self dismissModalViewControllerAnimated:YES];
}

- (void)dismissRight {
  //Work is done...dismiss me
  [[self parentViewController] dismissModalViewControllerAnimated:YES];
}
@end

In this example, both places where dismissRight exists are proper ways of dismissing a modal, whether from the parent or child. Using the technique in dismissWrong will only get you into trouble some day.

Consider a case where a developer makes several calls to presentModalViewController:animated:, creating a view controller stack.  Every view controller in that stack has their parentViewController and modalViewController properties set…except for the top-level and bottom-level controllers, whom have the parentViewController and modalViewController properties set to nil respectively.  If a developer has the improper concept in their heads of where to send a dismiss message, messages sent to view controllers in the middle of the stack (which is allowed) would dismiss the child of the receiver, instead of the receiver itself.  This would lead to major troubles in debugging because the design concept was built on flawed logic.  JUST DON’T DO IT.

Over Retention

The other common problem arises from the fact that developers may not realize that presenting a modal view controller (through the act of setting it as the modalViewController property) causes it to be retained by the parent.  Therefore, you do not need to create properties in your parent classes to hold instances of the modal:

@interface MyViewController : UIViewController {
  ChildViewController *modalVC;
}
@end
@implementation MyViewController
- (void)viewDidLoad {
  [super viewDidLoad];
  modalVC = [[ChildViewController alloc] init];
}

- (void)showNewView {
  [self presentModalViewController:modalVC animated:YES];
}
@end

In fact, this will keep the object from being released once dismissed, and you will be holding data from one use of the modal to the next.  If you want to make sure your modal view controller loads up fresh with each presentation, rely only on the retain message sent by the parent:

@implementation MyViewController
- (void)showNewView {
  ChildViewController *modalVC = [[ChildViewController alloc] init];
  [self presentModalViewController:modalVC animated:YES];
  [modalVC release];
}
@end

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!

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.