App Frameworks • iOS • 46:15
It's easy to write an app for iOS, however writing a great app takes effort. iOS Engineers will show you how to improve your app's responsiveness and performance, while polishing the overall user experience. Discover new techniques for saving your app's state, starting quickly on cold launch, improving user awareness by utilizing animations, and working efficiently with network resources and GCD.
Speaker: Ian Baird
Unlisted on Apple Developer site
Downloads from Apple
Transcript
This transcript was generated using Whisper, it has known transcription errors. We are working on an improved version.
Hi, I'm Ian Baird, Engineer in the iOS Apps and Frameworks group, and I'm here today to talk to you about polishing your app: Tips and Tricks to Improve the Responsiveness and Performance of your Application. Today we're going to talk about a bunch of things, so let's go through them in the agenda.
First, we're going to talk about first launch. It's that most critical time of your application. It's your application's first dance with your user. It's going to color her experience with your application for the rest of the time that she uses your app. It's also going to affect her estimation of your skills as an application developer. Next, we're going to talk about workflow. Workflow is what happens during that 10 to 15 critical seconds where the user has removed the device from his or her pocket, launched your app, and begun to complete a task.
From there, we're going to move into a discussion about responsiveness. Applications, especially on the iOS platform or any embedded platform, need to stay responsive. It's critically important, especially on a mobile device and especially on iOS. And finally, we're going to discuss something which is often overlooked by people, but you have to sweat the small stuff. You have to consider the details.
The details are a collection of principles, thoughts, and ideas that, if correctly addressed by the application developer, will take your application from being something that people merely use to something that they love, something that they'll want to tell their friends about, something that they'll want to tweet about, something that they'll want to rate with five stars on the App Store.
So first launch. As I already said, it's the most critical time. It's your first interaction with your user. So you have to really make it count. And the first thing that the user is going to see after your beautifully rendered application icon is the launch image. You can see here on the left, we have the launch image for the Maps application.
You'll notice that there's a segmented control at the bottom, as well as two buttons. What you won't see here is you won't see text, because this resource is not localized. You won't see a splash screen. Even though it would be nice to call attention out to your company or to yourself, if you've done everything right and your application loads quickly, the user is only going to see the splash screen, or the launch image, for just mere seconds.
What the launch screen is there for is to provide something for us to progressively build your user experience. It keeps there from being a jarring transition from that first launch and springboard to when your app is up, running, responsive, and ready for input. So let's launch the Maps app. You can see how it built in progressively into its user interface. It's up and it's ready to go.
Next, once you've gotten past the launch screen, you're going to want to avoid one-star reviews. You're often now going to be in your application delegates and in app did finish launching method implementation. And it's dangerous in here. If you take too long to become responsive to user events, Mr. Watchdog here is going to kill your app, leading the user to think that your application has crashed on launch. So, to reiterate, Mr. Watchdog is going to kill unresponsive apps. You're going to see crash logs with 8 bad food.
And your user is going to rate your application: one star, crashes on launch. Now what can cause some of these hidden hangs? What can cause your app to become unresponsive and crash on launch? Well, you'd be a little bit surprised. I know I was when I started to dig into this.
First thing, there's the BSD DNS API. It's generally made up of synchronous calls such as "Get Host by Name," Get Host By Address. These leave you at the mercy of any misconfigured network that your user happens to be attached to. Or, if they're on a slower cellular network, you're going to be at the mercy of any sort of latency that's built into the system. If the latency is too great, the watchdog is going to kill your app.
The next hidden hang that people often overlook is use of the reachability API. You should use the asynchronous methods where possible. We do have synchronous methods, though, and it can be oftentimes very subtle to figure out which methods are synchronous. So you'll want to look at the documentation and understand how to use the API.
And why is this important? You may say, "Well, I don't use the BSD DNS API when my app finishes launching." "I don't use reachability." In many analytics packages, there are embedded calls to reachability. The analytics package has scraped all sorts of metrics concerning your application, and it wants to be able to upload all of these metrics to a server. So there is a call to reachability. If this call to reachability hangs and your app crashes, your users are going to blame you. They don't know it's your partner's fault, and they don't really care. So again, they're going to rate your app. One star, crashes on launch.
So, you're up, you've initialized any analytics packages you're using, and your app is going to start building out its user interface. In this case, it's often easy, and it's a mistake that many first-time developers to the platform make, is to create this massive, giant nib. And you know why you do it? Because it's easy.
It's easy to do. You throw all your view controllers, your views, your buttons, you reference a bunch of external resources, or internal to your app bundle, such as images, and you jam all of this into one big nib, wire it up, and off goes your app. The problem with this is it's slow.
So, how do you avoid the giant nib? If you're targeting iOS 5, I would urge you to consider using storyboards. If you build a storyboard, Xcode is going to take that storyboard and factor it in to discrete nibs for you. Or, if you're still targeting iOS 4, Break these nibs into discrete nibs. Good rule of thumb is about one nib per view controller. But you can break down even further from there if you want to. So, to illustrate this point, I developed an application, my NibTester, and it uses one giant nib. Let's watch it launch.
Wait, wait, it's coming. Oh, there it is. And finally it's responsive. You see, it took a while. It took a while to become responsive and ready for user input. This isn't good. I have one giant nib in here with a UI tab controller and three UI view controllers embedded into it. When I took this application and refactored it into three When I first started working with NIBS with their associated view controllers, I was able to realize A 44% savings in CPU cycles required to load my app.
Now not only does this translate into wall clock savings for you, it also translates into energy savings for your user. And that's really important on a device which is mobile, like the iPhone or the iPad. And when you consider it like this, why should the user have to pay the energy cost and the time cost to load your entire application, when they may only be using a small slice of it to complete her workflow? So size does matter when it comes to your NIBs.
Next, one thing that people have often done, and it seems to be correct, and it's a naive approach, and it works, but it will slow down the loading of your application, it will make it less responsive, is to not lazy load UI resources. And by resources, I'm not only talking about images, I'm talking about making database requests, I'm talking about fetching model objects, I'm talking about parsing and loading plists, doing things like that.
You'll see it in many times being done in the View Controller's initializer. This is probably not a good idea, because you're going to have to pay this cost right when the View Controller is created. Even if the View is never used in the user interface, you're still going to have to pay this cost to reconstitute this massive image.
So instead, if you're using NIBs, you should consider using ViewDidLoad. This waits until we actually touch your View, which is under management by the View Controller. At this point, it's a great time to go ahead and load images, to send out fetch requests, to grab resources. Or if you're not using NIBs, consider using LoadView, or overriding LoadView.
So your app is up, you've loaded your UI, and we're going to talk about workflow. Consider that most of your users may be at the soccer field, watching their son or daughter play soccer, and they might just have a couple of minutes to pull out their phone and check their Twitter feed before a big play happens.
Or they're in the grocery line and they're about to check out. They generally have between 10 to 15 seconds to accomplish whatever goal they set out to accomplish when they started your app. So workflow and a consideration of workflow is very important to creating a responsive and performant iOS app.
The first thing you'll see a lot of apps do after they load is they'll hit you with an alert. These are not good. They interrupt flow. They can only represent a very small amount of text. Any inappropriate use of an alert will annoy your user. It's my least favorite alert of all time.
We can't really do much else. You have to rate the presentation. I can remind you later. You can tell me no thanks, but if I don't serialize this anywhere, I'm going to ask you again. It's really annoying. And if you create a good app, users are going to want to rate your application anyway by going to the App Store. They're going to be incented to tell their friends. You can come up with better ways to incent them to go to the App Store and rate your awesome app.
The next alert I'd like to talk about are the error alerts. What the heck does this mean? And more importantly, it begs the question, what state is the application in now? Am I actually going to be able to accomplish a task that I set out to accomplish when I took the device out of my pocket? I'm not sure. All I can do is dismiss it. Again, they don't give you very much context. You can only represent a small amount of text here, or a very, very discrete choice, and you're not getting that with this.
So, I'm participating in my company's Bike to Work Challenge, at least I did in May. I have a team and I see some of my teammates in the audience. And I'm sorry I didn't register my points last week, I was busy working on this presentation. But with that notwithstanding, the days where I'm being a good boy, I ride into work, I put up my bicycle, and I grab my device out of my pocket.
It's been tracking my ride as I ride into work. We get points, and we get t-shirts, and things like that for riding into work, and register our points. I go to upload my ride to the server, and I'm greeted with this: There are a few problems with this interface.
There's too much information, really, and it's the wrong kind of information. I have an indeterminate progress indicator spinning, so I know something's going on. And then I have a determinate representation of progress in bytes, which is pretty good. Good thing that I'm a computer geek, because I kind of understand what's going on. I know that they're probably uploading GPS coordinates, my time, my avatar, etc.
But there's something fundamentally wrong with this. You're presenting the user with the option to cancel, and you're not telling her what's going to happen if she elects to cancel. Does her ride, does my ride, go to the great bit bucket in the sky? Is it cached back and then uploaded again later when we can talk to the server? Who knows? My options are to let it spin or cancel it.
So, that being said, I'd like to show you what I would consider a good way to deal with a transient error, an error that comes and goes. You can see my dialer application here, and I have the phone number for the Yellow Cab company in Cupertino, just in case you ever need a ride. But right now I've assigned it to Johnny Appleseed. And we're having a hard time reaching the server. So I'm going to show this.
I'm just going to push in a little view from the bottom, and I'm going to tell people that we're unable to connect to the server, but I'm not going to obstruct their workflow, because I don't need to. They can actually continue on, they can continue using the Dialer app, So you might want to consider when you're talking to the user and when you're presenting alerts, do I really need to interrupt the flow? There are appropriate uses of alerts, and I've alluded to that in the last slide.
Only use an alert when you're going to offer critical information and choices, essentially when the user gets to a fork in the road and they have to make a decision before they can proceed working with your application, accomplishing the task, such as for a login. This is a big one. My wife reminded me of this when I was preparing these slides.
She said, "Remember that time that you guys released an alert and you didn't spell check the text, and then it showed up on the internet?" It was very embarrassing to say the least. So alerts bring this to the forefront, but you should always spell check any text resources that you include with your app.
Finally, I would urge you to refer to the iOS Human Interface Guidelines. It's going to tell you what the appropriate uses for alerts are, and it will even provide examples. It's great. Next, I'd like to talk a bit more about workflow and really get back into the main groove of workflow. And we're going to talk about minimizing taps.
I'm going to stress that the user has 10 to 15 seconds to accomplish whatever task that he or she set out to accomplish. In this case, in this contrived example, we have three Shakespearean plays, and we're going to ask the user to rate the passages. You can see that the user is able to tap on rows, and this exposes the ratings chrome. We're not pushing more view controllers. We're not showing modals. We're showing the ratings interface right in line. We're saving taps.
We're saving time. We're creating a concise workflow for the user. So for more information about workflow, especially with respect to user experience, I would urge you to attend the Designing User Interfaces for iOS and Mac OS X apps, which is going to be given in Pacific Heights tomorrow at 9:00 a.m. Yes, it is going to be worth getting out of bed for. I know I have trouble with 9:00 a.m. too, but it's going to be a great talk. Set your alarm.
Moving on, we're going to chat about responsiveness. It's difficult to maintain a good workflow if your application is not responsive, if it hangs, if it sort of judders when you scroll. These are all things which you want to avoid. And the reason why you want to avoid them is not only for the reason of being in service of workflow.
You want to preserve the illusion that goes with an iOS app. iOS apps have been, and iOS itself and the iPhone, have been described, and not just by Apple, as magical devices. And they're magical because there's this illusion to the user that she is working directly with elements inside of your application. She's not interfacing with software. She's not interfacing with a device. They're interfacing directly with items. And when your app is unresponsive, when it doesn't load quickly, when it throws up an alert, you degrade this illusion to the point that it may not exist anymore.
So, regarding responsiveness, there are a few key touch areas that I want to discuss today. Dealing with network resources, image handling, and TableViews. NSREL Connection is really the workhorse of any network resource activity in your iOS app. There are a couple of ways to use it. One, you can create a synchronous request. And there are some problems with synchronous requests, and I'll go through a few of them. You have no control, one, over any of the redirect activity that occurs. It's just going to redirect to whatever the server tells it to, wherever the server tells it to go.
Two, you're going to get back a big bag of bytes as an NSData. Unless you have a really good handle on how large this big bag of bytes is going to be, you may end up allocating too much memory, running out of memory, and your app is going to be killed.
Finally, and I'd say almost most egregiously, there's no way to cancel this. So if you create a request and then you no longer need that request, you don't have any way to turn it off when it's in flight. So I'd urge you to use the asynchronous API, like connection request or connection with request, and passing usually your controller, but some other object, which implements the NSERL connection delegate protocol, as the delegate.
Why is this important? Why is cancellation so important? Well, you can see here we have an app, and it's going to be using an item from the cloud. We see we're in the initial view controller, and we have a button to navigate to the subsequent view, which is going to fetch an image. So we do that, and our server is unresponsive. It's taking a while. So the user elects to go away from this view and go back to the initial one.
At this point, we need to be able to kill the request because we no longer need the resource. If we keep the request going, we're going to end up burning CPU. And more importantly, we're going to end up using the radio, especially if the user is using 3G or some other cellular data service. We're going to keep the radio on. And so the user is going to pay a battery cost for this.
And then if they don't have an all-you-can-eat data plan, you're going to end up costing them money, too, for a resource which they elected not to view, which is bad. Any discussion of networking resources would be incomplete without discussion of caching. In iOS 5.0, it's even more important.
You should take a look at NSRL request. There are a whole bunch of caching policies there. I'm not going to go through them today. And there are also some server-side considerations. I would urge you to look at RFC 2616. It's kind of heavy reading, but especially take a look at the last modified entity header and the e-tag response header. These are going to help you define how long cache items should live in the durable cache on iOS 5, the cache that can live between application sessions. So... You've defined your NSURL connection delegate, and you've started your asynchronous request.
How do you deal with the data? Well, one common pattern is to create a big buffer, like an NSMutable data, and as bits flow in off the wire, you tack them on the end of the data buffer. Then when the request completes, you process that buffer in one fell swoop, a lot of times on the main queue. I would say this is pretty bad. Again, responsiveness will suffer if you're processing a huge chunk of data on the main queue.
And secondly, you're really going to increase the amount of memory that your application is using, and that's not good either. So, I decided to create an example to illustrate this, of how you should endeavor to process this data. If you can, you should endeavor to process the records at a protocol or a data boundary as they flow in off the wire. So you can see here, you can see my face looking down on you, you can see my avatar is surrounded by some metadata. It's got my name, it's got a record number, it's got all this stuff.
Now, we could process all of these records, or save back all these records and process them as I was saying in one fell swoop, or, since we know the record boundaries, we can turn them immediately into model objects. And then toss the data as we go. And then the next record flows in. You can see, say, a picture of my boss here. He has a humongous brain.
We're going to take this record, process it into a model object, and then toss the data. And we can even do really cool things here, like write the avatars out to disk, so that we don't have to hold onto them in critically and possibly limited system resources like memory. So I touched on images just a little bit in the last slide, but I'd like to talk about it some more. It's critical that you understand how to deal with images in your iOS app.
The first lesson I'd like to share with you is that JPEGs are expensive, and I learned this the hard way. I had a recipes app before I came to Apple, and we had these beautiful parchment backgrounds. And as the user swiped back between the recipes, it would go back through time.
And as the recipe went back in time, the parchment would yellow with age. It would begin to show age spots. It was a gorgeous effect. But what it did was it swelled our app bundle up to the size that we were dangerously close to the over-the-air limit for downloading our application at the time.
So my partner and I decided to recompress all of our backgrounds into JPEGs. And this was awesome, until we noticed that there was a humongous performance hit. And when I opened up instruments, I saw that we were spending humongous gobs of time decompressing JPEGs every time the user would page between recipes. And so we went back, we ended up using some tiling techniques, and we converted these images into PNGs.
Now, pings in your app bundle are going to be optimized by Xcode for you as part of the build process. However, External pings are still going to need to be optimized by you, and you can use the Ping Crush utility to optimize them before you put them up on the server and refer to them in your app. Another interesting concept, a concept that is often overlooked, is that you can take smaller images and make them into bigger images.
You don't have to use massive gradients or other uniform images in the background of your buttons or table view cells. What you can do is you can simply feed this 1x50 pixel strip in and tell the image view to stretch it out into a gradient for you. This is going to save memory and it's going to be faster.
The same goes for tiling. You may have a regular pattern, such as a pinstripe, which I'm going to use here. And what you might think is that what you want to do is because it's a relatively complicated pattern. You'll want to make a huge pinstripe background and drop it right behind your table.
But you don't want to do this. You factor it in to a tileable image, tile it out, and stretch it using the API. Again, that's going to be faster, that's going to be more efficient. If you're rendering artwork, if you're tinting artwork, adding shadows to it on the fly, you'll want to consider caching some of it in order to maintain the responsiveness of your app. You don't want to have to recreate this every time it's called for, especially if you're using multiple instances of it in something like a table view.
The naive approach to this would be to stick it all in an NSDictionary and key it maybe by tint color or something, for example. I would urge you not to do this. You should consider using NS Cache. Why? Because you can avoid the abandoned memory problem, where you create this cached item, this piece of artwork, and you never refer to it again, but yet it lives for the rest of the lifetime of your app.
Also, NSCache is going to evict its contents during memory pressure, making your app less likely to be killed. And again, as I talked about, you avoid the abandoned memory problem fairly neatly. You can also evict cache items programmatically. So you should take a look at the NS Cache API.
Finally, I'm going to talk a little bit about table views. Table views and cells. Table views are really bedrock, foundational, is a great example of how to improve your app's performance. He's been working on a new app called "Table Views" that will help you customize your app's look and feel. It's a great app to use to improve your app's performance. It's a great app to use to improve your app's look and feel. It's a great app to use to improve your app's performance.
You should use instruments to find these performance hotspots and to fix them. And to that end, I've prepared a very simple demo showing you how to fix the performance of a customized table view and its table view cells. You can see here we have a gradient background for every table view cell. It's tinted blue.
We have a text label. And we have a bunch of app icons which are being ramped up and down in opacity to create a fading effect. Now, you may think this is the projector playing tricks on you, that it couldn't possibly look this bad, but let me tell you, this is absolutely crushing my iPad 2. Let's try to scroll. Oh, this is even more awesome.
When you're faced with a problem like this, the first thing you should do is look to the tools to guide your search to help you figure out how to fix the performance and responsiveness issue, which is highlighted in your application. So let's do that. Let's go into Instruments. If you've used Xcode 4 before, this will be old news to you, but if you haven't, you can dive directly into Instruments by going to the product menu and using the profile menu item.
This will build your application, run instruments, and run your application directly on the device. Going to use the core animation trace template. And this doesn't quite give me everything I want, so I'm going to pull open the library. And look here in System. And I'm going to pull in the CPU monitor so we can see how much CPU we're using as well. Sort by CPU. And you can see we're using about 90% of the CPU. This isn't good at all. This is a battery vampire of an app.
You see we're getting about four or five frames per second, and it really doesn't get much better when we scroll. Let's go and see what we're doing in the sampler. There's a lot of data here. We can hide some of these system libraries and see that we're spending most of our time in DrawRect. Let me just zoom in so you can see this. See that we're spending a lot of our time in DrawRect. And the way I hid the system libraries was to use this check in the call tree.
So, let's go take a look at our draw-rect method and see what we're doing. Okay, we're rendering the background, creating a tint, gradient, there's a label text here, and then we're rendering the image. I'm going to go to the controller and show you how we're accomplishing the effect. We're using an animation timer which fires once every 60 seconds, and we're calling the animateCells method, which is querying our table view for a collection of visible cells, and it's asking them to perform this selector animation step.
You see we're changing the icon opacity, and at the very end of it we're calling selfSetsNeedsDisplay on the cell, which is causing it to be redrawn. I think I showed you that this is highly inefficient. You should probably take me at my word. Is that correct? Is this pretty inefficient? Okay. So we can fix this. We're obviously misusing the API. So let's refactor the icon fade effect.
I'm going to come over here to Demo Monkey, grab my snippet. And you can see that when we set the icon image now, we're going to go ahead and add the shadow to it, but then here's the key bit. We're going to kick off an animation using this.
We're going to send the alpha of the image view back to zero, which is going to fade it all the way out. But also, we're going to execute that using the animate with duration call to UI view. And you can see here, I'll show you, I'm hitting option and clicking, that we're going to repeat the animation indefinitely.
And we're going to auto-reverse. So after the alpha is set all the way to zero, we're going to climb all the way back to one, and then back to zero, and so on. So I've written myself a couple of notes, I've removed the timer, and we can now remove this code, which was rendering the image from DrawRect. We're going to get rid of the timer. Get rid of the timer and get rid of this animate cells call. We don't need it anymore. Now, when we build and run, there. Now it's actually smoother.
But we're not done. You see as we scroll, we can get a little bit ahead of the table view, and the performance dies. This isn't good at all, and what we can do to fix this is we can get rid of the draw-rect call entirely. The next thing we were doing in DrawRect, or one of the last things, is to draw the text. Let's get rid of that and use the built-in label.
To init, insert this here, styling the label properly. And finally, we're going to add accessors using the embedded text label that comes with every UI table view cell and setting the string of that text label to our icon label text. Going back to DrawRect, you can see that the next thing that we need to kill off is the gradient. So, we can do that. Actually, let's just kill the whole thing. Gone. I love refactoring code by using the delete key. How about you? "We pull this in and put him there. We've got all of our reminders now, done all of those things. We can stop.
And build and run. And you can see that it's smooth. It's much smoother than it was before. But I can still detect there are momentary times where the table view can outrun it. I don't really know what's going on here. I've killed off DrawRect. And back to instruments. I'm hitting Command-I to get directly back in. And this time, I'm going to use the allocations trace template.
And I'm going to look for allocations concerning table view stuff. And you can look here while I'm scrolling, and I'm going to zoom in so that you can see it. That as we're scrolling, we're allocating loads and loads of table view cells. And this isn't good. Any sort of memory allocation, any sort of object build up and tear down is expensive. So, I'm obviously not reusing TableView cells. Let's go fix that.
Put in our cell reuse support, and we're going to reset the effect. We're going to drop our reference to our icon image. And we're going to go to our controller. And you can see here why cell reuse is not working. I have not set up a table view cell identifier.
This is fixed. Let's build, run, and go into instruments again. And I'm going to scroll the app, and as I scroll, you should see how many transitory objects we're creating. Zero. And if I flip back over to the iPad, you'll see that it's smooth. We scroll like butter. Whoops, hello Notification Center. I can't outrun it. And we're done.
So that was the demo. We noticed in the demo that some of the key points were: Do not fight the API. If you're spending a lot of time doing something, especially with respect to graphics, compositing, graphics handling, go back and read the documentation. Take a look at what Core Animation provides.
Take a look at what UIKit provides. And see if you can refactor your code around that. Next, we used instruments to find the hotspots in the application. We were able to see the hotspot in DrawRect and fix it. And then we were also able to see when we weren't reusing table view cells and fix that.
And again, I can't stress enough, we see it all the time, reuse table view cells. Building them up and tearing them down as a table view is scrolling will hurt responsiveness and hurt the performance of your app. This brings me to the details. Again, as I described earlier, the details are a collection of items when, if considered properly by the application developers, will make your application an enjoyable experience for your end user.
You won't have to pop a rating snag asking them to rate your app on the App Store. They'll tweet about it. They'll actually make their way back to the App Store and give you a five-star review. So, without further ado, we're going to talk a little bit about appearance.
Then we're going to have a discussion about the correct and tasteful use of animation. And then finally, or next to finally, I'm going to discuss an often overlooked feature of iOS, the care and feeding and management of the status bar. And then, now finally, we're going to discuss your application's last act, right before it moves into the background: state saving.
So, you can see my application, my boring nav bar, and my boring cell. I'm not going to do much about my boring cell. We already had a talk about table views. But you know what? I can fix the nav bar. And I can do it without subclass or category hacks or method swizzling in iOS 5.
And to do that, I would urge you to attend the Designing User Interfaces for iOS and Mac OS X Apps session. Again, that's going to be held in Pacific Heights Wednesday at 9:00 AM. and the Customizing the Appearance of UI Kit Controls session, which is going to be held in the Presidio Wednesday at 2:00 PM. Sorry, I had to tease it. For animation, I thought I would do a simple A/B comparison.
The A side, the left side, is the version of my application with all animations turned off. And the right side uses the standard traditional animations found in UIKit applications or in iOS apps. Let's see how this looks. The user is going to push into the next view controller and immediately be popped back out when they hit back. This is confusing. I have no idea where I am in the context of your view controller hierarchy, and thus I'm confused about the state of my workflow.
So, in UIKit, we solve this by using a transitional animation as one view controller pushes in over another one, or another view pops out over the previous. This helps to tell the user where they are in your view controller hierarchy, and consequently, they know where they are in their workflow. They're not lost.
This ties in to responsiveness, and it's the scrolling bounce. And it's this idea that an application or a user interface element which responds to touch should always respond to touches, even when there is no more data to be seen. So to illustrate this, I have our friend, the table view. It's a group table view full of cells. We're going to scroll to the bottom and see what it looks like when animations are turned off.
Reach the bottom. You have no indication of what the application is doing. You scroll back to the top. Has this application crashed? I don't know. Or did you simply run out of data? The user does not know. So, in iOS, when you scroll to the bottom of a list of table view cells, we bounce.
And when you scroll back to the top, we also bounce. You're out of data, but we haven't stopped responding to touches, haven't stopped responding to gestures, and that's critical if you're designing your own user interface elements, to consider that responsiveness, that you consider responding to touch even when there's no more data to show the user.
I'd like to chat about the status bar. Again, it's easily overlooked, but it's one of the most important user interface elements that we surface in iOS, because it shows the user, A, what time it is, B, if there's a network connection available, and C, most critically, the expected battery life for the device.
How much longer is this device going to last before I'm going to have to plug it back into a wall? So you should have a really darn good reason to hide it. And you should only hide it if your application is a game or has full screen media. And then if you hide it, all of your Chrome should be able to be retrieved by a single tap.
Finally, you should display the network status indicator when needed. When something's going on, it's important for the user to know that you're sending bits back and forth over the air. Maybe they're going to put the device into airplane mode. Who knows? And on the iPhone, especially if you're customizing the navigation bar or any other user interface elements that are close to the status bar, you may want to set an appropriate color to make sure that you don't clash with the status bar. And finally, I'd like to talk about the last act that your application is going to perform: state saving.
It's very important that as your application transitions into the background, you serialize a representation of where the user is in the application. That way, if she switches to go to another app to accomplish this workflow, a fairly complex workflow, such as Safari, to fetch some information which will be pasted back into a text field, that if your app is asked to quit, it starts back up in the same place as the user left it. So you'll want to consider serializing out your navigational state, where your user is in your view controller hierarchy, what is visible on screen.
And you may want to consider serializing any partially filled text fields, which of course you're going to wipe out before the multitasking architecture or machinery swings into place and takes another launch image for your user because you don't want to capture the contents of any text field. And I must stress that you don't want to serialize anything that's secure. Any passwords, pin numbers, anything like that, of course, you don't want to serialize to unsecure storage.
Finally, for extra marks, you may even want to serialize out things like scroll position. Again, it should be completely transparent to the user that the application was asked to terminate in the background and it had to restore when they came back to it. So, we've gone over many, many, many details today.
And in summary, We talked about the first launch. This is that critical first dance that your application has with the user. This is going to color their perspective of your application and their perspective of you as a developer for the rest of the time that they use your app. So it's important that your app launches quickly, it shows the correct launch image, and is responsive to touch as quickly as possible.
Then we talked about workflow. What happens during that critical 10 to 15 seconds that the user has your application out and is attempting to accomplish whatever task they originally set out to? And next we talked about responsiveness, not just in service of workflow, but also in service of creating the solution of direct interactivity with the elements shown and provided by your application. And finally, we talked about details. That collection of items that if you consider are going to make users love to use your app.
For more information, you're going to want to talk to Bill Dudney, our Application Frameworks Evangelist. He's reachable at [email protected]. I'd urge you to go and look at the iOS Human Interface Guidelines at developer.apple.com, and I'm not going to read the rest of that off. And then finally, who here has been to the Apple Developer Forums? Let's see some hands.
Great. Utilize this resource. You're going to be able to give and get peer-to-peer support. Plus, Apple Engineers will drop in from time to time, answer questions, give feedback. We can talk about radars and all sorts of other things there, and help you with your app. It's a great resource.
I urge you to utilize it. And here again are the sessions which I've called out today. The first one is Designing User Interfaces for iOS and Mac OS X Apps. That's going to be in Pacific Heights, Wednesday at 9:00 AM. Customizing the Appearance of UI Kit Controls, which is going to be in the Presidio, Wednesday at 2:00 PM. That's going to show you how to customize nav bars and things like that without hacking. And finally, improving the stability of your apps, which is going to be in the Presidio, Thursday at 11:30 a.m. Thank you.