Recently about iPhone

Have you ever tried to use a UIActionSheet to display more than just buttons? It doesn't work, as you have probably figured out. The common solution is to create a custom UIViewController and display it using UIViewController's presentModalViewController, which can give you a nice slide up transition, much like how a UIActionSheet appears.

But that solution is missing something. A UIActionSheet slides up, but not all the way, leaving a semi-transparent covering over the parent view. Let's try to display a custom view with this same effect.

Full sample code can be found on github.

Let's start fresh by creating a new project in Xcode and choosing "Navigation-based Application." This gives us a RootViewController and an AppDelegate.

1. Create our custom action sheet

Create a new UIViewController subclass with XIB (right click on Classes -> Add -> New File). I'm naming it CustomUIActionSheetViewController.
Add an outlet in the parent view controller (RootViewController) for our action sheet and synthesize it.

@property(nonatomic, retain) IBOutlet
CustomUIActionSheetViewController *customUIActionSheetViewController;

Open the RootViewController.xib in Interface Builder, drag a View Controller onto the document window. Tell it to be the CustomUIActionSheetViewController we just created by changing the Class property in the Identity Inspector, and the NIB Name in the Attributes Inspector. 

Connect the outlet we created from the File's Owner to the controller.

At this point we've created our action sheet view controller and given the RootViewController a reference to it. You won't see anything different from a new project when you build and run.

2. Show our custom action sheet

In the parent view controller (RootViewController), create a method to show the custom view.

- (void)showCustomView {   
    [self.navigationController.view
        addSubview:self.customUIActionSheetViewController.view];

    [self.customUIActionSheetViewController viewWillAppear:NO];
}

We're simply adding the view as a subview, we'll do the work to slide up the content in our custom view controller. Note that we need to call viewWillAppear ourselves, it's called for you when using presentModalViewController.

Add a button when the RootViewController first loads to show our custom action sheet.

- (void)viewDidLoad {
    [super viewDidLoad];
    self.navigationItem.rightBarButtonItem = [[[UIBarButtonItem alloc] 
        initWithBarButtonSystemItem:UIBarButtonSystemItemAction 
        target:self 
        action:@selector(showCustomView)] autorelease];
}

At this point, when you click the button in the navigation bar, you should see the custom view immediately appear without any transition.

3. Make background semi-transparent

When the custom view loads, we want the parent view to be covered by a semi-transparent view.

Open our custom action sheet NIB (CustomUIActionSheetViewController.xib) in Interface Builder. 
In the Attributes Inspector for the UIView (there should only be one right now), click the background field. Change it's color to black (or the darkTextColor property), and change the Opacity to 50%.

view_background_properties.png

When you build and run and click the right navigation button you should now see the semi-transparent view cover the parent view.

4. Add a view to hold our custom fields

Drag a new UIView onto the NIB and attach it to a new outlet in View Controller. I'm calling it the actionSheetView.

@property(nonatomic, retain) IBOutlet UIView *actionSheetView;
Adjust the height of the view to be less than 480, but keep the width at 320. This will determine how far up we'll slide the view.

Next, let's add a navigation bar and a 'done' button that will dismiss the dialog.

Drag a Navigation Bar onto the view, and then a Navigation Item onto the Navigation Bar. Change the Navigation Item's title to 'Done' and change the Navigation Bar's title to whatever you want.

Let's go ahead and add a stub in the the CustomUIActionSheetViewController's header for 'slideOut', make it a UIAction, and connect the done button to it in IB.

- (IBAction) slideOut;

At this point, when you click the right nav button, you won't see the view we just created because we haven't added it as a subview yet.

5. Add the slide up transition

Create a new method to slide up the actionSheetView, and call it from the viewWillAppear method.

- (void)viewWillAppear:(BOOL)animated {
    [self slideIn];	
}

- (void)slideIn {
    //set initial location at bottom of view
    CGRect frame = self.actionSheetView.frame;
    frame.origin = CGPointMake(0.0, self.view.bounds.size.height);
    self.actionSheetView.frame = frame;
    [self.view addSubview:self.actionSheetView];
	
    //animate to new location, determined by height of the view in the NIB
    [UIView beginAnimations:@"presentWithSuperview" context:nil];
    frame.origin = CGPointMake(0.0, 
        self.view.bounds.size.height - self.actionSheetView.bounds.size.height);
	
    self.actionSheetView.frame = frame;
    [UIView commitAnimations];
}
To do this, we change the frame of the actionSheetView so that its top is at the bottom of the screen, and then start an animation block. In the animation we change the frame again, this time setting it's top to be the height of the actionSheetView. When we commit the animation, the view will transition from it's current position (at the bottom) to the position defined in the animation block.

When you build and run you should see the actionSheetView slide up when you click the right nav button.

6. Add the slide out transition

To slide out the custom view when 'done' is clicked we need to implement the stub we created earlier.

