Posts tagged ‘objective-c’
Semi-Modal (Transparent) Dialogs on the iPhone
Popping up a modal dialog on the iPhone is a fairly straightforward process:
modalDialogViewController *modalController = [[modalDialogViewController alloc] initWithNibName:@"modalDialogView" bundle:nil]; [self presentModalViewController:modalController animated:YES]; [modalController release];
Dismissing it then is a simple matter of the modalController invoking:
[self dismissModalViewControllerAnimated:YES];
But what if you want to show only half a page’s worth or maybe you need the underlying view to continue being available for user viewing or interaction. Or maybe you want to show a pop-up toolbar where users are asked to choose something before continuing. You might think “Aha! I’ll just have my modal dialog view be half as tall and make the background transparent.”
Go ahead, give it a try. We’ll wait… (* … the girl from Ipanema goes walking … *)
So now you know that the standard modal dialog can only be full-screen and maintains a solid black background. What’s more you can’t interact with what’s behind it because, you know it’s modal — and modal means users shouldn’t be able to do anything else until they’re done with the front-most task (unless you’re the search box in the Contacts app in which case apparently it’s OK to be kindasortamodal).
So what we’re going to do is have a view that can be modal but takes only part of the top view, the space above it remaining visible. What’s more, you can choose to have it so tapping on the background view hides the modal view, or even go full-bore and let the background remain responsive to user input. This technically makes the view semi-modal so let’s ignore the sirens and the UI Police banging on the door and go with that.
The first thing you need is a view that has something interactive on it. The easiest way to build one is in interface builder, so go ahead and make yourself one. For the sake of expedience make it only a fraction of the screen. Here’s an example of a half-height view along with some user controls. The background is set to fully transparent. The view is connected to a UIViewController that reacts to user input:

