Configure player

Close

WWDC Index does not host video files

If you have access to video files, you can configure a URL pattern to be used in a video player.

URL pattern

preview

Use any of these variables in your URL pattern, the pattern is stored in your browsers' local storage.

$id
ID of session: tech-talks-2011-6
$eventId
ID of event: tech-talks
$eventContentId
ID of session without event part: 2011-6
$eventShortId
Shortened ID of event: tech-talks
$year
Year of session: 2011
$extension
Extension of original filename: mov
$filenameAlmostEvery
Filename from "(Almost) Every..." gist: [2012] [Tech Talk World Tour] Mo...

iOS 5 Tech Talk World Tour #6

Modernizing Your App Architecture with UIKit

2011 • 37:57

iOS 5 introduces enhancements in UIKit to transform your design ideas into high quality apps. See how to customize the visual presentation of views and controls using new Appearance APIs. Learn how to architect your app to use View Controller Containers, and get tips about using resizable images effectively.

Speaker: Jake Behrens

Unlisted on Apple Developer site

Transcript

This transcript was generated using Whisper, it has known transcription errors. We are working on an improved version.

Hi, I'm Jake Behrens, Apple's UI Frameworks Evangelist. Getting your application architecture right is crucial to creating a great iOS app. Today, you're going to learn how to take advantage of View Controller Containment on iOS to make the most of your code, and to make it easier to extend your app's functionality later on.

You're also going to learn how to use the Appearance API to create a custom look and feel for your app quickly and easily. Finally, you're going to learn some tips and tricks to help you prepare for extending your app for international markets later on, without having to redo a lot of the code that you've already done. So let's get started.

When you have a new idea for an application, you want to cement that idea as quickly as possible. Typically, you start off with something that looks a lot like this. You start off with a paper prototype, or maybe just a scribble on a napkin. Then after some time, you work with a graphic designer, or maybe you do some graphic design yourself, and you create a mockup that starts looking like a full application.

Now you just need to actually build the application. But how are you going to do this effectively? What does your application's architecture look like? How do you ensure that pieces of this application are reusable later on? How are you going to get the custom look and feel that your graphic designer wants? You can see that there's a wood green texture in the navigation bar, a custom font with its own custom color, and you also have a custom tint on the bar button item.

How are you going to do this without creating a lot of custom views? Your graphic designer also wants a custom photo picker presented when the user taps on a photo. But this photo picker has its own custom look and feel. Finally, once your application is on the store and doing well, how do you move this into another market and make localization easier up front? How do you make sure that the work you do now isn't work that you're just going to have to do all over again for each language? What are some things you can do now to save time later? Let's start with application architecture. On iOS, there are different types of controller objects. Today, we're going to focus on the View Controller.

The View Controller is one of the building blocks of creating applications. But more to the point, the View Controller's primary purpose is to manage a hierarchy of views. These views are responsible for drawing content, handling multi-touch events, and managing the layout of any of their own sub-views. Now, the View Controller may also connect to a model and present data to the user through views.

Conversely, it may take data that the user has entered into a text field and push that down to the model. But its primary purpose is to manage its hierarchy of views. A View Controller should also be encapsulated. Its functionality should not be dependent upon any other View Controller if at all possible. There are three main types of View Controllers on iOS. There are System View Controllers, and these are system-provided View Controllers that are part of the iOS SDK. Then there are Content View Controllers, and these are custom View Controllers that you create.

Then there are Container Controllers. Container Controllers handle layouts and application flow, and I'm going to focus on these today. First are System Controllers. These are view controllers that we provide to you, such as the Tweet Sheet and the Image Picker. Now, keep in mind that these really drive home the point of encapsulation.

All of these controllers can be presented from virtually anywhere in your application, and they work independently of your application. If you look at the Image Picker, you get the navigation between photo albums, the image previews, and the image thumbnails that scroll. This is all work that you don't have to create.

You don't have to write this code, and we've provided you all of this within the Image Picker itself, so that you can focus on the core functionality of your application. Then there are Content View Controllers. These are custom view controllers that you create. These are typically a subclass of UI View Controller or UI Table View Controller, and these usually represent some encapsulated content and functionality that is specific to your application.

Then we have Container Controllers. Examples of these controllers are the Navigation Controller, the Tab Bar Controller, and with the introduction of the iPad and iOS 3.2, we introduced the Split View Controller. These containers allow you to present multiple view controllers on screen at the same time, transition between view controllers, and define your application layout. Before iOS 5, you couldn't create your own custom containers. But now you can.

