iPhone • 58:06
iPhone OS 3.0 adds tremendous power to the view controllers behind your application's user interface. Explore the new support for alternate landscape views, contextual toolbars, and modal transition styles. Gain a strong understanding of a view controller's role, and learn how to apply that knowledge effectively throughout your application. These new features and best practices will save you time and dramatically improve your application's user experience.
Speaker: Evan Doll
Unlisted on Apple Developer site
Downloads from Apple
Transcript
This transcript has potential transcription errors. We are working on an improved version.
Good afternoon and welcome iPhone View Controller Techniques. My name is Evan Doll and I work on iPhone Applications and Frameworks. How many of you in the audience have already written an iPhone application? Most people. OK, cool. So you're experienced with UIKit and you probably understand the role of View Controllers in your application.
What I want to show you today is how you can adopt some really awesome features in your application without writing a ton of code. And you're probably going to be interested as well to hear about some additions and improvements to the View Controller classes in iPhone OS 3.0. Now, if you're just getting started writing up an application, there's going to be a lot for you-- here for you as well. We want to make sure you get started with a solid foundation.
You understand the role of View Controllers in your app, and we also want to show you some techniques that you can use when it's time to take your application to the next level. So what are we going to cover today? We're going to start with the quick overview of View Controllers. So regardless of your level of experience, if you're a beginner or an expert, there's going to be something for you here, so stay tuned in.
After that, we're going to cover five new features of View Controllers in iPhone OS 3.0. These additions are going to help you solve some common problems and really, you know, add a level of shine to your application. And finally, we're going to talk about best practices, carrying on a little bit from the previous session on iPhone Application Architecture.
I'm going to show you some things to avoid, some things to do, and basically, avoiding unsupported usage so that you can keep your application working well with minimum of headaches on future iPhone OS releases. So let's get started with, you know, a quick overview of View Controllers; where they fit in your app and what they do for you. I want to make sure again that we're on the same page before we get into the new cool stuff. This stuff is pretty cool, too, but not as cool or new. [Laughter] So let's start with the sort of high level picture of the design of iPhone applications.
And Alex talked about this a little bit in the last session. But if you think about it, you know, the applications that you've used on the iPhone, both first party or third party applications, they tend to be made up of multiple screens, and each of these screens has a distinct role, tries to do, you know, one or few things well, and has been connected to other screens in your application using one of a couple of common application flows or application design patterns.
So if you look here, you know, the Recipes App is our favorite poster child in terms of demos. And here, an enthusiastic, over-enthusiastic UI designer has tried to cram the entire app into one screen. It's a little bit overwhelming for the user. And really, what we want is something more like this, where at the top level, we've got a list of recipes, and as we drill down, you select the recipe, you can view the details of the recipe, you select the picture, and you can view the picture in greater detail.
So in this way, you know, you're not trying fit a bunch of stuff onto this small screen, and your app is approachable and understandable by your user because it matches the design of other apps on the iPhone. So we've got this new sort of ideas about design in our heads.
The next step is to figure out how to tie each of these screens together. And the idea here is that controllers play a pretty central role. And there are two ways that I could think about the role controller's play in your application. The first is as a connector between the model and the view. You've seen probably a million model view controller diagrams this week, you know, we've been, we were always, you know, pounding the pavement with that message. But really, you know, controllers help to mediate between model and view objects. They allow you to populate your views with data.
And when changes need to be communicated back to your model, they facilitate that as well, and analyze your model and your view to be much more reusable, maintainable, all that good buzzword happy stuff. The second role is that controllers are typically the objects where you're going to implement your application logic.
So, decisions about how to respond to different types of user actions like button presses, table row selections, shaking the phone, anything like that. But your controllers are typically going to be the starting point for your application logic. So really, think about controllers as the brains of your application. Hope there aren't any zombies in the audience.
So from there, what do we need? We need a starting point for our controllers. There's a lot of common things that controllers need to do in almost every application. The first thing is loading a view from an Interface Builder file, from a NIB, and sort of being responsible for managing the contents of that NIB. So that's a really common need. Another really common thing is responding to appearing and disappearing on the screen. You may want to prepare some data for display, you know, start loading some data from the Internet or from a database.
And when you disappear, that's often a good time to save data or it'd be to clear out some state that you don't want hanging around. There are a couple of other things which are common, elements that are common to iPhone applications such as presenting modal content and interface rotation.
The controllers are a good place for that too. So as we were working on-- this is some applications at Apple, you know, Mail and iPod and all that stuff, we wanted a starting point for our controller logic. So we came up for the class called the UIViewController. You've probably seen this slide before.
You know, UIViewController is the basic building block of your application logic. It's responsible for managing a screen full of content. And typically, you'll subclass UIViewController for each screenful in your application. And then that view controller will sort of tie together the data and the views that are going on in that screen. And that's all well and good. You've got a single view controller managing a single slice of your application. But what you really want to do is tie these things together, connect them to build an application. And there are a couple ways of doing that.
There are these container view controllers which you can use to plug individual view controller subclasses together. The first one, which you may be familiar with, is UINavigationController. And it manages a stack of view controllers presenting a hierarchy of data to the user. The second one is UITabBarController. And UITabBarController also manages multiple view controllers but it's different from an app controller. These view controllers are parallel.
They're separate. Each of them kind of has their own little world in your application. So, we're going to take a brief look at some key aspects of the Navigation and Tab Bar Controllers, real quick, and then after that, a couple of other things we're going to get into the new and cool stuff in iPhone OS 3.0.
So with the Navigation Controller, as I mentioned, your users can be drilling down into a hierarchy of data. And the key operations here are pushing and popping. That's what allows you to move around within the stack, transitioning views in and out, and keeping the Navigation Bar in sync.
And if there's only one method that you're familiar with on UINavigationController, that method should be pushViewController animated. That's going to be the method that you call in response to button presses or table row selections in order to transition a new view onto the screen as you drill down into the hierarchy of data.
So as you see here, we select a table row, we go down another level, we do it again, view the details per person, we can pop that by hitting the "Back" button. So in that case, it's actually pretty rare that you will call popViewController directly. It's something that typically happens automatically as a result of the "Back" button press.
Pushing is what you're really going to be responsible for. So that's UINavigationController. The other class that I want to mention is UITabBarController, and it has several separate self-contained modes, and each of these modes is represented by a view controller. The tab bar down at the bottom of the screen is updated to match each of these view controllers that it's managing.
And the user is going to tap the tab bar to initiate a switch between these modes. So typically, what's going to happen, you're going to set up your Tab Bar Controller. You're going to give it an array of view controllers. And from there, you just kind of let it do its own thing. The user is going to tap the tab bar.
It will switch between different modes. It's rare. It's possible, but it's rare that you're going to, you know, programatically change the selection in the Tab Bar Controller, except for a situation like application launch or something like that. One really common goal in many applications, many of the most full featured complex apps out there, like the iPod App, the Youtube App, the App Store or Music Store, is to combine a tab bar at the bottom of the screen with navigation at the top of the screen. This is a really common thing you want to do in an iPhone app. And the way this is going to work is that your top level view controller is a Tab Bar Controller.
This is the one whose view you add to the window. And from there, the Tab Bar Controller you know, has multiple children, some of those children may be UINavigationControllers. So each of those Navigation Controllers in turn, can have its own separate stack of view controllers. So these stacks are distinct, and the user can toggle between these different navigation stacks by pressing the tab bar at the bottom of the screen; so that's how that sort of, is put together in a complex application.
Finally, the last you know, overview topic that I want to cover is presenting modal content in you application. And the situation where you're going to want to do this is when your user needs to add some new data or pick from some existing data that's already in their application. And the key with modal content is that it covers the entire screen.
The user is in this mode and needs to either, you know, add or pick the data or maybe cancel out of it before they get back to the main flow of your application. So here's an example in the Clock App, when we want to add a new alarm, you hit the "+" button up at the top of the screen, and a view controller is presented modally.
It takes it to full screen, it goes away when you hit the "Cancel" button and you're back in the main flow of the app. Similarly, in the text messaging application, you can hit that "Compose" button at the top to show a new message composition and we're going to present an additional mode of view controller on top of that for picking someone from your address book.
So we call presentModalViewController once, we call it again, and then we dismiss them one after another, get back to the original state of the application.
Now, Modal View Controllers are a great way to display some other functionality into your application when we expose a few view controllers that are ready for you to present in your application. The first one which you are probably familiar with is the image picker.
This is how you integrate with the user's photo album and a camera in your iPhone app. The second ready to present view controller you may have used as well is the people picker, and it allows you to select a contact from your address book and integrate that into the application somehow.
Now on iPhone OS 3.0, there are a couple of new ready to present view controllers that you can use in your app. The first one is the mail compose view controller. So now on iPhone OS 3.0, you don't need to have the user exit your application to send an email. It can all happen in your process.
They can compose the message, they can send it, and they don't have to leave your app, which is really cool. A second ready to present view controller on this new in iPhone OS 3.0, there's actually a session going on about it right now as you'd see, you should definitely check out the video, alright, is the media picker. This allows you to get you know, your user's playlists and albums and songs into your app to use them in cool, interesting, unforeseen ways. So presenting modal content is really key.
You're actually going to see some example of presenting modal content later in today's talk. If you want to know more about view controllers, if this is a little fast for you 'cause, you know, we're just kind of covering the basis. First, I encourage you to check out WWDC 2008 on iTunes.
There are a couple of sessions. In particular, they cover this in really, really great depth, "Understanding View Controllers" and "Mastering View Controllers". I believe this is still available for download. Also, just a little bit of shameless self-promotion, the iPhone Application Programming Course at Stanford, all the videos, lecture slides, assignments, handouts. It's all online. It's all free. You can download it.
Check it out and produce at your leisure. And finally, tomorrow morning, 9 to 11:45 AM in iPhone Lab A is the iPhone View Controllers Lab. So if you have specific questions about using view controllers in your application or some use case that we didn't expect, in order to figure out how to make it work, come and talk to us. I'm going to be there, a bunch of other UIKit engineers will be there, and we can get your questions answered. So at this point, let's get in to the good stuff.
We'll talk about some new features in iPhone OS 3.0. And as I mentioned, there's 5 features we're going to cover: Contextual toolbars, flipping in your application, mixing interface orientations, alternate landscape UI, and full screen layout. So let's start with contextual toolbars. The idea with contextual toolbars is that, you know, the iPhone, it doesn't have a menu bar up at the top of the screen, and the next best place to put sort of contextual actions the user may want to perform on what they're currently viewing is in a toolbar at the bottom of the screen.
And you've seen this well used in the Mail App on the iPhone. As you navigate through the Mail App, the contents of the toolbar at the bottom of the screen change. If I were to go one additional level in here, you'd see how we get a bunch of controls for deleting and transferring your message, all that sort of stuff. And so in our Recipes Application, we want to have something similar. At this top of the recipe list, we've got a couple controls.
We want to change the sorter or maybe view settings for the application or view this handy-dandy utensils view. Or when we view the details for a recipe, watch here at the bottom of the screen as it changes. We select the recipe and the toolbar items change. This is really common. A lot of apps want to do this.
And we wanted to make it dead simple for you to adopt in your application. So how do we do this? The key now is that navigation controllers may optionally choose to display a toolbar. It appears at the bottom of the screen and the content of, you know, the area where the top view controller is displayed is automatically resized to accommodate the toolbar at the bottom.
So we've got a toolbar, where did the toolbar items come from? The second edition is that each view controller can have an array of toolbar items. So you would allocate and you would set an array of items on each view controller and then as navigation occurs, the toolbar is going to be automatically updated to match where you are.
So the key here is that you don't need to access the toolbar directly and say, you know, set the items, you can just, you know, set them on the view controllers, and it will be automatically-- the toolbar will be automatically updated to match. One thing worth mentioning here is that the hidesBottomBarWhenPushed property is respected for toolbars as well as tab bars.
You've probably been using hidesBottomBarWhenPushed in situations like the iPod Application where the tab bar is at the bottom of the screen but then it slides out when you're viewing Now Playing. So you set hidesBottomBarWhenPushed to Yes on the view controller which we'd like to hide that bottom bar.
And this works just as well for toolbars if there's a situation, and we'll see it on some of movies here where we want the toolbar to just go away and animate out correctly. So let's-- oh, that brings me to a very good point which is toolbars versus tab bars.
What's the difference? You know, they have pretty similar names, and actually in the very, very first data version of the SDK, they were the same class which is kind of crazy. But toolbars are really all about performing actions, and they use momentary highlighting to indicate what you've pressed.
But then, once you take your finger off, the highlighting goes away. Tab bars are all about indicating modes. They stay highlighted, the highlighting is persistent, and really, when the user presses on a tab bar item they expect a mode change but they don't expect a lot of other stuff to happen, alright. It can be very disconcerting and confusing to the user if they tap on the tab bar item and some other stuff happens in your app. So that will be one sort of recommendation in terms of how to use toolbars versus tab bars.
You know, toolbars are for actions, tab bars are for modes. Another thing worth mentioning is that your application may have, you know, one or the other, but I'm hard pressed to think of it of a situation where you'd want both, a contextual toolbar and a tab bar at the bottom of the screen. First of all, that's going to be taking up a big chunk of space. And second, I don't know, I just can't think of any apps where that would make sense. But who knows, maybe.
My recommendation would be choose one or the other. So let's look at how we actually use a contextual toolbar with some code sample and a little bit of you know, eye candy to really drive the point down. So right now, we've got a navigationController, it's been added, its view has been added to the window, and right now, there's no toolbar anywhere. And what we really want is for a toolbar to show up in that space at the bottom of the screen.
So the first thing we do, there's a property on UINavigationController, toolbarHidden, it defaults to "Yes," and kind of you know, inverse negative, toolbarHidden set to "No" which shows the toolbar. And when we do this, you'll see the toolbar magically materializes at the bottom of the screen. So we've got the toolbar now.
We want to populate it with some content. And as I mentioned, we're not going to access the toolbar and set these items directly. What we really want to do is set the items on the view controller, in this case, the recipe list view controller. So I create this array of items, and I'm going to set it on the recipe list view controller and by virtue of doing that, the toolbar is automatically updated to match.
And then finally, we want some different items when reviewing the details for our recipe, right? So we're getting the recipe detailedViewController ready to display. We're going to set its toolbar items to some array of different items. And then when we push the view controller, you'll notice without accessing the toolbar directly, the toolbar updates to show the new items.
So that's how you use contextual toolbars in your application. The key here, first of all, is that each view controller may have its own list of toolbar items and the navigation controller will automatically update the toolbar to stay in sync with where your users navigate it to. And again, don't confuse tab bars and toolbars. There's a big difference. And if you, you know, use one when you should be using the other, it can be very confusing for your user. So those are contextual toolbars. The next topic that I want to cover is flipping in your application.
So, you've seen this in a lot of places. The Stocks Application uses flipping, you know, very well, you can tap that little button at the bottom of the screen, flip over, change some things around, go back to the main flow of the application. So a flip UI is a really convenient way to present some sort of auxiliary views, settings, something that is an essential to the core application flow, but it sort of tangentially related.
So we wanted to make this really, really easy for you to do in your application. There was some sample code for this on iPhone OS 2.X. The issue there was that it involved a sort of intermediate view controller that contained both the front and the back side view controllers.
There's about a little bit more heavyweight than it should have been given how commonly people want to do this in their applications. So we want to make it really easy. And if you think about it, you know, this act of flipping you application here in the Recipes App, you hit the Settings button, go to some settings, hit the Done button, go back to the main screen. It's a lot like presenting a modal view controller, right? Your user is in this mode, they need to, you know, finish the settings and then hit the Done button, and then they go back to the main flow of their application.
So what we decided to do was actually extend modally presented view controllers to include different presentation styles. So if you look in UIViewController.h or in the documentation, there are now three modal transition styles that you can use when presenting and dismissing modally. The first one is the default, you've been using it, you know, since iPhone OS 2.0, it's the Cover Vertical style which will cause a view controller to come up from the bottom of the screen and go away in the same direction. And there are two new styles, the Horizontal Flip and the Cross Dissolve. We're going to be talking about the Cross Dissolve a little bit later. For now, let's focus on the Horizontal Flip.
So in addition to defining these styles, we added a new property to UIViewController which is the modalTransitionStyle. This is per view controller. This is a setting that you set on the view controller that will be presented. So here's how you use them. You first set the modalTransitionStyle, as I mentioned, on the view controller that's going to be presented. So in this case, I would set it on the settings view controller in the Recipes App, not the one on the front side.
It's unique to every view controller that is to be presented. And then I just call the regular presentModalViewController animated, dismissModalViewController animated, and that modalTransitionStyle will be respected. So let's take a look at how this actually works in the Recipes Application. I don't believe the sample code is on the attendee's site yet, but it will be up there pretty shortly for you to mess around with. So here's the Recipes App again. We've got the toolbar at the bottom.
We've got that Settings buttons and we want that Settings button to flip over to the settings for the Recipes App. So the first step as we're preparing the setting view controller here referred to as the backViewController, we're going to set the modalTransitionStyle, and this is something that persists.
You set it once on the view controller and that will last through the presentation, dismissal. If you present it again, you just need to set it one time on your view controller. So as we set that, we'll then call the regular presentModalViewController animated method. And here, we're pressing "Yes" for the animation property, of course.
And what this will do when the user hits that Settings button, we're going to flip over to the back side of the application. And now, when we want to go back to the main application flow, we don't' need to set the modalTransitionStyle again, we just call dismissModalViewControllerAnimated and we'll go back to the main flow of the application. Pardon me for a second.
[Applause] Yeah, yeah, right. So, yeah, you know, this used to be pretty complicated on iPhone OS 2.0. There was a lot of code. There was a lot of boilerplate code you had to bring in from a template to get this working in your application. Now, it's easy to integrate anywhere. It's, you know, just a couple lines of code.
You set the modalTransitionStyle and you present and dismiss. Yeah, so that's something we got a lot of feedback about and we want to make it easier. This next topic, we probably got even more feedback about and it's mixing interface orientations in your view controller-based application. Ton of feedback about this, this is heavily, heavily requested, and we wanted to make it work for some of the most commonly requested use cases. And there are two cases that we're most interested in. And the first one is a case of modally presented content. As you can see, we're going back to modally presented view controllers a lot today.
So back at the main screen, you'll notice this little utensils toolbar item down in the lower left. And when we tap this, we want to show just, you know, just in case you forgot what utensils you had in your covered, remind you what you've got, and this is a landscape view.
There are a lot of other applications where the main flow of the application is in portrait, but then there's some modal view control that you want to present its landscape only. So this wasn't really supported on iPhone OS 2.0. All of your view controllers had to support pretty much the same set of interface orientations. Otherwise your-- the behavior was kind of unexpected.
But now, if we tap on this utensils button, you'll notice that we present this thing in landscape, and the tool-- this set of part at the top of the screen updates itself automatically. We're going to do this in slow motion, don't worry if you blinked and missed it, I promise there was no sleight of hand. So if we hit "Done" now, we're going to return to the main flow of our application in portrait. The Status Bar is back to the portrait orientation and everything is as it was before. The other case is within the navigation stack. So this was another common request.
A lot of applications want to have portrait view controller, portrait view controller, portrait view controller, and then some leaf node which is rotatable. You may have seen this in Mail on iPhone OS 2.0 where Mail was not rotatable except for the attachment view. You could rotate attachments and then hit the "Back" button from there and you would be restored into portrait in the main flow of your application.
So if you check it out here, we're viewing the photo detail for a recipe and this view is rotatable. The previous one in the stack is not rotatable. So when this-- we rotate here, the automatic interface rotation occurs automatically and we hit the "Back" button, and we're back in portrait. So it was kind of a crazy transition. I'll do it again. Well, there's that one.
Go back to portrait here. You rotate, hit the "Back" button, it's back in portrait. So how do we do this? The key here is, well first of all, mixed interface orientations are now supported in two situations: Presenting modally, and within a navigation stack. And the key here is to implement -shouldAutorotateToInterfaceOrientation: in the expected manner.
And the key is to return YES for the orientations that you support. This is a way to indicate to the view controller presentation machinery what orientations are okay for this view controller and which ones are not supported. And you really want your implementation here to be simple. And if you find yourself writing a ton of code in shouldAutorotateToInterfaceOrientation, and you're checking with the five or six other objects, "Hey, should I rotate? Should I not?" and you're checking maybe even your own current interface orientation, that may be not exactly what we're expecting out of shouldAutorotateToInterfaceOrientation.
So we want to keep it simple and we want to keep it about design time, not run time logic. So let's take a look at a couple example implementations of shouldAutorotateToInterfaceOrientation. So the first example is a portrait-only view controller. And this is actually the default implementation right here.
So we're only returning YES here if the proposed interface orientation is portrait; so simple stuff, portrait only. Another example of view controller is one that's landscape-only, like the utensils view controller that we were looking at before. So here, we're only returning YES if the interface orientation is landscape. Sorry, if I was standing in front of that.
If we wanted to support both portrait and landscape, you might have an implementation where you order these two together. So that's like the photo detail view controller that we were looking at previously. So let's look now at how we actually mix these interface orientations. So here in the case with the utensils view, the recipe list view controller only supports portrait, it's not rotatable. And then the utensils view controller supports landscape orientations.
It returns YES for landscape left and landscape right. So when we call presentModalViewController here with the utensils as utensils view controller as our argument, you'll notice the status bar automatically moves over, the view comes up in landscape, and then the user can rotate their photo to look at this incredibly compelling content in landscape.
When they dismiss they hit the "Done" button, the status bar automatically updates and then back in portrait. You'll notice here, there's really no any new API. It's just supported via the existing shouldAutorotateToInterfaceOrientation method. There's no secret sauce you need to set up, just implement shouldAutorotateToInterfaceOrientation and it works. So as I mentioned, the other case is within the navigation stack. And I should be, maybe a little bit more specific here.
What we now support is a case where you've got a portrait-only navigation stack and tell your leaf note and that one is rotatable. So all of a sudden, you're in landscape with this leaf node whether it's, you know, a photo or an email attachment, whatever, and you want to pop back to something that's portrait-only. And that's the case specifically that we now support. We got a lot of request for this, and every single person who has asked for it, that was the case that they were trying to do.
So here, the photo view controller supports both portrait and landscape orientations. The previous view controller in the stack, the recipe detail view, for whatever reason, we've decided it only supports portrait. So as long as we indicate our support via the shouldAutorotate implementations, when we hit the Back button here after rotating the landscape, of course, you'll notice this kind of crazy transition where everything slides out vertically and the previous view comes in already in portrait. So that's that. No new API. It indicates supported orientations via -shouldAutorotateToInterfaceOrientation, and this is new to iPhone OS 3.0. Next stop.
[ Applause ]
Next stop is alternate landscape UI and this is another heavily requested thing. And actually, what we're going to see here is this actually combines two of the last topics we've just talked about; mixing interface orientations and using custom modal transition styles. In this case, the Cross Dissolve modal transition style.
So you've all seen the iPod application, and you know that when you're browsing around in these table views, when you rotate the phone, you all of a sudden go to this different mode, the tab bar goes away, the navigation bar goes away, and you're in this totally, totally different, you know, sort of optional additional mode for the application.
And we got a lot of API requests actually, requesting things like, you know, "I want to be able to hide the tab bar in this particular area or I want to be able to fadeout the navigation bar". And it turns out a lot of those requests were actually asking for this, they wanted to present an alternate landscape UI in their application. So we want to make this easy for you in your application as well. If-- folks out there who have done it, I know their Quicken app does a great job presenting an alternate landscape UI.
Hopefully those folks can, you know, delete a bunch of code and do things the easier way after all their hard work has gone noticed by users.
So here when we rotate the phone out of the recipe list, or the recipe detail view, we want to show a little card view, you know, browsing through your card book of recipes, I don't know, probably like cover flow, they kind of matches.
So how do we do this? As I mentioned, we're going to use mixed interface orientations in the context of presenting modal view controller. So we've got two view controller; the one that's underneath is in portrait, and the one that we're going to be presenting, the card view, is landscape-only. And the next step is to observe UIDevice orientation change notifications.
So this is a case where we don't want to opt into the standard interface autorotation stuff because that's really geared toward a single view that remains on screen as we're rotating from portrait to landscape or vice versa. That single view changes shape, maybe animates itself a little bit, but that's not what we want here. We want to show an entirely different view. So, we're going to observe this UIDevice orientation change notifications. And when we hear about a rotation to landscape, we're gonna present the card view controller.
And when we rotate back to portrait, we're going to dismiss that card view controller. And we're going to do it all using the modalTransitionStyle, the CrossDissolve modalTransitionStyle. And we're going to see, there we go, alright. So this is again leveraging a couple of things that we've already talked about in the presentation today.
So, starting with the recipe card view controller, our implementation of shouldAutorotateToInterfaceOrientation, we just returned YES for either landscape left or landscape right. You'll also notice that the status bar up at the top of the screen is black. So we want to enforce that as well in our viewWillAppear method.
After we call the super-- the super implementation, we're going to set the status bar style on the application to the black opaque style. Conversely and view will disappear, we're going to set the status bar style back to default. Now let's assume that the main UI bar application uses the default status bar style. If we really wanted to be super clever here, we'll probably, you know, save away the original style in viewWillAppear and then restore it in viewWillDisappear but that didn't quite fit on the slide.
So I just kind of assumed that we're in default in the main flow of the application. So that's the card view. Now, the second step as I mentioned was to listen to UIDevice orientation change notifications. And we do this first. Let's say that we're going to take this all off in our application delegate.
In our applicationDidFinishLaunching method maybe we want to begin observing this notification. So the notification is UIDeviceOrientationDidChangeNotification. And so anytime the device orientation changes, it's going to invoke this method on us, on the app delegate called deviceOrientationDidChange. Let's look at the implementation of that method. So here we go, deviceOrientationDidChange.
And the first thing we're going to do is get the current device orientation. We're then going to perform a few checks to figure out if this is an appropriate time to show the landscape card view controller. So we first check, see whether the orientation is landscape or not. If that's true, we also want to check and make sure we don't have a different modal view controller already presented.
And the third check we want to make for the case of the Recipes Application, because the recipe photo view is rotatable, we don't want to show that in the cover flow view when you're viewing the recipe view. So we check whether the top view controller handles landscape or not, and if it doesn't do landscape, then it's going to need appropriate for us to show that card view. So, if all these conditions evaluate to YES, we're going to get our card view controller, ready to go, and we're going to set the modalTransitionStyle on it to CrossDissolve modalTransitionStyle.
And then just as we've done, you know, a bunch of times already today and however many times in your application, presentModalViewController animated. So that would cause the application to fade into this cover flow-esque mode. It's-- maybe card flow is the right word for it when the user rotates the device to landscape. So now we need to implement the other half of that method, of the deviceOrientationDidChange method.
So here we go. And the other half the if-- of the if statement. If the orientation is portrait and our currently presented modalViewController is the cardViewController, so this is just a kind of simple check here to make sure it actually has been presented. Then, we're going to dismiss that modalViewController.
We call dismissModalViewControllerAnimated, we want YES animation, so it'll just cross dissolve out. And because mixed interface orientations are supported with modally presented view controllers, this just works. So if we go back here, we rotate the device back to portrait, we're going to fade back to the main UI in our application. So hopefully this should make it really easy for you to adopt an alternate landscape UI in your application. Hopefully it's not too insane or gratuitous but I look forward to checking it out.
Yeah.
[ Applause ]
Alright, so that was alternate landscape UIs. We used view controllers here. We present and dismiss modally in response to device rotation, and we used one of this new modal transitions styles. Finally, the last new feature that I want to cover is full screen layout.
And the idea here is that in the Photos Application, for example, when the user is viewing a photo, they can scale that thing up and it can occupy the entire screen including the area underneath the navigation bar, underneath the status bar, even at the top of the screen.
A lot of people want to this when they've got really compelling especially user-generated content that they want to just fill the entire screen with. So we wanted to support this as well in the Recipes App. As you might expect, now when you view a recipe photo, you'll notice that recipe photo content can fill up the entire screen including the area under the status bar.
So I'm going to show you how to do this. The first step is to cause the navigation bar to overlap your content. And this is actually something that has been supported since iPhone OS 2.X, what you do is you make your navigation bar translucent. And if you're in the context of a navigation controller and you set its navigation bar to be translucent, the content will automatically be positioned in the space underneath the navigation bar. To be more specific, this has always worked if your top view controller's view is a scroll view, and it's worked since iPhone OS 2.0. And we extended this to work for all views for application that are linked on or after iPhone OS 2.2.
So if you're working with iPhone OS 2.2 or later, any view should be positioned in the area underneath the navigation bar if the navigation bar is translucent. But the next step that's a little bit more complicated is getting the status bar to overlap our content as well. By default, content just does not appear underneath the status bar. And people tried some ways of making this work on iPhone OS 2.X and some of them worked, and some sort of worked, and then they broke, and all that sort of stuff.
I'm going to show you the fully espoused supported way for you to do this in your application. To overlap the status bar with your content in a navigation or tab bar based app or even when they combines nav bars and tab bars, you first want to make sure that your window and the containing view controller are sized to the full screen.
So this means when you create your window, if you create in code, maybe you're creating your NIB, in which case, it should already be sized to the full screen size, you're going to use the value of UIScreen mainScreen bounds, if you're not turning any area off the top of the screen for the status bar.
The same thing goes for your containing view controller. So in this case of the Recipes App, when we size this view controller to start with, we don't want it to be the application frame, we want it to be the full screen, we want it to take up the full area of the window.
The navigation bar and tab bar, no, sorry, navigation controller and tab bar controller are both intelligent enough to position their content in such a way that if it, how should I say this, navigation controller, you might worry that the navigation bar is going to overlap the status bar now at the top of the screen. But the navigation controller actually handles that for you automatically. The navigation bar is shifted down by the height of the status bar even if it's full screen.
And then the key here to indicate to the containing view controller that it should actually size its content using the full screen is to set this property wantsFullScreenLayout to YES on your view controller. So in the case of the Recipes Application, we're going to set that property to YES on the recipes photoViewController.
Now, important to mention here is that the status bar style change is still up to you. So, after you've, you know, you've got this full screen window, full screen navigation controller, one of your view controllers in the stack has wantsFullScreenLayout YES, you still need to call the UI application method to update the status bar style. So, kind of a lot of stuff here. Let's look at some code and hopefully that will help to illustrate what's going on. So here's the full screen aspect.
When we're creating our window, we're sizing it, we're calling it with frame, we're going to call UIScreen mainScreen bounds to get the frame that we're using for the window. Again, if you're using Interface Builder, you might just want to check out your mainwindow.zib and check it out but it should already be full screen.
So we call initWithFrame and we use that fullScreenFrame. And then the key here and this is probably different from how you're doing it right now in you iPhone OS 2.X-based app is let's say our top-level view controller is a navigation controller.
To correctly work with full screen layout, we want the navigation controller's frame to also be full screen. In this case, on the iPhone, it's 320 x 480.
So don't use UIScreen mainScreen application frame here, just use the bounds of your full screen window. Next, we're getting our photoViewController ready to display, and we want it to be positioned under both, you know, the navigation bar and the status bar. And to indicate that it should go under the status bar as well, we set wantsFullScreenLayout to YES, just a simple Boolean property, set it and forget it, whatever, it's good to go.
And then we're going to push that view controller. The last thing to do is ensure that we update the bar styles to match here. We're going to update the application StatusBarStyle to the BlackTranslucent style so that the content showing through, you know, so you can see it. Additionally, we're going to set the navigationController.navigationBar, we're going to set up a couple of properties here, the bar style is just purely for looks, so we want that cool BlackTranslucent navigation bar when we're viewing the recipe photo just like in a Photos App on the phone. But the key here is setting the translucent property on the navigation bar to YES. And that will cause-- that's the other domino that used to fall for the navigation controller will size its top view controller's content to fit the full screen.
Then when we're popping, we need to revert this stuff, you know. And again here, this assumes that the bar style on the status bar and the navigation bar both the default style, you might want to do something more intelligent like cashing the original bar style and then restoring it. But the key here is that we need to restore both the application status bar style and the navigationController.navigationBar.translucent property and then we'll be back exactly where we were before we viewed this full screen view.
So, picture is worth a thousand words, we're going to go ahead and select this photo here, go in. You'll notice that the navigation bar and the status bar both update as we go in when we push. And we can then zoom in. You'll see that that content really is full screen.
When we hit the Back button, navigation bar reverts to its old appearance and the status bar does its thing as well. So now you can integrate this in your application in a fully supported way that we'll continue to work on future iPhone software releases which is always nice.
[ Applause ]
So what did we just see? There's a few things to keep in mind here when you want to do full screen layout. First of all, again, ensure the window and the containing view controllers are full sized. Set the wantsFullScreenLayout property to YES on the view controller to be displayed. Use the translucent bar styles on the navigation bar and the status bar and then restore the original bar styles when you're going away.
So that covers our little selection of new features in iPhone OS 3.0. Hope you all adopt them in your applications. Yeah, you don't have to write a ton of code to get some pretty cool visual effects. And now I want to talk about some best practices, and it's not talked about earlier in the application architecture talk. And you've probably been hearing elsewhere at the conference, there's somethings you need to do that will really help the robustness of your application and ensure that you avoid some potential headaches in the future with, you know, iPhone software releases down the road in the foreseeable future.
And the first topic that I'd like to mention is respecting the view hierarchy. What does this mean exactly? There are some system views that have their own internal view hierarchies that are not documented. These internal view hierarchies are only subject to change. And it's kind of the whole point of abstraction and encapsulations. It's something like the navigation bar.
We expose UINavigationBar, you can use it in all different places, but the actual contents of UINavigationBar are not documented either in API or in the docs. So making assumptions about what it contains can be dangerous. The same goes for navigation and tab bar controllers. Each of these controllers have a set of views and making assumption about what's contained within these views can be very dangerous.
Finally, some of the other view controllers the ready to present view controllers like the camera, the address book, all that sort of stuff. You really want to avoid making potentially dangerous assumptions about what's in those views. So in particular, what I would ask you to avoid doing, both for your benefit and mine, is to avoid manipulating the subviews of any of these opaque system views. Also, adding your own subviews is probably something that may work under particular circumstances.
You can always make assumptions about how that might change in the future. So if you find yourself in your code doing something like walking up the superview hierarchy, and then walking down into some subviews, subview object in the next 3 subviews, object in the next 2, and then do a little bit of stuff and hey, it works, chip it, it might not be the best thing to do. So I would really encourage you, in this case, to try working with the system frameworks, not against them.
The full screen layout, example, is one example of this. We weren't able to support that well in iPhone OS 3.0. A lot of folks kind of handled their own solutions to it. We now have a better way for you to do it that will continue to work on future iPhone software releases.
If there are cases where you're finding yourself needing to dig around in view hierarchies, you know, do what you will, but I first beg you to file a bug at [email protected] and request the behavior that we haven't yet exposed to you. The more we hear your feedback, the more we can make intelligent decisions about what API are worth focusing on on future iPhone software releases.
The next topic I wanted to mention is container view controllers. And container view controllers are things like navigation controllers, tab bar controllers, and those two in particular are really designed from the get-go to contain other view controllers. And if you find yourself writing your own code which, you know, writing your own container view controller, you might want to just pause for a second and ask yourself if it's really what you intend to do.
It's, I don't want to say that it's not something you should do period, there's, you know some example code that we've published which does this, which has one view controller which contains another view controller. You just need to make sure that there are certain methods which you will need to pass from the containing view controller to the contained view controller to ensure that it works properly and we don't necessarily have the facilities to fully support that as API right now.
One example of this where you can now switch over to some public API is for flipping and crossfading view controllers. So you can use the modal transition styles now, present the view controller modally, and you won't need to have this wrapper view controller that maybe contains a bunch of other ones.
So, a bit of advice. Next, I want to talk a little bit about navigation bar appearance. What's going on here when you're using a UINavigationController? Obviously, when you're just using a navigation bar by itself in your application, you can do, you know, whatever you want with it. You can size it.
You can position it, all that sort of stuff. But when you're using a UINavigationController, the navigation controller owns the navigation bar. There are certain properties which are OK for you to manipulate, and there are properties which are actually pretty dangerous for you to make assumptions about or change. So, properties that are OK to change include bar style, obviously, the tint color, even the translucency.
Some properties which are dangerous to manipulate include the frame and the hidden value. So really I mean the navigation controller because it's responsible for sizing and positioning the navigation bar in an intelligent way and sizing and positioning other views relative to that navigation bar, if you go in under the hood and move around navigation bar, you might start to see some unexpected behavior. So you'll notice I called that hidden here.
A lot of people want to hide the navigation bar. What's the best supported way to do this with a navigation controller? I would encourage you to check out the -setNavigationBarHidden:animated: method on UINavigationController. And this method has actually been beefed up a lot on iPhone OS 3.0 and now supports animating horizontally during pushes and pops.
So if you want to, you know, hide or show the navigation bar while you're pushing and popping it, have that navigation bar go with the incoming or outgoing view controller, you can call setNavigationBarHidden:YES or NO animated:YES from within your viewWillAppear or viewWillDisappear method and you should get the right behavior there in terms of the navigation bar appearance.
So let's say that maybe on the top level of our Recipes Application, the recipes list, we don't want to display the navigation bar right up the bat, we want to use the full available screen real estate and we want to just, you know, fill it all up with our list of recipes. But then when we view a recipe, we want that navigation bar to come in, because otherwise, you won't be able to navigate it back, right? So what we'll do here, this is actually the implementation of viewWillAppear for the recipe list view controller.
So we call super and then we call self.navigationController, setNavigationBarHidden:Yes and we pass along the animated flag that we've got. The implementation of the next view controller, the recipe detail view controller, would be similar to this except that you're passing NO for NavigationBarHidden. You could also implement this in viewWillDisappear instead on the recipe list.
So if we have that in place, we then select the recipe, and we call setNavigationBarHidden:NO animated:YES as it's appearing, and the navigation bar will come in from the right side of the screen looking just like you would expect it to.
You can navigate back, the navigation bar will again hide itself and you're back at the root of your application since navigation bar. So you'll notice this is one of a few bits of new functionality that I've been covering.
Well, there isn't necessary new API for it, so it's not going to jump out at you if you're just browsing through the header. The documentation does mention it, but you know, keep in mind that we've added some subtle changes to the view controller classes in iPhone OS 3.0.
Mixing interface orientations is another great example of this. One additional topic that I want to mention is that of Interface Builder and view controller integration. So a lot of people ask, when should I use interface builder? I mean some view controllers maybe make sense, maybe it doesn't make sense.
What's the rule of thumb for most applications? And I would say really it's almost always recommended to use Interface Builder in tandem with your view controllers. It's especially useful if you got a really complex view hierarchy. You've got some containing view and it's got a bunch of labels and text fields and other sort of, you know, views in there that need custom positioning 'cause it's really painful to lay that stuff out in code, get the centering and the sizing, all that stuff right.
Interface Builder makes it easy to verify that, you know, what you see is what you get. The one case where Interface Builder is maybe not as useful in some iPhone applications is if your view controller's view is just a table view, right? Because the appearance of the table view isn't really customizable in Interface Builder, it's defined by the table view data source and delegate. So in that case, it might not make sense for you to use Interface Builder. Actually, that's what the UITableViewController view controller subclass is designed for.
It's just simply UIs where you got a table view filling up the whole screen. This is a good time to mention also the idea of one view controller per NIB. So in an application with many screens and many view controllers managing those screens, you'll often have multiple NIBs and each NIB will be owned by a view controller subclass, and they'll make connections between the view controller and elements of that NIB via the File's Owner, this little translucent orange box, which is a sort of a proxy for the object which is loading the NIB. Anytime you create a view controller, using init with NIB name and bundle, specify the NIB name.
What that does really is later cause the NIB to be loaded with the view controller as the File's Owner. So you can make connections between the view controller and the view or anything else in the NIB via the File's Owner property. As far as NIBs and memory management go, there are 3 topics I'd like to mention here that are worth knowing. The first is that top-level nib objects are autoreleased.
This behavior is different from what you might be used to on Mac OS X. So any top-level nib objects, and by top-level nib objects, I mean any of these objects which you may drag out into the main window, the main sort of top level UI of your NIB Interface Builder.
Any of those objects you might drag out are going to be autoreleased after the nib has been loaded. So if you want to keep them around, you need to retain them. And the most common way to do this is via IBOutlets where you can drag your connection from the File's Owner or from some other object to these top-level objects, and that will cause them to stick around.
Second related topic is that an IBOutlet without a setter is just setting directly into an instance variable will be retained by default. And this behavior again is different from what you might be used to on Mac OS X. So the tricky thing here is that you may have a case where you've got an IBOutlet on an instance variable. You don't have a setter defined. You don't have a setter method implemented. You don't have a property.
You don't have synthesized property. You don't-- you're not retaining this thing anywhere, but you still need to release it in your -dealloc method. The reason is actually because in the absence of a setter method, it will go through this standard sister method called setValueForKey. And setValueForKey will retain its argument as it's setting the value.
So really for the best clarity here, you're going to want to declare a property or just, you know, implement your own setter method. In that way, it's really obvious to you the fact that you've retained this object in a setter method or in a synthesized property with the retained attribute, it's then really obvious that this thing needs to be released in your -dealloc method.
Finally, and Alex mentioned this in the previous talk on iPhone App Architecture, there's this new method on UIViewController which a lot of people ask for -viewDidUnload. So in the case where you get a memory warning, and your view controller's view will probably be released but not always because in some situations that it's being displayed in its top view controller and navigation stack, for example, we don't want to throw that view controller's view away.
And it was tricky before iPhone OS 3.0 to know when exactly your view had been dropped and when the view was still around. So there's a new method -viewDidUnload which is a good place for you to empty out to release and set to nil any IBOutlets especially if you've got a view controller with the view and that has, you know, several subviews, and maybe you've got IBOutlets to those subviews, this is the place to release and nil out those IBOutlets.
Another method which is actually isn't here on the slides but is good to know about as well, is -viewLoaded, which you can call on a view controller and sort of non-distractibly say, "Hey, is your view loaded or not?" Because calling viewControllerView will always return the view. If the view has been unloaded as the result to the memory warning, calling viewControllerView will cause it to be reloaded which is kind of a counterproductive thing to do if you just got memory warning. So these are some useful hints for Interface Builder integration with view controllers in your app.
So that just about does it. In summary, I'd first like to encourage you to build your application using view controllers. They include a lot of built-in functionality for really common, you know, paradigms in iPhone applications. We use them in our system apps. We're moving-- almost all of our system applications, use view controllers.
We're living on them just like you are and we'd encourage you to use view controllers in your app. In iPhone OS 3.0, we've gotten a lot of feedback for our developers about what's important and what was missing from the view controller then we tried to add that in there.
So in addition to, you know, fixing, you know, filling holes, there's a lot of cool new functionality that you can adopt in your application without needing to roll it all by yourself. You know, we want you to spend time on the aspects of your application which are truly unique and interesting.
We, you know, you're time is limited. We don't want you to waste time reinventing the wheel doing a cover flow view. And finally, you know, follow best practices. It's going to save you a lot of headaches down the road. And again, let you focus on, you know, doing cool stuff in your application and not worry about whether some bit of code might break in the future. For more information, Matt Drance is of course your constant companion out there in the evangelism world.
The Documentation is getting updated everyday. We're adding more and more view controller content, working on a lot of great stuff there, and the View Controller's Lab, which I mentioned previously on the talk, tomorrow morning, iPhone Lab A, I think. If it's not iPhone Lab A, just look around for me and tackle me. That'll be going on from 9 a.m. to noon. Don't actually tackle me. That's it.