- (void) slideOut {
    [UIView beginAnimations:@"removeFromSuperviewWithAnimation" context:nil];
	
    // Set delegate and selector to remove from superview when animation completes
    [UIView setAnimationDelegate:self];
    [UIView setAnimationDidStopSelector:@selector(animationDidStop:finished:context:)];
	
    // Move this view to bottom of superview
    CGRect frame = self.actionSheetView.frame;
    frame.origin = CGPointMake(0.0, self.view.bounds.size.height);
    self.actionSheetView.frame = frame;
	
    [UIView commitAnimations]; 
}

- (void)animationDidStop:(NSString *)animationID finished:(NSNumber *)finished context:(void *)context {
    if ([animationID isEqualToString:@"removeFromSuperviewWithAnimation"]) {
        [self.view removeFromSuperview];
    }
}

Like before, we're going to change the origin of the frame wrapped in an animation block to slide out the view. We're doing a little extra work with setAnimationDidStopSelector. When the animation finishes, we want to remove our entire view (including the semi-transparent view), to go back to the state we were in before it was slid in. In the callback selector we simply use removeFromSuperview to do this.

That's it! The actionSheetView should slide in when you click the right navigation button, and the done button should slide it out.

Thanks to this answer on Stack Overflow for getting me started.

Download or fork the sample code from github.

We're in San Francisco this week at the Atlassian Summit lunching Mini Confluence Enterprise Edition -- a way to access Confluence on your iPhone, Blackberry, Palm or Android.

At last year's summit, we released Mini Confluence Personal Edition, an iPhone app that individual users can purchase from the app store and run based on Confluence's XML-RPC.

The new version is a custom plugin installed on the server. For people using an iPhone, they still download an app from the app store, but with the Enterprise Edition, it's free. For people with other mobile devices, they get to Mini Confluence through a web client.

This year's version is two times as fast as the original (!), and has some cool new features like filtering on the dashboard based on your favorites, status updates, landscape mode, and multiple user accounts. Find out more about Mini Confluence at www.miniconfluence.com.

We've been talking to lots of the conference attendees the past couple of days. Some of them have already been using the personal edition, and have given us feedback on that. Other people have ideas for MCEE, like an iPad version, support for Confluence instances protected by VPN, and even "make Mini Jira!"

I'm surprised by how many people here are on Android. It's still mostly iPhones, but a lot less Blackberry users than last year.

A lot of people have been signing up for the beta program -- we're sending out copies of the plugin for free to anyone who's interested and letting them try it out for three months. I'm anxious to hear the feedback so we can improve it before people start paying for it.

If you try it out, let us know what you think!

And now... I think it's time for some Ghirardelli Chocolate :-)

I just bought Keynote Remote for my iPhone. It is very simple, has the option to display presenter notes, and is absurdly simple to link to Keynote and start controlling presentations. What was a little more involved was that I wanted to use it regardless of whether there is a wireless network around or not. Since Keynote Remote requires a wi-fi connection here's what I'm doing to get around this little annoyance. Basically all you need to do is set up an ad-hoc wireless network with your Mac, and have your iPhone connect to that network. Voila! So, when there's no wireless network available, I just use my handy-dandy Verizon Wireless USB760 Modem to connect to the Internet, and I can still control presentations with Keynote Remote.