To start, let's look at a great example of a container controller. Here is the interface for Mail on the iPad. You can see on the left there's this list of messages, and on the right there's the detail of the currently selected message. Now, if you look at this head on, it may look like there are just two view controllers. But if we break this apart, we start to see that there's a lot more going on. So let's walk through how this interface breaks down.

[Transcript missing]

Let's go over some of the basics of what a Container Controller is. First, a Container is a UIViewController subclass. It's just like a regular View Controller, the only difference is that you can add and remove child View Controllers. Remember that since a Container is just a regular View Controller, it can also manage its own view hierarchy. You also get some things for free with a Container, such as automatic appearance and rotation method forwarding to the child View Controllers. You don't have to call ViewWillAppear, ViewDidAppear, and so forth on the children. These are all forwarded by the parent Container for you.

So let's look at the application that you're building. How are you going to break this up? Well, the system provided containers don't really work well here. A tab bar controller doesn't really work because you don't have tabs. And you don't want to use a navigation controller because there isn't a full transition away from the main screen.

Now, this is a bit of a crude example since there is a navigation bar, but the reason why there is a navigation bar is so that you can get the title and button placement for free. So first, you're going to create a container, which is a Photo Container View Controller. And this is going to be the root view controller of your window. So its view will be used as the content view of the window.

Then, you're going to add four of these photo view controllers, and these are just segmented to be top left, top right, bottom left, and bottom right. And since a container is a regular view controller, and can manage its own view hierarchy, you can add the UI navigation bar to the container's view hierarchy.

To look at this another way, if you look at the hierarchy, you can see that the Photo Container View Controller is the parent. It's the Container View Controller. Then, added as children to the container, are each of these four Photo View Controller Content View Cont rollers. If you get into a situation where each of these children are needing to know information about each other, you probably want to look at your architecture again because something has gone wrong.

Each of these should be encapsulated pieces of functionality, as I mentioned earlier. Additionally, you'll notice that the arrow is pointing down from the parent to the children. The parent sends messages down to the child view controllers. However, usually the children don't talk back to the parent. If you find that you need two-way communication, this is a great opportunity to use properties and delegates.

Okay, so you've opened up Xcode, and you've made the Photo Container View Controller, and that's set as the Windows Root View Controller. Then you added in the navigation bar, set its title, and added the bar button item on the right. Now you have something that looks like the start of an application. You just need to add the child view controllers. So how are you going to do that? Well, you need to do a little bit of setup in your container.

First, you're going to declare a property for each of the child content view controllers. This is so that you can know if one of the view controllers has been set or not. Then, you're going to implement a custom setter method for each of these properties. This is where you're going to add the child content view controllers. So let's take a look at what this code looks like in the container.

Here, you see the ContainersViewDidLoad method. The first thing you need to do is instantiate one of the PhotoViewControllers. Once you have that created, you're going to set that new instance of a PhotoViewController to be the top left ViewController. And since the PhotoViewController is just a simple ViewController with a UIImageView, you want to create a property for the ImageView's image so that you can set the image of the ImageView to be whatever you want later.

Now, you would write this code four more times, one time for each of these segments. However, the code is the same. So I'm just going to show you one example, and you can assume that the code for the others is exactly the same. So next, let's take a look at that Custom Setter method.

The first thing you want to do is check to see if the View Controller that has been passed in is the same as the current top left View Controller. If it is the same, you don't want to do anything. You just want to move on. But if they're different, then you're going to set the top left View Controller to be this newly passed in View Controller. And once you do that, you're going to call Add Child View Controller. You're going to add the new top left View Controller as a child to the container. This creates the controller hierarchy and the parent-child relationship.

[Transcript missing]

So now, you've added the first child view controller, and you can see that your application is starting to look complete. You just need to write the custom setter method for the other child view controllers, and do it three more times. Now, if you stopped here with your code, what happens if you want to remove one of these photo view controllers? What if you want to swap it out with a different view controller? How are you going to do that? Well, let's take another look at the custom setter method.

You're still checking to see if the view controller that's passed in is already the view controller set for that segment. If not, then now you're going to call remove from super view on the photo view controller's view, which is going to remove that view from the container's view hierarchy.

Then you need to call remove from parent view controller. You're going to call this on the top left view controller, so you're removing that view controller from the controller hierarchy. Once this is done, you're going to set the newly passed in view controller to be the top left view controller.

And again, you're going to call add child view controller passing in the top left view controller. Then you're going to set the photo view controller's views frame to be where you want it to show up on screen. And then finally, you're going to add the photo view controller's view to the top left view controller.