In this case, the view is the same height as the whole screen because we want the upper portion to be see-through but not react to user input. If we wanted it to be truly interactive, we could make the view height be as tall as the actual content (i.e. half-screen) but that would make it a bit strange for the user because it would be hard to tell apart the actual content from the modal view. But hey, it’s your app. You can do what you want. Another option is to set the background of this view black and partially transparent. That would look cool and show a nice smoky cover while we’re in modal mode… unless you’re mucking with color (like we are in this example) in which case it’s best to leave it fully transparent. Next throw the following code in the parent UIViewController. Load up the UIViewcontroller/UIView you just created and pass the view to this routine instead of calling the standard presentModalViewController method (substitute your application delegate for MyAppDelegate):
// Use this to show the modal view (pops-up from the bottom)
- (void) showModal:(UIView*) modalView { UIWindow* mainWindow = (((MyAppDelegate*) [UIApplication sharedApplication].delegate).window); CGPoint middleCenter = modalView.center; CGSize offSize = [UIScreen mainScreen].bounds.size; CGPoint offScreenCenter = CGPointMake(offSize.width / 2.0, offSize.height * 1.5); modalView.center = offScreenCenter; // we start off-screen [mainWindow addSubview:modalView]; // Show it with a transition effect [UIView beginAnimations:nil context:nil]; [UIView setAnimationDuration:0.7]; // animation duration in seconds modalView.center = middleCenter;
[UIView commitAnimations]; }
What this does is add your view as a top-level above the main window, effectively rendering it modal. It also uses Core Animation to move the window from offscreen bottom up until it’s fully shown. You should adjust the timing to suit your view’s actual height. I’ve found that the taller the semi-modal view, the more time you should give it to become fully visible. Now let’s go through the hiding action. Note that we use the animation completion handler to do the actual removing of the item from the parent view and cleaning up. We also use the context parameter of the animation call (which was thoughtfully provided for exactly this sort of thing) to keep track of what view to clean up afterward:
// Use this to slide the semi-modal view back down. - (void) hideModal:(UIView*) modalView { CGSize offSize = [UIScreen mainScreen].bounds.size; CGPoint offScreenCenter = CGPointMake(offSize.width / 2.0, offSize.height * 1.5); [UIView beginAnimations:nil context:modalView]; [UIView setAnimationDuration:0.7]; [UIView setAnimationDelegate:self]; [UIView setAnimationDidStopSelector:@selector(hideModalEnded:finished:context:)]; modalView.center = offScreenCenter; [UIView commitAnimations]; } - (void) hideModalEnded:(NSString *)animationID finished:(NSNumber *)finished context:(void *)context { UIView* modalView = (UIView *)context; [modalView removeFromSuperview]; }
[ Update: As noted in the comments, there was an extra release on modalView in hideModalEnded. This code was excerpted from a larger code-base and the release was left in inadvertently. The code listing here has been updated. Thanks for catching it, folks. ]
What I’m not showing you here is the way to trigger the show/hide action. That’s entirely up to you. In standard iPhone modal dialogs this is often a button in the toolbar or navigation bar. In the case of the semi-modal dialog, however, you have even more flexibility. Basically it comes down to the show/hide elements being:
- Explicit: Provide an Accept or Cancel button on your view.
- Implicit: You can simulate an action toolbar that shows and hides this way. Put a row of buttons on the view and wire it so tapping each one invokes
hideModalbefore going on to the actual action. - Other: Tapping anywhere else on the screen dismisses the dialog. You can do this by placing a full-screen sized view (or custom transparent button) behind your modal dialog and wiring it so it a tap-down action dismisses the dialog . For best results, try making this full-screen view black and semi-transparent (e.g. opacity=0.2). This way the user’s main view darkens so they get a sense your modal dialog is in focus but they still get to see what’s behind.
Here’s a movie of the above semi-modal view in action. It lets the user select a color then confirm or cancel the action. The modal view in this case also has interactive controls on it. As the user changes color the background image changes in real-time so they can visualize what the end-result will be like. Once they’re done they can tap the checkbox or X/cancel buttons to make the modal go away.
The semi-modal dialog is a handy UI interaction component but it’s important to think about how to dismiss the dialog and what to allow in the rest of the visible region on the main window to avoid confusing the user. Also note that there are no restrictions on the shape or size of the overlay view as long as the background color is set to [UIColor clearColor]. You can use the same method for irregularly shaped pop-ups.
Go nuts and have fun.
[Update: Nathan in the comments below has posted some code on github. You may want to check it out. ]
NSArray debugging on the iPhone
If you’re debugging on the iPhone and encounter a lot of NSArray objects, you’ll notice that dumping out the contents doesn’t give you too much information. To help remedy this situation, I whipped out a quick-and-dirty ‘helper’ to show a bit more detail. As with the UIView helper all you have to do is include the NSArrayDebugExtras.m file in your project and build it. This works through the standard Objective-C category mechanism by adding an extra method to an existing class.
To use it, set a debugger breakpoint and enter:
(gdb) po array-variable-name
What you will get is a recursive dump of the array and its contents. If the array contains strings, labels, text fields, or text views, the actual text value is shown. If it contains other arrays, it recursively walks down the array of arrays and shows the contents.
Since there’s no garbage collection on the iPhone, probably the most useful information is the retainCount for each array and item.
If the array contains a lot of custom objects, of course, you’ll only see rudimentary information. In that case, I suggest you make a point of defining a debugDescription method for each object so the contents can be properly shown.
Of course, you’re welcome to tweak the display format to show whatever information makes sense to you, but this should give you a good starting point.
[ Download: NSArrayDebugExtras.zip ]
Update: The [obj className] method is no longer accessible. Fortunately, the runtime function object_getClassName(obj) provides the same service. The attached code has been updated. Thanks to Howard Katz for noticing the problem.
Easy UIView debugging on the iPhone
The user interface of the iPhone is based on a series of nested UIView objects, arranged in a view-subview/parent-child relationship. When building a complex application with a lot of views, sometimes it’s handy to be able to see exactly what that relationship is.
But if you try to print out the value of a UIView in the debugger (or through an NSLog function) you’ll be sorely disappointed. In this example we have a variable called front that is derived from a UIView. Setting a breakpoint in the XCode debugger, I type in a po (print object) command to show what that variable contains:
(gdb) po _front
<BCCardSideView: 0x106b020>
What you see is that the variable is of type BCCardSideView and its hex address. Not very helpful.
One solution is to take advantage of the Objective-C dynamic runtime and override UIView’s default describe method. This is the method that is called every time you try to display the value of an object. Here we have just such an override that recursively walks the view tree and dumps out the values. The code is contained in a file called UIViewExtras.m. All you have to do to enable it is include the file in your XCode project. There are no explicit methods to call.
Here’s the same output after UIViewExtras.m is included in the project (the indentation is a little messed up because of the column width of this blog. In the debug window, it should look fine):
(gdb) po _front
+ BCCardSideView retain:3 - tag:0 - bgcolor:(r:0 g:0 b:0 a:1.00)
bounds: x:0 y:0 w:130 h:80 - frame: x:5 y:5 w:130 h:80 - center: x:70, y:45
++ BCCardBackgroundView retain:4 - tag:0 - bgcolor:(r:255 g:255 b:0 a:1.00)
bounds: x:0 y:0 w:130 h:80 - frame: x:5 y:5 w:130 h:80 - center: x:70, y:45
++ BCCardTextView retain:4 - tag:0 - bgcolor:(r:0 g:255 b:255 a:1.00)
bounds: x:0 y:0 w:100 h:20 - frame: x:0 y:0 w:100 h:20 - center: x:50, y:10
text (len:4 - color:r:0 g:255 b:0 a:0.00): 'name'
++ BCCardTextView retain:4 - tag:0 - bgcolor:(r:255 g:255 b:0 a:0.00)
bounds: x:0 y:0 w:100 h:20 - frame: x:0 y:20 w:100 h:20 - center: x:50, y:30
text (len:5 - color:r:0 g:255 b:0 a:0.00): 'title'
++ BCCardTextView retain:4 - tag:0 - bgcolor:(r:0 g:0 b:255 a:1.00)
bounds: x:0 y:0 w:100 h:20 - frame: x:0 y:40 w:100 h:20 - center: x:50, y:50
text (len:5 - color:r:0 g:255 b:0 a:0.00): 'email'
++ BCCardTextView retain:4 - tag:0 - bgcolor:(r:255 g:0 b:0 a:1.00)
bounds: x:0 y:0 w:60 h:20 - frame: x:0 y:60 w:60 h:20 - center: x:30, y:70
text (len:7 - color:r:0 g:255 b:0 a:0.00): 'phone.1'
For each view object you see:
- The retain count.
- The tag value (if specified).
- The background color value in RGBA. RGB values are scaled up to 0..255 and alpha is shown as a floating point value between 0 and 1.
- View bounds rectangle (x, y, width, height)
- View frame rectangle (x, y, width, height)
- View center (x, y)
If view is a UILabel or UITextField, you also get:
- - Length of text
- - RGB value for text itself (vs. the background)
- - Actual value of the ‘text’ inside the field.
Subviews are indented by multiple “+” (plus) signs. So the top-level has one ‘+’ all its subviews have two ‘+’ signs, *their* subviews will each have three ‘+’ signs etc.
I personally find this handy in debugging views — especially those created dynamically. I hope you do too.
[ Download: UIViewExtras.zip ] (Update: Link updated.)
Update 2 [06-Oct-09] : Modified method of obtaining class name so it should work with newer SDK releases.