So, after having actually written a blog entry covering each day of the iPhone bootcamp at Big Nerd Ranch, I thought a more broad summary would be in order. (That, and I'm sitting in the airport waiting for my flight this evening.) Anyway, the iPhone bootcamp was my second BNR class (I took the Cocoa bootcamp last April and wrote a summary blog about it here.)

As with the Cocoa bootcamp, I had a great time and learned a ton about iPhone development. I met a lot of really cool and interesting people with a wide range of backgrounds and experiences. This seems to be a trend at BNR, that the people who attend are people who have a variety of knowledge and experience, and bring totally different perspectives to the class. The students who attend are also highly motivated people in general, which, when combined with excellent instruction and great lab coding exercises all week, makes for a great learning environment.

Another interesting thing that happens at BNR is that in this environment, you somehow don't burn out and can basically write code all day every day and many people keep at it into the night hours. I think this is due to the way the BNR classes combine short, targeted lecture with lots and lots and lots of hands-on coding. In addition, taking an afternoon hike through untouched nature really helps to refresh you and keep energy levels up. (Maybe if more companies, and the USA for that matter, encouraged this kind of thing people would actually be more productive rather than less.) And because of the diversity of the students, every meal combines good food with interesting conversation.

So, thanks to our instructors Joe and Brian for a great week of learning and to all the students for making it a great experience. Can't wait to take the OpenGL bootcamp sometime in the future.

Today is Day 5 — the final day — of the iPhone bootcamp at Big Nerd Ranch, taught by Joe Conway. The last day of Big Nerd Ranch bootcamps are half-days so you have breakfast, have class until lunch, have lunch, and then shuttle back to the airport. Unfortunately for me my flight isn't until 7pm so I have a lot of time to write this entry!

See here for a list of each day's blog entries.

Preferences

First up on this last day of iPhone bootcamp is preferences, which allow an app to keep user settings in a centralized location. While you can store user settings yourself using a custom UI, the easiest way is to use the iPhone's built-in Settings app. The Settings app is the central location where apps can store user settings. It is also where all the other common iPhone settings are, which is convenient.

You work with the NSUserDefaults object to store app settings based on the app's bundle ID, for example com.acme.MyKillerApp. You'll also want to ensure you provide "factory default" settings using the registerDefaults method; these settings will be used if the user has not changed anything. NSUserDefaults is basically a dictionary of key-value pairs, and you obtain the standard user defaults using the standardUserDefaults method. From that point you can write user settings using methods like setObject:forKey, setInteger:forKey, and so on, and you can remove settings using removeObjectForKey. Similarly, you read settings using methods like objectForKey, integerForKey, and so forth.

As mentioned earlier, the Settings app is a uniform way of setting preferences for all apps. Users know where to look for setting preferences for apps there. In addition, managing the settings separately from your application also saves screen real estate, which is important since the screen isn't all that big. You define the settings for your application using the Settings Bundle. This is a standard Property List. You use special property keys, such as PSToggleSwitchSpecifier, to define the settings for your application. The Settings app uses the Settings bundle and property list to create the settings UI for your app. For example, if you define settings for a PSToggleSwitchSpecifier, a PSTextFieldSpecifier, and a PSMultiValueSpecifier, the user will see a toggle switch, a text field, and a slider when she goes to edit your app's settings.

For the lab exercise, we modified our Random Number app (from Day 4 during the Web Services section) to have user settings for how the random numbers are generated — for example minimum number and the range of numbers. It is quite easy to add user settings to your app in the iPhone using the Settings app and bundle.

Instruments

Joe briefly talked about using the Instruments app to do powerful debugging, for example to detect memory leaks in your app. He then demonstrated using the Leaks tool within Instruments to track a memory leak — which he had previously introduced intentionally — in the Tile Game app. Leaks was able to pinpoint the exact line of code where a leaked object (i.e. it was not released) was allocated. This is pretty cool.

Joe also showed what leaks look like when they come from C code versus Objective-C code. C-code allocation leaks (i.e. allocated using malloc) show up in Instruments as just 'General Block' and from what I gather are probably not that fun to track down the exact cause. Leaks in Objective-C code are easier, and as I just mentioned, Leaks can show you the exact line of code where a leaked object was allocated.

Networking

By this time, it is getting closer to lunch, and the end of the class. Sad, I know. Anyway, probably one of the hardest and broadest topics was saved for last, given that you could spend an entire course on nothing but network programming. Anyway, on the iPhone a key concept when doing network programming is "reachability." Reachability does not mean that a network resource is definitely available; rather it means the resource is "not impossible" to reach. Another important thing on the iPhone is figuring out what type of connection you have, for example EDGE or 3G or Wi-Fi. You might choose to do different things based on the type of connection.

With the SystemConfiguration framework you can determine if the outside world is reachable, again with the caveat the "reachable" means "not impossible to reach." When coding you work with "reachability references" and "reachability flags" which describe the type of connection, for example reachable, connection required, is wide-area network, and so on.

We then learned a bit about Bonjour, which is basically a zero-configuration network service discovery mechanism that works on all platforms, has implementations in many languages, and is easy to use. For example, Bonjour makes it ridiculously easy to find printers on a network, as opposed to the way you have to do it in other operating systems. You can use Bonjour on the iPhone to discover and resolve Bonjour services. You use NSNetServices and NSNetServiceBrowser to accomplish this, and you specify a delegate to receive notifications when services appear and disappear.

In addition to Bonjour, you can also send and receive data over the network as you would expect. To send and receive data you can either use a C-based Core Foundation interface or use a stream-based interface with CFStream. You register read and write callbacks with the main iPhone run loop, which calls your functions when network events occur.

Network programming is really hard, since there are so many things that can go wrong and you need to deal with many if not all of them. Joe recommended that before jumping right into hardcore networking, developers should become familiar with the concepts and recommended the Beej's guide to network programming as a good primer. Given that I've really only done simple socket programming in higher-level APIs like Java's, and have not really done much of it, I'll need to check that out. And so this concludes this episode of iPhone bootcamp blog, since it is now time for lunch and, sadly, the course is over!

Random Thoughts

This week has been a really intensive introduction to iPhone programming. We've gone from a very simple app on Day 1 and covered all kinds of things like form-based UIs and text manipulation; Core Location; Core Graphics; view transitions and Core Animation; using the iPhone camera and accelerometer; retrieving web-based data; manipulating Address Book data; setting user preferences, and finally networking and debugging using Instruments. We've basically covered an entire book's worth of content and written about 10 iPhone applications ranging from simple to not-so-simple. If you are interested in programming the iPhone, I definitely recommend checking out the iPhone bootcamp at Big Nerd Ranch. Because the OpenGL stuff we did on the iPhone was so cool, I'm thinking my next course to take might be the OpenGL bootcamp but that probably won't happen until next year sometime! With my new knowledge I plan to go home and try my hand at creating a snowflake or snowstorm application that my daughters can play with. If I actually get it working maybe I'll write something about it.