And then finally, you're going to set the photo view controller's view to the container's view hierarchy. So you've added it to both the controller hierarchy and the view hierarchy. Adding and removing the child to and from both hierarchies is extremely important. If you don't do both, you can wind up in a bad state.

So here is the container and its view hierarchy, and a child and its view hierarchy. Now, if all you did was call AddSubView, you can see that there's an issue. Now you have two view controllers pointing to the same view, the child controller's view. This is an issue because appearance and rotation methods may now be called multiple times on that child's view.

This can be hard to debug sometimes because you can see the content on screen, but the controller hierarchy isn't correct. So you want to make sure that you're doing both steps of this process. Here you have the same setup as before, but this time you're calling AddChildViewController, and adding the child to the controller hierarchy. This means that appearance and rotation methods will come from the parent and flow through the child controller.

Then you're calling AddChildViewController, and adding the child's view to the container's view hierarchy. This is good. Now the child's view will only get the appearance and rotation methods called once, and children, once added to a parent, should stay there unless removed properly first. Be aware that you may run into issues if you don't follow this rule.

Here's another issue that you could run into: an inconsistent hierarchy exception. This example is similar to the last one, except in this situation, the Photo View Controller was previously added as a child to a UI Navigation Controller. So now, if you were to call AddSubView and add the Photo View Controller's view to the Container's view hierarchy, you're going to get an hierarchy inconsistency exception.

This is because the Photo View Controller already has a parent. So when you call AddSubView on its view, the parent controller should be the Photo Container View Controller. But currently, the Photo View Controller is still a child of the UI Navigation Controller. And since these don't match, the controller chain is incorrect, and you'll receive this exception.

In the console, this is the output that you'll see for this situation. It may not seem helpful at first, but it is. If we break it down to be more readable, we can walk through it and find exactly where the problem is in our code. If we follow through it, it's saying that the child view controller, in this case the photo view controller, should have a parent view controller that is a photo container view controller.

However, in this case, its actual parent is currently set to be a UI navigation controller. This means that somewhere in our code, we've called addChildViewController, passing in a photo view controller, and we've called it on the UI navigation controller. So you can look through your code and find out where the issue is and correct it.

So now your architecture is set up correctly, and your application is looking great. But your graphic designer has reminded you that you need to create the custom photo picker for when a user taps on a photo. How are you going to do this? You used to have to do this modally, but on iOS 5, we've made it so that you don't have to.

First, you need to do some setup in the Photo View Controller. You're going to add a Tap Gesture Recognizer to each of the Photo View Controller's views. This makes it so that when a user taps on a photo, you can fire a method. Naturally, next you need to write a method that presents the Photo Picker Controller. So let's take a look at that method.

In the PhotoViewController, you've created a method called ShowPhotopicker. First, you need to instantiate the PhotoPicker controller. Then, you're going to call PresentViewControllerAnimatedCompletion. If you've used this API before, you may notice that it has changed. We've removed the modal part from the name, because you no longer have to present View Controllers modally. We've also added a completion handler where you can use a block to execute some code once the View Controller has been presented.

Again, if you stop there and tap a photo, it comes up full screen. Why is that? Well, you need to do some additional setup to get the effect you want. Let's look at the Show Photo Picker method again. You need to set Defines Presentation Context to Yes on the Photo View Controller. This makes the Photo View Controller the current presentation context for the application.

When the View Controller was presented modally, that was because by default, the Windows Root View Controller is the current presentation context. Now, we've specifically defined it to be our Photo View Controller. Next, you want to set the modal presentation style of the Photo Picker to be the currently defined presentation context. In this case, the Photo View Controller. As an aside, we've also added a Presenting View Controller property that you can access from within a presented View Controller. This saves you from having to look up view hierarchies and try and figure out who presented the View Controller.

So now you're done. Your architecture is set up, and when you tap a photo, you get your photo picker presented in the way your graphic designer wanted. The other great thing about presenting your View Controllers the way you want, is that your users can still interact with the content on the rest of the screen while a View Controller is presented.

So let's wrap up. On iOS 5, you can now create custom container view controllers. These let you define your own layouts and application flow. You also want to make sure that your child content view controllers are encapsulated functionality. They should not be dependent upon other children. You also want to make sure that you're respecting the controller and view hierarchies. You've seen some of the issues that you can run into if you don't. But you also know how to fix them now. Finally, we've made presenting view controllers much easier, and we've given you more flexibility to present view controllers the way you want.

Next, let's talk about interface customization, and how you get that custom look and feel that your graphic designer created in your initial mockup. On the iPad, the default look and feel is a combination of silver and gray tones. You can see that here in the navigation bar and in the bar button item, and when you present your photo picker.

