Essentials • 1:06:38
The best solution sometimes involves just a small change in coding or thinking. Learn how best to take advantage of Cocoa Touch to improve your iPhone application. Hear tips and tricks directly from Cocoa Touch framework engineers.
Speakers: Andrew Platzer, Paul Marcos
Unlisted on Apple Developer site
Transcript
This transcript was generated using Whisper, it has known transcription errors. We are working on an improved version.
Good morning, my name is Andrew Platzer. I'm a... I haven't finished yet, so... My name is Andrew Platzer. I'm an iPhone engineer. I work on the UI kit. And today I'm going to talk about some tips and tricks along with my co-worker, Paul. And we're going to cover some of the classes that maybe didn't get full sessions and also some of the ways that you can make your application draw faster, respond to the user faster, and also some other setup stuff. So what we'll be covering today are... We'll be covering these.
So drawing, how to draw fast, how the drawing system works, some stuff about image and graphics, how scroll views work, some of the basic settings for that and how it's set up. I'll also be talking about how to display large content, like, for example, maps by using tiles. And then I'll talk about how to animate controls. So if you want to, for example, have them resized or have new components added, I'll show you some examples of that. And then Paul will be covering these sections.
So first to drawing. So you've got your screen, the button, and you touch. The system will send the event from the sensors to the framework.
[Transcript missing]
Once your Touch's method returns, we actually call back into your application. And here we call layout subviews. This method's available to any view, and any view that was added, or whose geometry is changed by either the size or the bounds origin, or explicit calls to set needs layout will be called.
And it'll work from the top down, calling each view. And what you should do in that call is lay out your subviews. So if you've got some parts to your view, you'll want to put them in certain locations. You can also call set needs display here. And you can change the geometry and the attributes. And you can begin animations here too.
Then after the layout subviews have been done, the draw rect calls will be called. So any view whose class implements draw rect and whose has been just recently, just been shown, or you call set needs display, or you have the view content mode set to redraw, will be called. They'll be called in no particular order, and each one will be given its own context. There's no sort of shared window context. Again, you can also queue up, you really shouldn't do any more animations, you should just be just doing drawing as quickly as possible.
All that information is bundled up, the attribute and geometry changes, the images, the view buffers that have been created by draw rect, any animations, and all shipped over to the compositor, and then to the graphics hardware, and something shows up on your screen. So you've got to remember this is, it's three phase steps are important. That's sort of what we'll be using through the whole other parts of the, the, the, the, the, the, the course. Drawing.
So if you want to efficiently draw content, you avoid DrawRect. The system isn't that fast. Doing a lot of fancy drawing, like gradients and so on, will be slow. Instead, use ImageView. Give your graphics art department something to do. They can draw all these PNGs in Photoshop. You just have to load them in and put them on the screen. So you want to use subviews to assemble the content here. So if you've got a lot of sort of complex parts, maybe you just need to draw a little bit, and then you can use another view for the remainder.
You want to use opaque background colors. I think it's mentioned in table talk. You know, you want to set the table cell to be opaque, other views opaque. If you even have an image view, tell us it's opaque. The system can copy stuff to the screen very fast if it doesn't need to blend between the view content and what's already there.
If you do need to use DrawRect, there are a few things to keep in mind. What you want to do is make sure that you only draw what you need to do. Don't get too fancy. Use images if possible. Factor out fixed backgrounds, as I said before. If you've got, for example, just a small label inside a big view, that doesn't change. Use a separate view. Put the label inside as a sub-view.
Size the view to the content, of course. Each view gets its own buffer. So if you're going to have screen-sized views, you're going to have screen-sized memory allocation, and you're going to run out of room pretty quickly. As I said before, turn on the opaque flag. If you're going to fill the view with opaque color or an opaque image or whatever, tell us it's opaque.
Turn off clears context before drawing. When we give you the context, by default, it will either be cleared out or set to the background color. But if you know that you're going to do some drawing like a fill rect... That's just going to set every bit with something else, tell us.
And then we'll save some time and we won't do anything. We'll give you a context that's got junk or whatever was there before. So you've got a guarantee that you're going to fill every pixel. Use set needs display in rect. That will set initial clipping so that when the call is made to draw rect, only the area that's been dirtied will be drawn to.
Everything else will be clipped out. And the system is very efficient about not drawing things. And use clipping while in the system. And you can use the inside drawing, of course. If you're drawing a long string and you only need to draw the first letter or something, use clipping and the system will not spend a lot of time trying to draw characters that don't appear. And don't try to animate. All this drawing is just not that very fast. There are other ways to do this and I'll show you that later.
So some things to keep in mind. I said every rendered view when the drawback call is given a context. This context points to its own unique buffer. So don't assume it's pointing to some kind of window context or window buffer. And this way we can have views that overlap.
So you don't need to make them too big. Draw Rect as a Set is called first time the view is shown if the Draw Rect method is implemented. Also on Set Needs Display or Set Needs Display in Rect. Or if you set the View Content Mode to Redraw and you've changed the bound size. And as I said before, the context for each Draw Rect will be different. Don't assume they're shared. And don't try to hold onto them either.
There's a few view content modes. Once you've drawn your view or you used an image and set it, you can position the content. The standard ones is the stretching to fill, but there's also an aspect fit and an aspect fill. For those of you with widescreen TVs, you know what that does.
It'll clip otherwise. There's positioning ones where it won't scale the content, but rather position it in the corner or on the side or in the center. And if the view isn't big enough, it will just clip. And finally, there's one special mode, which is used if your view implements DrawRect. And that's automatically calling set needs display whenever the size changes. So you don't have to worry about then going override set frame and go, if the frame size changed, then call set needs display.
So we give you a few utility functions if you want to sort of capture drawing in order to make this fixed background view. We have a special context that you can create. And that will create a bitmap backing store where you can use all the UI calls as well as the CG calls to draw.
And here's the sample code. And the first thing you want to do is call UIGraphicsBeginImageContext. And all you do is pass in a size, and that's just how big it is. The origin is 0, 0 at the top, just like it is in other drawing. You'll just do regular UI drawing, UI color set, string drawing, or you can just get the current context, which is a CG context graph, and use Core Graphics Context drawing directly. At any time, you can ask for an image. So you can do this multiple times. You can draw a bit, get an image, draw a bit. And you call UI Graphics Image from image, get image from current image context.
And that will return an auto-released image you can use. And then when you're done, you can call UI Graphics and Image Context. That will pop it, release any storage. And restore the previous graphics context. And for example, in this code, I draw a blue box, draw some red text, and you get a lovely blue box with red text.
We give you the ability to create stretchable images. What you do is you get an image and then you say, give me a stretchable version of that image. You pass in either a top cap height or a left cap width. We assume a one pixel wide fill and the remainder is the other side.
This is a new image which you can use and whenever you call draw and rect, it will stretch the content. So it will draw the caps fixed and stretch the middle fill. So for a horizontal case, I've sort of magnified the width there. That would be one pixel wide fill. It'll just draw the end caps and then stretch the fill to fit.
For a vertical case, same thing. And you can do sort of a nine-part image where the center pixel will be stretched and then the sides will be stretched and the top and bottom edges will be stretched and the corners will remain undistorted. This is not that fast to draw, so if you want to sort of draw something that's increasing in size, it will be noticeably slow.
Some image gotchas I just wanted to cover. PNG is the preferred format, as always. You can read in other formats like JPEG and TIFF. We support those, but they are slow. There are functions to write PNG and JPEG, and again, they are also very slow, but they are available if you want to capture some drawing or something and write it out.
For toolbar and tab bar, we automatically adjust the image. We'll take just the mask and we'll generate a selected and unselected gradient, for example, in the tab bar. And for the toolbar, we'll just generate a white, bezeled image. So you'll just need to provide the mask, no colors and no other custom images right now.
So now I'd like to talk about Scrollviews, another little introduction to a class that didn't have its own session. Here are the basics for the scroll view. What you need to give us first is the content size. Unlike some systems, for example, Mac OS X, where the content size is derived off a content subview, we don't want to require that you allocate a really large view. Instead, what you do is just tell us how big it is.
There are the bounds of the scroll view. This is sort of the visible area of the scroll, what the user will see. And what's important is the origin of the bounds, and that's what we call the content offset. When you say set content offset, it's just setting the bounds. And so that's sort of the visible area of the whole content.
[Transcript missing]
So now you've got a scroll view. Unlike Mac OS X, there's no scroll bar. So how do you know to scroll? How does-- if the user clicks on a view inside, how do you know if it's meant to be the click in that subview or you want to scroll? And what happens if the user puts their finger down and then starts to scroll, like for example in table view? So unlike Mac OS X, you need to have some sort of a little bit of communication between the scrolling system and your application. You need to tell us what to do.
So there are two methods that are important. One is touches should begin with event in content view. And what will happen is after a delay, the system will-- the scroll view will figure out which view you clicked in, and it will call this function. And you can say at this point, if that view is enabled, user interaction enabled is yes, do you want to scroll? In which case you return no.
Or you want to handle the event in that subview, in which case your subview will now start getting the same message, touches begin and touches moved. Now if the user moves their finger far enough, the system goes, well, did you want to scroll instead? Maybe they just paused for a moment. And that's where touches should cancel in content view is called.
Your content view is-- your scroll view subclass is asked, is it OK to start scrolling instead? If you return yes, the subviews, the content views, touches canceled method is called. And we just start scrolling from then on until the finger lifts. If you return no, which is the default for controls, then tracking will continue in your subview, and you won't be able to scroll until the user lifts her finger and starts scrolling again.
So there are some settings that you can use. One is delays content touches. And by default, there's about a .15 second delay before we decide that the user meant to touch in the content, one of the subviews rather than the scroll. You can turn that off immediately. And so that touch should begin with event and content view will get called immediately. And so you can decide at that point whether or not you want to scroll. Maybe they clicked in some area of the subview that isn't reacting.
You can disable content view canceling for everything. Instead of overriding the touch, it should cancel in content view. You can just say, no, you never can cancel. And once you start tracking a subview, that's it. And you can just turn off scrolling completely if you want to do something, not allow the user to scroll and just by setting scrolling enabled to false.
Another whole host of settings that you can also change, just to quickly review them. Bounces is what it says. If you're scrolling and it hits the edge, it will bounce a little bit past and come back. That also turns on sort of the rubber banding, where if you're already at the edge and you pull it down, it will spring back.
Otherwise, it will just stop. By default, if the bounces are turned on and the content size is wide enough that you can scroll, we'll bounce. But if the content is too small, we don't know which way you could drag. In this case, you can tell us. Always bounces vertical or always bounces horizontal. For example, the table view always bounces vertical turned on. So we know no matter how many cells you've got, if you've only got one or two, you can still pull it down past the top or bottom, but not side to side.
In Safari, if you've seen, you've got sort of like a little what we call a directional lock, where if you drag in one direction, vertical or horizontal, it will try to just keep dragging in that direction. So you can turn that on for your own views. Paging enabled gives you the, like in the weather app effect, where you just go from page to page. And you can do this horizontally or vertically, and it will page one scroll bounds width at a time.
You can adjust the scroll indicator positions. Let's say you've got an index down the side. You can just move the scroll indicators over so they don't overlap. And you can just by setting, for example, the right inset. And finally, there's one function you might find useful, and that's Flash Scroll Indicators.
And if you change your content size or your scroll view is shown for the first time, you'll probably want to show the user that there is a scrollable area. And that's what this does. For about a second or so, the scroll indicators will appear, and then they'll vanish. The same-- and they'll appear just as though the user had clicked.
Zooming. So we have some basic zooming features in there. We wanted to give you that gesture. If you want to get fancier, you can see the code from the multi-touch talk from a couple of days ago that shows you how to do sort of arbitrary zooming and panning. But this will give you some simple sort of map style zooming.
What you do is have a delegate method that's called View for Zooming in Scroll View. And you return that. And it should be a subview, not the scroll view itself. And as the user pinches, the transform of that scroll view will be changed. It'll just call set transform with a CGF in transform scale. And any subviews inside that view will be magnified or shrunk, depending on which way they go. There's not too much you can do while you're zooming, which is why we don't sort of keep telling you what the current scale is.
But if you really need to, you can override the set transform method and keep track of it that way. At the same time, if you just move your fingers, it'll adjust the bounds offset, set the content offset to keep that in, to keep sort of the content under your fingers. And then finally, when the user releases their fingers, you'll get the call scroll view did end zooming with you at scale. And then what you want to do is probably reset the scale, and redraw your content.
So now you've got a scroll view and you want to show some content. But you've got a lot of content. And there's just not enough room to load it in all at once. And so you're going to have to--
[Transcript missing]
And as I said, there are time versus space issues. In fact, there are both. Easier to expect smooth scrolling, but there's a limited amount of memory, so you can't load in all the tiles. And there's a limited CPU, which means you can't do as much drawing as you might want to do.
So for example, for Maps, what we do is we just break everything up into tiles. Square views, they're marked as opaque, so that's efficient. And we only really want to show-- We don't want to show what's visible. So rather than having 10,000 tiles, we just have maybe 20.
[Transcript missing]
So as I said before, layout subviews is called whenever the content size or content offset changes or the bounds origin changes or the size changes. So that's the clue that you need to relay out your content. You look at the bounds with the offset, that's your visible area. So a simple, for example, CG rect intersects rect will tell you whether or not the frame of the tile intersects the frame of the scroll view.
In your layout subviews, you'll want to hide or remove any views that are no longer visible, no longer intersect the bounds. And you'll want to add tiles to fill in new areas. If you're scrolling, usually that's sort of an L-shaped area, so you can make your calculations a little bit more efficient.
So as I said, keep computation to a minimum. Any drawing will slow things down. You just do a little bit of drawing, the user will see it stutter. And I'll show you how that looks. You want to maintain a pool of reusable views. So don't keep allocating and deallocating views, because it's just going to take more time.
Not a lot more time, but it's just going to slow down your scrolling speed, add to the amount of time per frame. So you can use the hidden flag, for example. Just mark the flag as hidden, keep the view off in a separate array or something, and then it's available for reuse.
You want to use separate view for content and changes. Just like in a table view, the background is pretty much the same. It's just like the label will change. So again, you'll have a UILabel subview. That's the only thing that needs to be modified as you reuse the cell.
And you want one little sort of hint is you can also keep the recycled views the same size. If it needs to redraw the view, if you're using DrawRect, just make it wide enough for the widest possible content. And then it won't need to allocate new buffers. And that'll just add an extra few percent to your scrolling. But you probably need it. I'd just like to show a quick demo of how some of the settings interact.
So I just got a little demo here. And it just draws a bunch of little squares. In this case, what it's doing is just redrawing every time the view needs to be displayed. It's still only showing visible views. But it has to redraw each one. It's removing it and adding it back in. And as I scroll--
[Transcript missing]
So I'd like to talk about resizable controls. And this uses sort of the similar techniques you've seen already with the tiles and using efficient drawing.
UI view animations and draw recto mix, as I said. Once it's drawn, once that view is drawn, that's the content of that layer that's underneath. And unless it gets redrawn, it stays the same. If you just change the frame, by default, it'll size to fit. So what you'll see here, for example, is-- but the default mode being scaled to fit, rather-- if I just, in an animation block, say, change the frame to be wider,
[Transcript missing]
What we'll do is override layout subviews, which is what you should do. And you can position the left and right caps at the left and right edges of the bounds. Then you can set the frame of the fill to the remainder. And it will automatically stretch that one pixel fill to cover the whole rectangle. And then finally, you just need to position the label in the middle.
And this is useful to do even if you're not animating. It just lets you localize all your layout to one function. So now we want to animate the pieces when we resize the button. We're going to change the width. So what we can do here is either in a block, an animation block, we can call layout subviews directly. Or inside your layout subviews, you can set an animated flag and do the animation there. So here, for example, you'll say set frame animated. Set the flag.
The layout subviews gets called. You'll check the flag. Begin and end commit animations. And just do the layout inside. And the button will just resize nice and smoothly. The center fill will be stretched at all times. The system will do that smoothly. The caps will be moved out of the way of the fill. And the label will be kept centered.
You can get pretty fancy with this. UISegment and Control does this. It's got a three-part background view, just like you saw. It's got three separate image views. It's got a set of dividers. So if you've got five items, it'll have four image views. They're just simple image views. They don't do anything. You'll have a selected background.
And we've got a couple of different end caps, depending whether the selection is at the beginning or at the end. and we've got content which will just be, you know, if you set the label or the image of the segment that's just stored in a UI label or a UI segmented view, a UI image view.
[Transcript missing]
What you want to do is, if you're adding a segment, for example, you'll add a new divider view on top of the existing divider view. You'll add a new image or label view. That's your content, and that will have its alpha set to zero.
[Transcript missing]
There's probably a little bit of delay before the content fades in, just to give it time for the dividers to move out of the way. And it looks nice and smooth. And at the same time, if you change the frame, it will also relay out those views for wider or narrower case. So you don't have to do anything extra. You can do a lot just by, inside a block, setting the location of things where they should be.
So that's my part of the talk, and I'd just like to recap. So for fast drawing, you want to work smart. You want to do as little drawing as possible, have everything pre-rendered as much as possible. For scroll views, it's a new world. You have to--there's more interaction between you with the application and the scroll system. You have to tell it when it can scroll, when it can't scroll.
As I said with tiling content, recycle, reuse all the time, just the little changes possible. Also use opaque views as much as possible. For example, in the contacts list, only the top header that might have some cells underneath it is opaque. The ones in the middle are transparent. The ones in the middle are actually opaque. So there's literally only a few little non-transparent, non-opaque views when you see it. And that's why we can easily get, you know, 30 plus frames a second scrolling a large amount of content.
and finally with Resizable Controls, break it up into little parts. And then you can move stuff anywhere. They can fly off. They can fade in and out. All our controls do that. If you're going to try to override DrawRect and UI Segmented Control, nothing will happen because we don't do any drawing. Now I'll turn the stage over to Paul to talk about the second half.
Morning. My name is Paul Marcos. I'm an engineer on the iPhone apps team. And we're going to go through kind of a variety of topics. Andrew focused more on view stuff. We're going to kind of jump around a little bit more here. So the first one we're going to talk about is text entry and table views. And this is a question that's come up a number of times, and it's a pretty common thing.
The example I'm going to use here is our settings application for the mail settings. Where you can see we have a bunch of text fields inside of the table view with a label. And if you've been to any of the other table view sessions or you've heard in a variety of sessions, we talk about using sub views to compose these cells. So in this case we have a UI label on one side, which is static. And then on the other side we have a second sub view, which is the text field itself. And that's the editable piece. So that's the structure of the views.
If you'd gone to the text session, you also heard about the automatic keyboard. And the idea with the automatic keyboard is that when the user taps in a field, they'll indicate that they want to start editing in that field. So the user taps there. The field becomes the first responder. It gets the insertion cursor. And in response to that, when we notice that the first responder has changed, the automatic keyboard will slide in.
And then the field is editable. And this is great. This is kind of the best case scenario where the user just taps and the editing begins. If we look at-- there are two other scenarios I want to look at that are a little bit more problematic. The second one is-- this is a
[Transcript missing]
When the keyboard is made visible and is hidden, there is a sequence of notifications that get posted using NS Notification Center. The four that are here are the Will Show, Did Show, and then the Will Hide, Did Hide. And those are the notifications you want to pick up on in order to adjust the size of your table.
So to do that, you would add an observer. This would likely be a controller, a view controller. That's managing the view that contains the table view. You would add a couple observers to pick up these notifications. And then if we look at, say, the keyboard will show method that we would write, we have to do a couple things. The first is we want to pick up the geometry of where the keyboard actually exists on screen.
And that's a piece of information that's carried by the notification that gets delivered to the observer. So we have to pull that out. It's a CG rect value, ultimately, the bounds that we're looking for. It gets wrapped inside of an NS value. So we kind of need to unwrap that.
But we pull that out, we get the bounds of the keyboard of where it will ultimately be displayed, and then with that piece of information, we want to get the frame of the table view, subtract off that height of the keyboard, and then we reset the frame on the table view to make it that shorter height. and in the will hide, you would want to do the opposite of adding that height back in to extend the table view down.
So this kind of approach of listening for the notifications and responding it, adjusting geometry is pretty common. One of the other questions that comes up in the area of editing text in a table view is, if you have a text field that's being edited, what happens to that if the user scrolls that off screen? And as you've seen in some of the table view sessions, we're pretty aggressive about reusing and putting cells that are now out of view onto the reuse queue.
And the question is, do we do that with the cell that the user is currently editing? And the answer is no. When a cell that contains the first responder is scrolled out of view, that cell actually is left alone, and it's left in existence in the table view. The field is still editing. The user can continue to type and type into the field as you would expect.
As soon as the first responder goes out of that cell, that's when that cell may be taken and put onto the reuse queue. So you want to make sure when that happens, by the time that happens, you've taken any content out of that cell that's of use, because that cell may go away at any time.
And a couple places that you can pick up on that happening is if you set up a delegate of the text field that's being edited, in the text field did end editing delegate callback, you can grab the contents of the field. And that's generally a good idea even for cells that remain visible just because you want to kind of get the content out as soon as the user is done editing it. You can also pick up on the control action for the field itself, UI control event did end, editing did end.
So those are just a couple of the issues surrounding editing in a table view, editing text. The next topic I wanted to move to is contextual toolbars. And actually before we get to the contextual part, I wanted to talk about just the toolbar part of it, because that really hasn't been covered in great detail in any other sessions.
One of the points I wanted to make was we have a number of bars in the system. And we have the navigation bar at the top, and then down at the bottom we have the tab bar, which is used for switching between views. And this is the toolbar that we're talking about.
So if you can try to keep the bars clear in your head. The contextual part that I'm going to talk about is if you look down here at the bottom of the screen, this is an example of a toolbar. We have this toolbar with a couple buttons on it.
And as the user navigates the hierarchy above it, going from the account list to the mailbox list, down to the message list, and then into a mailbox, you see the contents of the toolbar kind of changes at each level of the hierarchy. And that's what we're going to -- that's what I'm going to talk about in just a little bit.
On the UI toolbars in general topic, they are similar to the tab bars. And I'm going to try and keep the terminology straight. They're similar to the tab bars. So the UI toolbars that show up at the bottom, the visual display is a little different. The tab bars are typically that black style that you see in the iPod app or YouTube where there's a selected item that the user has chosen. So tab bars are also at the bottom. They're blue. And there's no notion of a selected item. They're just buttons that you click on and generally perform some action or present some view.
They do share the items that are in the toolbar are also UI bar button items, which is something that you can do. And they're also a little bit more specific. They're not just a little bit more specific. They're a little bit more specific. So the UI toolbars are similar to what the tab bar uses. And they have titles and images. They can show just text. Or it can be a custom view that you provide.
We have a whole bunch of built-in system items for some of the common icons, the plus button, the magnifying glass for the search field. These are actually all built-in standard icons. So where the functionality is similar to something we do, it would be good just for consistency to use the same system items so the user has a good understanding of what that icon does. And you can configure these both in Interface Builder. It has some great support for laying these out in a very flexible manner. or you can do it in code if you need.
So a couple examples just to set the stage of what you might do with these. These are how they would appear in Interface Builder. So this is just three items on there with a couple flexible spaces in the middle. And the flexible spaces here work to keep that camera image centered in the middle.
And this concept is similar to toolbars on Mac OS X. The idea of a flexible and fixed space items that you can put into a toolbar. Another one just with some text and a flexible space in the middle. So the buttons are kind of pushed to either side. And you notice the items can either be bordered or not bordered. In the top case, they're not bordered. In the second case, they are bordered.
And then the third one shows a custom view in the middle. This is from mail, so we have a custom view that shows the status of an account. And then two system items on either end. So let's see how you'd set this up in code. The first thing you would need is an array. We hold the items in an array, so we just create an array. And then you would go through adding UI bar button items to them. This is an example of allocating a system item. In this case, the refresh item.
So you create it. It's a control. It supports target actions. So you specify the target, the action that should be invoked when that item is clicked. You add it to the array. And then you would probably add some other number of items. And there's another example shortly. And then you would, on the toolbar, just call set items animated yes or no. And the animation will basically crossfade between the two sets of items, the old one and the new one.
So in order to accomplish the contextual part, there's a couple different ways you could approach that. And one fairly natural approach is what would be the wrong way of doing it. And in this case, the way that these views are structured is there are two controllers involved. There's a navigation controller, which is providing a hierarchy that the user is navigating left to right in. And then there are view controllers that manage the content at any given point in the hierarchy.
And the area, if we look at kind of what part of the screen each of these controllers is managing, the navigation controller is managing from the navigation bar all the way to the bottom of the screen. And the individual view controller is just managing the content underneath the navigation bar.
And in this example, since the navigation bar kind of, sorry, the toolbar appears in the area that the view controller, the individual view controller is responsible for, it's very natural to think, oh, let me just put the toolbar in there. And it would actually be managed by the view controller for the individual screen.
And that would be problematic because then as the navigation controller moved from screen to screen, the navigation, the toolbar would move with it. And in this example, we can see if you kind of keep your eyes on the toolbar at the bottom, as we move from level to level, the whole toolbar slides from side to side. And that's definitely not the user interface that we want to present.
So how would we do this? How would we do it the right way? Well, actually, it's fairly similar to what we do with the UI tab bar view controller that we provide. And the idea is you have the same pieces that we had a minute ago, but there's one additional controller that kind of sits above everything. And we basically elevate the toolbar out of the individual view controllers per view up to a higher level that's managed by this toolbar controller.
And the toolbar controller is managing both the navigation controller, as well as the toolbar at the bottom. And so we've seen the navigation controller's content has effectively been shortened by the height of the toolbar itself. And then the view controller, each individual view controller at the different levels of the hierarchy, is managing the content between the nav bar and the toolbar.
Then the question becomes, well, how does the top level toolbar controller populate the toolbar since it's now kind of disassociated from the individual view controllers? And the idea would be that as each new view controller gets put into the navigation controller, the top level toolbar would ask that incoming view controller, do you have any toolbar items? Where now the items of the toolbar are really managed by the individual view controllers at each level of the hierarchy, not the toolbar itself. And we'll see this in code.
So the individual view controllers at each level of the hierarchy would implement a method that returns just the toolbar items. And similar to the previous example where we built an array that holds the toolbar items, that's really all this method would do. So we create a few toolbar items.
Here we have a flexible space, which is one of the system items. And then we would add a compose item to it. And then we'd go and add a couple additional system items. These are all system items. Or sorry, we would create them. We haven't kind of put the array together yet.
Once we have that, we would create an array with the items ordered the way we want them to appear in the toolbar. So it would be the compose item on the left, then a flexible space, then the camera item, and then another flexible space and the action item. And that would yield a toolbar that looks like this.
So that's all the, just the items is what the individual view controllers would provide. And then in the top level toolbar controller, which is the delegate of the navigation controller, it would implement this will show view controller method, which is when the new incoming view controller is displayed.
And it would ask first, do you respond to this toolbar items method leveraging the dynamic nature of Objective-C? And if it does, we would ask for the items. And then given those items, we would set that on the toolbar.
[Transcript missing]
[Transcript missing]
So in order to set the status bar state, there are a handful of info P list entries that you would make in Xcode directly in your info P list. And the three ones typically of interest are the UI interface orientation, where you indicate whether it's landscape left or landscape right, depending if you want the user to turn the device one way or the other.
And then also depending on the UI in your application, you may want to set the UI status bar style. If you take over the entire screen, you might want to set it to-- sorry, that was the next point. Depending on the content of your view, it may be best to use the default gray status bar or one of the black status bars, either translucent or opaque, depending if you have content under the status bar or not. And if you are taking over the entire screen, you might want to just set the status bar to hidden using the third key, UI status bar hidden, and you set it to true or false. The default obviously is false.
So where you set these is in Xcode. You can select your Info.plist. That just shows you the standard plist editor. You can add keys to it and then add the appropriate values. And that information is used at launch time in order to configure where the status bar is, how it kind of animates in, whether it's shown, hidden, what the style is, or whatnot. That's why it's done in the plist as opposed to code, because the application is not running yet.
and you would just add your keys there. So now that you've indicated the state of the status bar, you would want to set your views up correctly for landscape mode. And an easy way of doing this is just in Interface Builder if you have a view, just change the size in the size inspector to be, you know, whatever the appropriate size is depending on the state of the status bar.
So you'd just set it to wider than tall, lay out your UI as you would, connect an outlet to the view so that you can access it. And then in the application did finish launching, you would want to set the center of the view, set the rotation, transform on the view.
And an important note here is don't set the transform on the window itself. That's also a very natural thing to do. That will not give you the results that you're looking for. That will kind of confuse some of the event handling. So you always want to just rotate or transform the view inside of the window, not the window itself. itself.
So if we look at some code here, here's an example of application that did finish launching where we have a view. It's been laid out in landscape mode. So we grab the initial transform of the view. And then we have to do some geometry to kind of figure out, you know, where is the status bar? Where should the center of my view go? Because as the status bar moves from the top to one of the sides, kind of the center position shifts a little bit up into one side.
So you can use the geometry of the status bar frame to do this calculation of where is it positioned, where should my content be positioned? And once you've calculated where the center is, you would set the center on the view, and then you just want to rotate it about that center. And you just do that by taking the initial transform, applying a rotation transform to it, and then setting that transform back on the view.
And then lastly, since we're doing all this before the main window has been shown so that the user doesn't see any of this, then you would make the window visible to the user. So that's launching in landscape. And then the next topic is detecting swipes. So you've seen in table views where you can have some content, and when the user taps and swipes their finger to one side, we reveal the delete button.
So if you want to incorporate something like this into your application, it's pretty easy. Basically, what you would do is override touches, begin with event, grab the point where the user started the drag, and then as they moved it, you would kind of track where it's being moved to and answer questions like, is it moved far enough? Is it in a straight line? You might even factor in things like how fast are they swiping? And if the user kind of starts moving in a direction or in a pattern that doesn't look like a swipe, you can do that. You probably want to kind of stop considering it as a swipe. You know, if they move their finger in an L shape or if they go back and forth, you know, that's kind of probably not, the user's indicating they're not swiping, so you'd probably stop considering it.
And so here's an example of actually how the table view would implement this, and this is some code that's actually taken out of UIKit. So in the touches begin, we just grab the starting point of the event, the location in the view, and then that's not particularly interesting. Over in touches moved is where the... the logic really is implemented.
And we basically grab the current position, we calculate how far has it moved from the starting point, and then in the case of table view, we just apply some fairly simple geometry of have they moved far enough in the horizontal position and yet not too far in the vertical position to qualify as a swipe? And then are they moving to the left or to the right? And then we determine, yes, it's a swipe and in which direction. So this is really, it's very simple. It's easily adaptable. It's adaptable to vertical swipes as well if you need to do that. And that was where the logic was.
And now the, I believe the last topic that I'm going to cover is handling interruptions. And so on the phone and even on the iPod Touch, there's a number of ways that your application when it's in the front can get interrupted. Things like an incoming phone call or an incoming SMS both on the phone and then even on the Touch, a calendar event may pop up an alert for a reminder.
And so what happens to your application when one of these interruptions happens? Well, right at the time the interruption happens, then some piece of UI is going to come up on top of your application and your application delegate will receive this application will resign active. Your app is still running. It's just not, no longer the active application. And the important thing to do here is that if you are, if your app is performing some action and taking up some CPU, you probably want to pause that or kind of back off on taking up CPU time.
So, for example, if you're playing a game or you're writing a game, you might want to pause the game because the user no longer is interacting with your game. They're interacting with something else. Or if you're running an OpenGL app, you may want to throttle back the frame rate that you're updating the CPU or using the CPU so that the, whatever the other interruption, the UI for the other interruption isn't jittery or whatever. And in fact, the template for the OpenGL projects in Xcode, you'll see that there is an implementation of application will resign active where we do actually throttle back the CPU. automatically.
And then given this interruption, the user has two choices. They can either accept the interruption, in which case we're going to take them somewhere else, to the phone app, to SMS, to calendar, to view the invite. Or they may decline the interruption, in which case they'll go back to your application.
So in the first case, your app will actually get terminated. And it's no different than if the user hit the Home button. You'll get an application will terminate. You should clean up your state, save anything, and then the app will exit. In the second case where they reject the interruption, your app will get application did become active. And then we just go back to your app. You come to the front and you can resume whatever it is you're doing.
So following down the path of the user did accept the interruption, they could-- let's say they go to the phone. The rest of these are all kind of in-call scenarios. They could go to the phone, menu out, and go back to your application.
[Transcript missing]
In order to do that, if you are using view controllers, they'll kind of account for that automatically and your view will simply be resized to make it shorter. If you're not using view controllers, you may need to pay attention to the status bar frame.
One thing, one case to watch out for is when the, if let's say you're in this call, you come back to your app, you have this beautiful double high status bar. If the call ends on the remote side, the status bar will actually shrink back down to its default state and your content will be resized taller to get those pixels back.
So you just want to, this is something you want to kind of incorporate into some testing where you want to make sure you account for that space properly. And the best way to do that using view controllers is just rely on the springs and struts and the auto resizing of the views.
Because then things will just kind of naturally, you know, slide back into place and it'll look very natural. And one last case to keep an eye out for is not only is there the portrait case, but there's also the landscape case where you have the double high status bar there where you've lost that height as well.
So that's just, that's the list of topics that we wanted to cover here. I think, where's Malcolm? We're going to have a, no presentation is actually complete until we have Malcolm Crawford come up on stage. And in this case, we're going to have Malcolm come up and do a demo of configuring tab bars in Interface Builder. Is that right? Yes. Excellent.
So a question that's come up on a couple of occasions during the last few days has been, how do you actually set up the recipes example using Interface Builder? So just to remind you. The Recipes application as it stands at the moment appears with a tab bar controller at the bottom and then a couple of navigation controllers.
So that, as you all know, is currently set up using code. So let's see how we actually migrate that across to Interface Builder. So I've got a copy of the project then that has an empty, the original main window nib file with no tab bar controllers. If I build and run this, you'll see then that nothing comes up. So to this we have to add the navigation controllers within then the tab bar controllers. So if I go back to interface builder, first of all, I'll drag out into here. The Tab Bar Controller and remind myself then to connect.
The Recipes application delegate to that. Now then we have to configure the tab bar controller itself and actually put those navigation items within that. For that, personally I find it easier to go into the outline view mode in Interface Builder. We don't now need the original two view controllers. What we do need are the two navigation controllers in the right place.
[Transcript missing]
If I now add a second navigation item and swap the order and inspect its view controller, I can set that now to be an instance of the unit converter table view controller. The other thing that I have to do is as a slight difference in this version of the application, because in the initialization method for the first recipe list controller, we actually passed the recipes controller as part of the initialization method.
In this implementation, I actually have to connect the recipes app delegate now to the recipe list table view controller so that it can then separately pass the recipe controller. And I'll show the code for this afterwards. So that should then be all of the code, all of the actual setup that's required. And now I'm back in the position where I was before.
So that's setting up then the interface in Interface Builder. I can actually extend that a little bit further just for the sake of it. So if I go back into interface builder, let's suppose that we're so excited by the weight converter that I actually want to bring that up as a view controller by itself. I can add that then as the first item within the tab bar controller itself and set the class there.
Oh, and I've, and... Just to show that demos need to be practiced beforehand, set the nib file for that view controller. So that's then illustrated adding a separate view controller separately to the top level in the tab bar controller. If we look then at the differences in the code, it's probably actually easiest to see that in file merge.
So the most significant difference is actually in the application delegate itself. So first of all, I mentioned that we had a separate connection now to the Recipe List Table View Controller because we want to pass the Recipes Controller to that view. All of the code then for actually creating a navigation controller and the separate view controllers is gone. That's all managed now in Interface Builder.
As far as the other ones are concerned, the significant difference that may have tripped up some people as they're migrating code to Interface Builder is, remember the units that you put into, or the elements that you put into your Interface Builder file are now archived. So rather than, oh, did I hear a few ohs in the audience there perhaps? So rather now than calling init with the name to whatever for your initialization, you have to implement init with coder, since these are now going to be unarchived.
So init with coder now in the recipe list table view controller and in the unit converter. So for those of you who wanted to know, that's then how you can convert the existing recipes example to using Interface Builder. And can I just quickly get a show of hands? One of the ironies for this for me has been that for the last decade or two, people have been wanting to see how do you do all that stuff that you're doing in Interface Builder actually in code because it's too abstract.
and So now we're left with this sort of ironic position where now you actually do want to see things in interface builder rather than in code. So quick show of hands please. Who would like to see the recipes example converted to interface builder? And who would like it left as it is? So, I'll see if we can do both, but okay, thank you to John Hess then for getting most of Interface Builder working. The other thing then that... The other thing that came up in the last session was how do you use interface builder for table view cells. And it just so happens that I wrote a demo for that as well.
So let's just first of all bear in mind the caution that Jason gave in the previous session. The idea here is to provide you with some catharsis to show that this can be done. You'll see that it can be done. Hopefully, that'll satisfy everybody. Don't do this at home. So the main resource that we have here is a table view cell.
[Transcript missing]
If we have a look at the project then, there's not an awful lot of code in there. The principle one, the principle method is the table view cell for row at index path method. And whereas typically in this method we would actually create programmatically the instance of the table view cell, here we'll actually just load the nib name. TV Cell with Self as Owner. That will then point our instance variable, a TV cell instance variable, at the new cell that's been loaded.
[Transcript missing]