Archive for August, 2010

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:

- (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.

- (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:

- (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

Adapting to Empty Views

Android has a pretty flexible framework for displaying data from a database, array, or other list structure using classes called Adapters.  There are plenty of great tutorials in the Android docs and throughout the web on how to use adapters to create custom behavior for display this data, so I’m going to focus on it’s less-discussed couterpart; the AdapterView.

Adapter Basics

Android uses an Adapter class to bind data from a data structure (like a database Cursor or an array) to a view that is specially designed to dynamically display the data as it moves and changes…this is the AdapterView.  There are many common subclasses of AdapterView (ListView, Spinner, Gallery, etc.), but they share the common function without any special overriding that I will discuss in detail today.

The Empty View

All AdapterView subclasses have a property that maintains a reference to a View object known as the empty view.  The point of this view is to be displayed when the Adapter bound to our data tells the AdapterView that the data structure has no data.  It does this either by just returning zero from getCount(), or through a more complex implementation of isEmpty().  Conceptually, it is then the role of the AdapterView to replace the view reserved for displaying content with this View, if it is not null.

Setting the Empty View for an AdapterView

You may call at any time AdapterView.setEmptyView(View v) on an AdapterView object to attach a view you would like to use as the empty view.  You may also call AdapterView.getEmptyView() at any time to verify if an empty view has been set.  That’s it…sort of.

There’s a trick…

As of this writing, one of the things the Android documentation doesn’t tell you is that the AdapterView simply manipulates the visibility properties of your content view and empty view based on the information it receives from the Adapter.  After determining which view to display, it simply calls setVisibility(View.VISIBLE) on that view, and calls setVisibility(View.GONE) on the other.  This is efficient and works just fine, but it requires that you create both views and insert them into a visible layout yourself.  AdapterView will not do this legwork for you.  You cannot assume that supplying an empty view to AdapterView means that it will insert that view in it’s place in the layout at runtime…

Example, please!

Let’s examine the basic ListView example to get a grip on things.  When you place a ListView in your layout per the SDK tutorials, it will look something like this:

File: main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  android:id="@+id/layout1"
  android:layout_width="fill_parent"
  android:layout_height="fill_parent">
  <ListView
    android:id="@+id/list1"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:drawSelectorOnTop="false"
  />
  <TextView
    android:id="@+id/empty1"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:gravity="center_horizontal"
    android:text="Nothing in the List!"
  />
</LinearLayout>

and the setup code in your activity would be like this:

ListView list;
TextView empty;

@Override
public void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.main);

  empty = (TextView)findViewById(R.id.empty1);
  list = (ListView)findViewById(R.id.list1);

  list.setEmptyView(empty);

  //...code to create and set an adapter of your choice...
}

The key here is this works because both the ListView and it’s “empty view” are defined as children of the displayed root layout, so they are both laid out when the Activity is displayed.  Because of this, AdapterView’s method of simply manipulating visibility works perfectly.  Let’s modify the example now so that the layout XML looks like this:

File: main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  android:id="@+id/layout1"
  android:layout_width="fill_parent"
  android:layout_height="fill_parent">
  <ListView
    android:id="@+id/list1"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:drawSelectorOnTop="false"
  />
</LinearLayout>
File: empty.xml
<?xml version="1.0" encoding="utf-8"?>
<TextView
  android:id="@+id/empty1"
  android:layout_width="fill_parent"
  android:layout_height="wrap_content"
  android:gravity="center_horizontal"
  android:text="Nothing in the List!"
/>

with the same setup code in our Activity.  Now, the layout process is not handling the setup and inflation of our “empty view”.  When our list is empty, AdapterView will manipulate the visibility of the two view objects…so the content view will become invisible.  However, with the empty view not attached to our layout, it will not show up anywhere on the screen (even though it has been set to View.VISIBLE)! In order for this second example to work properly, you would have to modify the Java code as follows:

ListView list;
TextView empty;

@Override
public void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.main);

  empty = (TextView)findViewById(R.id.empty1);
  list = (ListView)findViewById(R.id.list1);

  //New code to add the empty view to the visible layout
  LinearLayout rootLayout = (LinearLayout)findViewById(R.id.layout1);
  rootLayout.addView(empty, 0);

  list.setEmptyView(empty);

  //...code to create and set an adapter of your choice...
}

Take-home reminder: AdapterView does nothing more for you than manage visibility between a content view and an empty view. You are responsible to making sure those views are laid out in the appropriate locations prior to utilizing this functionality. You could also subclass the AdapterView you are using to handle it, but many AdapterView classes are not as straightforward as you might think. I would highly recommend reading the docs and the Android source thoroughly before doing so.

Looking Forward

Knowing this about AdapterView is actually a bit exciting because it means we have yet even more flexibility at our fingertips.  This means that we are not forced to have the AdapterView content and empty views occupy the same space.  We could define our content at the top of the screen (like an image gallery), and display the empty view somewhere else, or even on top of the entire screen (like a popup saying “nothing to see here, move along”) restricting access to other UI elements in the view.