But your graphic designer wants this custom look and feel. In the past, it's been a little bit tough to get this custom look and feel easily. You've had to create custom views and do a lot of heavy lifting. And then every time the OS updates, if we've changed anything internally, then you have to change things on your end as well. Well, now you don't have to do that, because we've introduced the new Appearance API on iOS 5.

With the Appearance API on iOS 5, we've added tint colors to almost every UI element. We've also added background images to elements like the navigation bar, the tab bar, and the toolbar. So let's see how you can start implementing the look and feel you want in your application.

To get the navigation bar background, you call setBackgroundImage for bar metrics on the navigation bar. Here, you're passing in a UI image for the background image, and for the bar metrics, you've passed in UIBarMetricsDefault. This is for portrait orientations on the iPhone and iPod Touch, and all orientations for the iPad, since the navigation bar doesn't change size with orientation changes.

We've also added UIBarMetricsLandscapePhone so that you can use this for landscape orientations on the iPhone and iPod Touch. So now that you've set the background image for that specific navigation bar, well, you don't want to have to do this every time you have a navigation bar in your application. You just want to apply it to all of them.

So how can you do that? On iOS 5, we've not only added the Appearance API, but we've added an AppearanceProxy. And this is a way for you to set these properties, such as the background image, on your navigation bar throughout your entire application. You call the Appearance method on the UINavigationBar class, and then set the background image for the specific bar metrics that you want. This will now apply the appearance to all navigation bars within your application.

So this is great. Now that you have that woodgrain background, you just need to change the font for the title and the tint color for the bar button item. Let's look at how to do those. To set the title to a different font, you're going to use the same appearance method on your UI navigation bar, so it applies to all your navigation bars. And you'll use another method, which is Set Title Text Attributes. This takes a dictionary of text attributes, and you can change things such as the font, the text color, the text shadow color, and the shadow offset.

On iOS 5, we have 58 built-in fonts, so you can have a lot of variety. Then, for the bar button item, you're going to call Set Tint Color, again using the appearance method on the UI Bar Button Item class, so that this applies to all the bar button items in your application. And this tint color has been set to something that's going to mesh well with the navigation bar's background.

Great. So now you have the navigation bar looking the way you want. You have the custom font with the color that you want, and you've set the tint color to be what you want. But if you tap on the photo, you see that the navigation bar for the photo picker looks identical to the main navigation bar in the application.

Your graphic designer reminds you that you need to have this beautiful brown tint color instead. So how can you narrowly define rules so that you can get this look and feel for the photo picker?

[Transcript missing]

For the title text, you're going to do exactly the same thing.

You're going to set up an NSDictionary of text attributes, where you alter the font, maybe you change the color, and then you call AppearanceWhenContainedIn on the UINavigationBar class, passing in the PhotoPickerViewController class, and you set the text attributes to be the new dictionary that you've defined. Now, when you have a navigation bar in a PhotoPicker, its text attributes are these values.

So now if you tap on a photo, you can see that the tint color is what you want, and the font is what you want, but the background image is still showing over the tint color. Why is that? Well, if you look at the original code that set the background image for all navigation bars to be this woodgrain background, you used the appearance method on UI Navigation Bar. Meaning, all navigation bars now have this background image.

But when a navigation bar has a background and a tint color, the background image wins. So in this case, you need to use Appearance When Contained In, passing in the PhotoPickerViewController class, and set that background image to nil for the UI Navigation Bar class. This allows the beautiful tint color to show through. So now when you tap on the photo, you get the photo picker and it looks exactly the way your graphic designer wants. Great. Now let's look at some of the implementation details of the Appearance Proxy in API.

Just before Layout Subviews is called, we evaluate these containment rules that you've set up for your application. Then the specific customizations are applied, and know that these updates are only applied when the view hierarchy changes. So if you're looking to do this in real time, it's not going to work the way you may be expecting. Additionally, instance methods have been tagged with a #define UIAppearanceSelector. This lets you look at the headers for our classes, such as UIButton or UIBarButtonItem, and you can see which methods have opted into the appearance proxy.

We also leave your custom appearances alone. If you've applied a specific customization to a single instance of, say, a UI button, we won't overwrite your specific customization. When we evaluate containment rules, we will see your specific customization to a single instance of an object, and we won't apply appearances to that instance.

So, you can see, we've made extensive additions to UIKit. And with the Appearance API, you can really get the look and feel you want in your application. We've also set up containment rules using Appearance When Contained In, so that you can granularly define a custom look and feel for different areas of your application. We leave single instances alone if you've customized them specifically. So anything that you create, we're not just going to stomp on.

Finally, let's talk about some tips and tricks. Some things you can do now in your application and development to ensure that you don't have to do a lot of work over again later on. One of the things that we've added in iOS 5 is image stretching and tiling.

We did this for performance gains when using images that you need to resize. You can create an image that's resizable and set insets so that the image stretches or tiles appropriately and still looks the way it should. This makes it a lot easier to do localization within your applications. Here, if I set the insets on this image to be 0, 1, 0, 1, this goes from top, left, to bottom, to right.

So I haven't inset the top or bottom, but I have set the left and right by one point. Right. You can see in this image that I have rounded corners that I still want to look rounded, but I want the rest of the image to resize appropriately, and this ensures that the middle section is resized and stretched.

You can also do this with a larger image, and that middle section will be tiled across. So if you have an image with a pattern in the middle, you can resize that, and we will tile the center of the image and do the right thing to make sure it looks correct.

You can also do this as a 9-part image by setting the insets to be a value all around the image. Here, my insets are 1, 1, 1, 1, meaning that the center of this image is going to be stretched and tiled appropriately so that it looks correct if I change the height and width. We've also done this to animated images on iOS.

You can take an image that you've created, use a file name with a number at the end of it, and we will iterate through those files and you can set the duration and the insets so that you can resize the image and it'll still animate. Another area where you can benefit from image stretching and tiling is in your application bundle size. You may find the size increasing over time if you've made your application universal and have support for a few languages.

You may be reaching the cellular download size limit, but we're going to be stretching and tiling images to make sure that you don't have more images than needed for subtle size changes. Next, let's talk about the keyboard on the iPad. The keyboard has traditionally been docked at the bottom of the screen, but with iOS 5, we've added the ability for users to undock and split the keyboard. This means that your content needs to move and reflow according to where the keyboard is. You want to ensure that specific UI elements are not obstructed by the keyboard. No matter where the keyboard is, the user should still be able to interact with your application.

We've added some keyboard notifications to UI Window, and this is in the form of Keyboard Will Change Frame and Keyboard Did Change Frame. So you can know where the frame of the keyboard is and where it has been so that you can relay out your content appropriately so that you're not obstructing key areas of your interface.

Something else I've seen, though not exclusive to iOS 5, is anchoring views to the top of the keyboard. If you have a view anchored to the top of the keyboard, you want to be sure that you're doing this correctly. I've seen where the view's frame is hard-coded to specific values, and this can hurt you later on if we change the size of the keyboard.

In this example, we see that iMessage is doing the right thing. Maybe your user is using the Japanese keyboard, which has an auto-correction bar across the top of the keyboard. Suddenly, the frame of the keyboard is different, and you need to account for this so that the text field is still on screen. You want to ensure that the user can interact with your content. So you want to make sure that if you're anchoring a view to the top of the keyboard, you want to observe the Keyboard Will Show, Keyboard Did Show, Keyboard Will Hide, and Keyboard Did Hide notifications.

These notifications will give you user info keys, such as where the keyboard's frame begins, where it ends, what the animation duration time is, and what the animation curve is. When you observe these notifications, you can fire a method that may have a UI view block animation, or maybe you're doing some core animation to move your anchored view. But you can do this knowing where the frame of the keyboard is. You can do this with the same exact animation time, and in the same exact type of animation. Everything works together and looks very fluid to the user.

So now you have a checklist of next steps. First, you want to check and see that you're stretching and tiling images where you can. This keeps your bundle size low, and we've also done many optimizations under the hood to make sure that the performance is great. You also want to avoid putting text in images when at all possible.

In some situations you may have to, but in others you want to make sure that you're drawing the text over the image or using something like a UI button with a background image so that if you're localizing for another language, what may be a certain length in one language may be a different length in another.

You want to ensure that you're not having to create images for all these different languages. So now you have a checklist of next steps. First, you want to check and see that you're stretching and tiling images where you can. This keeps your bundle size low, and we've also done many optimizations under the hood to make sure that the performance is great. Finally, you want to use the frames that the keyboard notifications give you. You want to ensure that you're moving your content so that wherever the keyboard is, the interaction with your application is not obstructed for the user.

So today, you've learned how to use View Controller Containment to create dynamic and robust layouts for your applications. You've also seen how easy it is to use the Appearance API to create a wonderful custom look and feel for your app. And going forward, you can utilize the tips and tricks you've learned to be proactive to make extending your app in the future even easier. I can't wait to see how you use this information to make your new apps and existing apps even better.