Essentials • iOS • 1:01:52
Learn about the new application state restoration feature in iOS 6. This new feature makes it easier than ever to save the state of a running application and restore it on subsequent launches, improving the user experience over the course of the application's lifetime.
Speaker: Gordie Freedman
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.
Good afternoon. Welcome to WWDC. I hope everybody's been having a great time so far today. Thank you. My name is Gordie Friedman. I work on UIKit at Apple, and we're going to talk about saving and restoring state in iOS applications today. So before I start, just to get a sense, if you've written an application, could you raise your hand? Anybody who's-- wow.
Very good. Okay, and if you're new, if iOS is new to you, bring it up just to see if we got some new people. Okay, a few. Welcome. And if you're currently working on a new app or revising yours heavily, I just wanted to see. We got a lot of coders here. People are busy. All right.
That's excellent. If you have an application, I want you to leave here knowing what you can do to implement state restoration for the app, to add it. And if you're working on a new app, I want you to leave knowing, you know, how you can put this in as you go along, hit the ground running with the new app. And if you are new, for a few new people that we have, I just want to continue to give you insights into our development environment and one more good reason to want to write an iOS application.
So I'm going to talk about four things today. I'm going to describe state restoration, what the feature is, why we decided to do it. I'll talk about concepts, things that are important to understand to be able to make the most effective use out of it. I'm going to discuss how you can incorporate it.
I'll go through some examples and then we'll show what we did so you can see how you put this into your app. And throughout the talk, we'll kind of describe some tips and tricks that'll be useful as you go along. This is a new feature in iOS 6, brand new API as part of our SDK.
All right, let's get started. So what is state restoration? The main idea is when an application starts up, it goes right back to where it was the last time the user had used it. So in order to do that, you need to preserve the application state. And I'll talk a lot about that.
It's both what you see and also the behavior of the application. So we don't want to just come back up with a thin veneer of what the user was doing, but we want it to appear as if the application had just been in the background the whole time, even though it exited. So we want it to come right back to where the user was.
This is similar to the application lifecycle we have on the desktop. So if you've seen that or used that, it should be pretty familiar. There's a few differences, though. One big change is on the desktop, applications are driven with one or more windows. So you have one window, a few windows in your app, and that's kind of the top level unit of coin. On iOS, we use view controllers, and we have a hierarchy of those. So here we'll focus on stitching back our view controllers, and you'll see how that plays in state restoration.
If you run an application the first time on iOS 6, nothing unexpected will happen, both for the user, your code won't encounter anything weird. This is entirely opt-in. So you control whether you want to do this or not. It's very easy to use, but we just want to make sure no surprise is out of the gate.
You can also do this in phases. You can pick a couple important things, save and preserve that state, ship that, or just do incremental development. So you don't have to do it all or nothing at all. You don't have to start off and adopt a whole bunch of stuff. You can kind of ease in.
Throughout the talk, I'll be describing semantic state, so I just want to go into what I mean by that a little bit. So, essentially, we're looking at what your application understands. So it might be that the user is editing some media, manipulating some photos, maybe they're doing something social. This is all the semantic state.
And we want to try to save things from the top level, so save it from the top on down. As developers, you know what your application is doing and how it's built and all the little tiny parts that go into it, but we really want to think from the top down. Now, aside from what the user is doing, there may be some things happening in the background. Computation, you might be connected to a server up in the sky somewhere. That's all part of the high-level state we want to keep track of.
And to do this, you really want to think like a user. So again, even though you know how your application is structured, when you're thinking about state restoration, approach it as if, hey, I'm using this. What am I doing? As an example, what's going on here? So if a user was in this application, they'd think, "Well, I've got a song queued up. It's paused, looking at the album cover.
And I've got the advanced controls so I can scrub." So if we were going to save state, what would we want to focus on here? Well, not every little bit and piece on the screen, not all the controls, but essentially what I just said. We'd remember what song it was, that it was paused, maybe a Boolean to keep track of the fact that we've got that scrubber showing. Not much else. How about here? may notice I switched albums. Same thing, but we're looking at the back of the album cover. Not much difference, so there's not going to be a lot more state we have to save to get here.
Finally, how about this? When you're listening to a song, there's different ways you could have gotten to it. You could have gone to an artist's song list. You could have used a playlist, as I did here. We want to remember that as well. So it's important to remember what you're doing and also remember how you got there.
So you might think, okay, sounds like a nice feature. It's pretty cool. I'll put it on my list. And I've got a few other things to do. I'll get to it. Well, we think this is a really critical feature, something that's really important to do as soon as you can. So why? Applications are for users, and whenever you can better that experience, make things a little smoother for them, you get a lot of bang back for that buck.
So how does this play to do that? Sometimes when you put an application in the background, you have this tension. You're kind of wondering, is it going to come back where I was? If you're doing something fairly complicated, if you've navigated deeply into the app, the phone rings, you might feel like I don't really want to answer it right now. You know, you have this mistrust.
So if we can bring the app back to where it was, make the user feel more comfortable, it's like the app is working for the user instead of the other way around. And it's the same thing if you need to update an application. There might be a great new feature you want, but you're in the middle of engaging in something and you don't want to take the time to update, because now the app's going to start back at the beginning.
Now this may seem like a small thing. I save a few seconds of time when I go back into the app. But it actually really plays big psychologically. People flip in and out of apps all day long, back and forth. And we don't always remember exactly what we were doing in an application.
You might also be leaving the house or in a hurry, and you want to run into an app quickly, just get something done. And if you go back in and the application is right at the beginning, it's jarring. So we want to provide a more seamless experience. And all of that adds up to a more pleasant experience using the app.
Again, we want to make it seem like the application never really quit. So it's kind of magical. It's just always there doing what the user wants it to do. If you have a number of apps in the store that have similar feature sets, you know that the one that feels nicer to use is going to be the one people are going to generally prefer. So this helps to make things more competitive.
So how do we help you to do this? iOS does a lot of the process flow. We coordinate saving your state. We coordinate restoring it. We handle a lot of the scaffolding, things that are tricky to get right. We also have some default behaviors. Chris mentioned this if you were at the talk before. We'll remember the navigation stack, selected tabs, scroll position. I'll go over many of those as we go through the talk. And this is really convenient, saves you a lot of time and effort as well.
We made it very easy to adopt. You can opt in quickly. You can do pieces of your application. You don't have to do the whole thing at once. So we really want to make it easy for people to be able to start using this. If you have more than one application, by using this, you get a consistent approach to saving and restoring state. That's good for your users. It's also good for you. You don't have to keep reinventing the wheel. And if more developers adopt this, users will come to expect a specific, consistent approach, and they'll get it when you have this.
This is important. We've implemented some APIs, mostly methods that you'll write, and it kind of focuses your attention on the things you need to save, what you need to understand and do, and we take care of a whole bunch of other stuff so you don't have to do all this needless coding just to get the scaffolding set up.
So what do you have to do? I talked about the semantic state. That's the first thing. Understand your app. Know thyself. You probably already do, so look at that. We've already got one knocked off. When you identify what state you want to save, you write some methods to save and restore it. So this seems pretty straightforward, probably about what you'd expect, right? How do you save state? You save it. How do you restore it? You restore it.
Here's the tricky part, though. Sometimes when an application starts up and you want to get back to a state, the user might have pushed some navigation on the stack. They might have presented some controllers. And we'll need to recreate those. And we make it easy to do that. And you'll see how we can just focus you on the right task to make sure we come back.
I just want to caution you, when you're saving and restoring state, don't conflate the model or the view with your state. You've already got something that manages your model data, a database, maybe you're document-based, or there's a server with information. At best, you'd be redundant. At worst, it would be inconsistent. So you really want to focus on restoring what the app is doing and not use this as a way to save model data.
Same with views. You've already got something that manages all your views. You've got code that you've written. You should be able to reuse all of that. If you're using storyboards or nibs, it's all going to be very natural. Okay. So I'm going to wander over here, put on my demo hat, and I got something quick to show you. Okay. So you can see here we have an iPhone. Hopefully you're all familiar with the WWDC app. I love it.
Use it all the time. So this is a fairly-- you know, similar structure to many apps that you'll see. We've got a tab bar controller at the top level, and we've got various things that are pretty useful. So right here, the first tab is the schedule, and it's got a navigation controller. We've got a table view controller in here, and I'll scroll around a little bit.
You know, maybe I'm curious about what's happening a little bit later in the day. So I'm looking at Wednesday here. We've got a favorites tab. Similar. It's got a navigation controller with a table view. I'm gonna just scroll down to the bottom because I'm excited to see what's coming up at the end of the week.
If you're lost, we've got some maps. This is pretty basic stuff, and this is going to be common in a lot of apps. So let's say, you know, I'm just kind of using this in the same way I normally would. I went to look at a certain time of day.
I'm sort of checking out my favorites. I want to see what's up on the third floor. Maybe there's some new news. Okay. So now I'm going to put the app in the background. Either I decide to go do something else, maybe the phone rings, whatever. Okay, so I'm going to do something I don't recommend you normally do. I'm just doing it for personal use.
I'm going to kill it outright. Okay, so that app's not running. So if we restarted this application on iOS 5, it would just go right back to the beginning. And that would be both kind of jarring and it would interrupt your flow. Now, I just did this quick little demo, so you saw what I did. You probably already remember. But imagine four hours later you come back.
What session was I looking at? Was I reading this news item? What was I doing? And maybe in your app, you've written some code to handle this on iOS 5. or prior and could be pretty complicated to get it all right. So let's see what happens here. I'm going to start it back up.
Let's see where it goes. Alright, look at that. So we're back on the same tab we had before. Still looking at news. So the app is kind of working for me. It's helping me to remember what I was doing. So if we go back here, still on the third floor. If I look at my favorites, still scrolled to the bottom there, schedules at 10.15. So it's really like the application had never quit. So let me just recap real quick what was important there.
So this is key. We brought the user right back to where they were. So it feels kind of magical. You've probably noticed if you do things right, people don't always give you any credit. You do something wrong, you hear about it. So here, we want to do something right, even if we don't get, you know, a big deal from the users, this is really going to be nice. It's going to give people a better feeling. This is also important.
So even though a lot of this wasn't visible, we remembered the state of the app, even things that weren't being shown right away. Okay, so how much code was it to do that? How much did I have to write? You know, 50 lines, 20 lines? You know, well, sometimes there actually is a free lunch, or at least an appetizer. So in this case, that was zero. Zero lines of code.
So how did we do that? That's how we rope you in. Okay. So what do you have to do? I lied a little bit, 'cause I like the word "zero." There were two lines of code. Remember I mentioned you opt in, so one line, "Yes, I would like to save state." We'll see where that goes later.
One line, "Yes, I would like to restore it." That's pretty easy. You just write a method. But how do we get all the rest back? We added restoration identifiers to everything we cared about. That includes the views we wanted to have restored and the view controllers. And then we did the rest.
So let me talk about restoration identifiers for a minute. It's just a property, both on view and view controller. You can set it whenever you want in your code. But you can also set it up in Interface Builder, which is really convenient when you're constructing your nibs or storyboards. That's a good place sometimes to think about, "Where do I want to name this? Where do I want to go? And what do I want to save? If something doesn't have a restoration identifier, we don't touch it.
So this is how you can kind of opt in a piece at a time. So you start giving things restoration identifiers, they start to play. If things don't have restoration identifiers, they're out. And we also use this internally to be able to map and find objects. You can actually refer to other objects in your state restoration archive and we'll wire them back up correctly.
So here we are in Interface Builder. I actually liked Chris's picture better if you saw the last talk. But anyway, I've got a view controller over here. And on the right side in the properties, here's my restoration ID. Now, if you're using a storyboard, you might have already set a storyboard ID for a view controller. We do that so that you can instantiate the view controller from code right out of the storyboard or you can wire things up, have segues.
And I would recommend if you're using a storyboard ID, set the restoration ID to the same thing. So you see the convenient little checkbox right there. It will fill it in for you. Otherwise, the left hand thinks it's called one thing. The right hand thinks it's called something else. Now, we still let you set it differently if you want to. There might be a good reason. You know, your data model and the way that your view controllers are constructed may require that.
But as a general rule, I'd start by using the same ID. So let's look at the example app that we had. How did we wire that up? Well, we had a root tab bar and I gave it the restoration identifier root tab bar. I actually like to use the word root at the top level.
It makes debugging a little bit easier when you see that and pin things off of that. We had some children. I didn't show them all just so the picture wouldn't be too cluttered. But you can see I have a navigation for schedule. I have one for favorites. I've got my map.
And those first two actually were navigation controllers with a child. So I gave them names. And here's our views. So I cared about restoring the schedule, the favorites, the tab bar, the navigation stack, and I also cared about these three views. The map scroller got us to the right map automatically. That was actually just a scrolling view we snapped to a quantum. And then the schedule table and the favorites table remembered where they were scrolled to for us. So we got a lot of stuff for free there.
Now when we track the restoration identifier, we actually track a scoped path. You notice that we have a hierarchy of view controllers, so we want to keep track of these things using the same path. Here we're starting off with the tab bar. Here's our navigation. You notice up at the top it's like a file path. It's just a simple scoped path. So we'll go down to the schedule, finally down to his view. So you can see we built up a path. The reason we do that is so you can use the same restoration identifier in multiple places.
As long as they're in different places, you can keep the same ID. All right, so here's an example. We've got a list of contacts. So what would be a good restoration identifier for that? I don't know. Contact list. We pick a contact. We pull it out of a storyboard.
And let's say that we set the restoration identifier in the storyboard. So now I've got contact details. Should I call it? How about contact details? I'm going to call it contact details. All right, so we've pulled this out. And you'll notice right here we've got some linked contacts so we can get to another contact from here. And when I select one of those, I'm just going to go pull one of these out of the storyboard again. And what's it going to have for a restoration identifier? Same thing. Is that okay? Well, it turns out it's fine.
As long as you're not in the same part of the hierarchy, you can use the same ID. And really for view controllers, the same part of the hierarchy really just doesn't matter. They're at the root level. So if your application delegate sometimes swaps out an entire tree of view controllers and puts in another one, don't give the roots the same name.
For views, you can't give any two views in the same view controller, the same restoration identifier. But across view controllers, that's fine. So that means you don't have to contrive these big long-winded weird names to try to identify the path to get to them. Okay. Let's go back to the same example we have before, see a couple more things.
Okay, so here we are on the Favorites. Let's say that we want to go and look at one of these in particular. So I like asynchronous design patterns. That's a totally cool one. So here I am. I just pushed something on my nav stack in that Favorites tab. And I want to make sure I know where it is. There's the pin. It's in Pacific Heights.
Cool. And now I'm gonna go back and-- I actually want to read the description a little bit, prepare myself before I go. But before I do that, I want to look at a few other things. And you know how it is. You jump around in an app sometimes and you leave little breadcrumbs to go back to. So what I'm gonna do here, I'm gonna change the day to Monday because I remember there was some interesting stuff on Monday.
But wait a minute. There was more than that. Where did it go? Well, I turned off labs in this filter. So I'm gonna turn them back on. So let me just dismiss that. So I had a PresentedController that set up some semantic state for me. So let's say that I pull that up and I want to hone in on some stuff. Maybe I scroll to the bottom. to see if there's anything important. And, oh, the phone rings.
Well, you can see where this is going. Let's say the phone rings, somebody wants to go out and get coffee, we want to talk about one of the sessions, we're having fun, we're writing code, one thing leads to another, this poor application gets killed. Now that app's all gone. All right.
Now, six hours later, I come back to it, and I'm like, "All right, let me prepare for what I want to do tomorrow." I start the app back up. What's it going to do? Look at that. Got our presented controller back. It's even scrolled to the right place. All right. So we'll get rid of him.
It still remembers that it was Monday. You can see up there. And it has the correct sessions loaded for Monday, which is important because when you change the day, you've got to reload that table view. So we want to have the right info there. How about favorites? What was I doing on favorites? I knew I was doing something. There we go.
I wanted to read that description. So here I am. I'm scrolled to the right place. And I've still got the correct favorite that was selected. And when I come back off, you know, it's even going to sort of nicely transition that cell that shows me what I had selected. Okay. So let's see what we had to do to get that.
So now you do get to write a little bit of code. What exactly did you do there? Okay, so we had the day. We remembered that. We remembered what was in the table view for the current day. We introduced something new. We created a new view controller for that session. We put that on our nav stack. We also presented a controller. And what's interesting about that, and this is probably a common thing that people do in apps, we had some shared state. So a lot of these objects don't exist in isolation.
When you create a view controller to present it, you'll often hand it some other piece of context, another object. In this case, it had a reference to one of the other view controllers. So it could query it for the data store and be wired into the application and not just out there in the fringe.
We had to create both the session details that we were looking at and the presented filter. That's because when the app starts up, it's not going to create them by default. Your application is not going to start up and create every possible view controller just on the off chance you might want to restore it. So we'll do it in a targeted fashion, and then we'll restore the state. So let's look at all that.
Before I talk about actually how the code works, though, I want to just show you quickly the structure of the restoration archive. These are all keyed archivers, which is great because keyed archivers are really convenient. You know, you can give things semantic key names that represent what you're saving. You get one for the top level application delegate.
There's a few methods we call on that. They'll all get the same coder. Every one of your view controllers gets its own keyed archiver. So we've got one at the root level with the tab bar, and then all the other view controllers get their own. Finally, any views that save state, they get their own coder.
So this is nice. Again, you don't have to contrive weird names to try to disambiguate things. You could just call a comment a comment, whatever you want, you know, something in the table, you can give it a rational name that makes sense. It's going to be in your own little cubby hole for that object. Okay, let's look at saving code.
So remember I changed the date of Monday over here. So what did I have to do? We have a new method that you implement on ViewController, EncodeRestorableStateWithCoder. It gets passed in an NSCoder. It's actually that keyed archiver I told you about. So you'll implement this. So what do you have to save here? We wanted to remember what day it was, so it's pretty simple. We just ask the data source, "What day did I tell you it was?" We save that. Hey, we're done. That was it. That's all you had to do to get that back.
Don't forget to call super. We do have some default behaviors that you'll get when you do that. Also, it's tempting when you're writing this code to just type in the name of the string. I probably don't have to tell you this, but if you use a define or a constant string, it's going to be a lot nicer because you're going to use that same key when you decode. It avoids errors.
Okay. How about the session details? This one's interesting. So we actually created the session view controller. We want to come back and restore that. So what do you think we need to save? Well, we had a description for that session. We had a title, what time it was. We had a map that showed us how to get there. Do we need to save that? Actually not.
Let's just grab the session ID out of our data model. So in our application, every session has a unique ID. So all I have to do is save that and I'm done. Now, one other thing that we keep track of in our application is where we showed that session object. And this is something you might do in your code as well.
You'll have a view controller that you pull out of a nib or a storyboard, and you'll place it in various different parts of your app. So this might go in the schedule view, that first tab, or it might go in the favorites view. And in fact, that's exactly how our app works. We want to save a Boolean so that when we come back, we remember where we're supposed to be. Pretty easy.
Super and some defaults and you're done. How about the filter? What do we have to save for that? I mentioned that we were sharing a view controller. We had some shared context so that this guy wasn't a lone wolf, but he was actually wired into the app. So we're going to go and we're going to encode one of the other view controllers, just an instance variable that I set up when I created it. We don't really save the whole view controller.
We just save a reference. That's because we don't want to restore another view controller object that's not the one you expect. So we'll save a reference to it, wire you back up later, and we'll see that happen. Call super, we have our define, we're cool. Okay, how about all the views? There was a whole bunch of views in there. There were table view cells, we had the title, we had a whole bunch of different stuff. You don't have to save any of those.
Well, we have some default behaviors. We remember the scrolling position for views, so that's why you didn't have to take care of that. But also, in general, your views just kind of come back naturally. We're trying to devise a way for you to leverage all the code you've already written that brings your app to whatever state it's in so that when you come back in state restoration, you don't have to go and reinvent the wheel and duplicate stuff. Okay.
So let's look at the control flow for doing that. Again, a little diagram here, pretty simple. Okay. So I mentioned this is opt-in. So this is the first thing we do is we ask the app delegate if we should even save state. Might say no. If it does, we're done.
We're sad, but we're done. If it says yes, happy face. Then we will tell the delegate that we're going to encode its state. So we call a method application, will encode RestorableStateWithCoder. Now, both of these methods actually get passed to the same coder. It's that top-level keyed archiver for the app delegate. So it can track whatever it wants, things that might be relevant to the app. So we're going to call it, and we're going to call it the app. So we're going to call it the app.
So we're going to call it the app. So we're going to call it the app. So we're going to call it the app. And then we're going to call it the app. And then we're going to call it the app. So we're going to call it the app.
And then we're going to call it the app. So we're going to call it the app. And then we're going to call it the app. And then we're going to call it the app. And then we're going to call it the app. And then we're going to call it the app. And then we're going to call it the app. And then we're going to call it the app.
We look at the windows on the main screen, main screen only. We look for the root view controllers for those windows that have a restoration identifier. If we find any, we put them in a list, we save their state. We tell them to save state and you saw those methods and code Restorable State with Coder. We save state in view controllers and views. You might actually refer to other view controllers. We saw that with the filter, that presented controller. It referred to another view controller.
Navigation controller refers to its children. Tab bar controller refers to its children. So we find that there's more view controllers we need to take into account. We tack them on the end of a list. We loop back, take care of those. Maybe we get a few more. Eventually, hopefully, it finishes and we're all done.
Okay, I just want to talk for a moment about how to manage view controllers in a way that works well with state restoration. So there's two major things to keep track of with this. One is, how do we find view controllers when we're restoring state? We saw a few examples here. How did we find all the view controllers? So let's go into that.
In the first demo, you didn't have to write any code to return view controllers. We were able to find them implicitly. We did that because they were created before we needed them. So if your application starts up, the main storyboard of the main nib creates a set of view controllers, or you do it in code before state restoration starts, we can find them.
Now, if you have view controllers that aren't normally part of your application when it starts up, we're going to have to ask for them. And the best way for your application to do this is to specify a restoration class. It's actually a factory. There will be a class method we'll consult. We'll ask for a view controller. We'll give it the whole restoration path so you know which one we're asking for. We'll even give you the coder so you can look in there if you need any context.
So that's nice because it encapsulates the code that finds things along with the code that creates them. So whatever's managing view controllers and creating them in the first place is going to be what's used to restore them, to find it when we're doing restoration. There's also kind of a catch-all on the app delegate. If we can't find it implicitly and if you don't have a restoration class, then we'll call a method on the application delegate and ask for the view controller.
And that's convenient for things like root level view controllers. If you're changing your view controller hierarchy at the top, if there's any code that your application delegate uses to create view controllers, you probably want to keep the code to find them in the same place. So we can do that.
And it's always fine to return nil. If we're asking for a presented controller, we get back nil, we just won't present it. If we're asking for something on a nav stack and we get back nil, we'll ignore it. We just won't go down that far in the navigation stack. And it turns out that because your application might be restarting after the data's changed drastically, something that the user was looking at may no longer be relevant, may not exist.
Or you might have had a presented controller that said, "You need to set up an account." They go set that account up on another device and your app starts back up. You don't want to show them something that's useless at that point. So you can just return nil. We'll be fine.
Now, you've already created view controllers in your application. Otherwise, it really wouldn't do very much. But there's some things that you can do that sort of make state restoration a little bit easier. So it's tempting sometimes when you create a view controller in response to some user action to create it, configure it for the specific context it's in, and then present it right then and there. So you have, like, one nice method. I need to create a person, go and make it. Here's some initialization info. Here's the context it's in. Present it for me.
But if you do that, you can't really reuse that code when we ask for the view controller during state restoration, 'cause we just want to get it. We're gonna present it or push it on the nav stack. And you're also gonna decode its state from a coder, so you don't want to jump the gun and set it up in a specific way.
So you can write a shared initialization routine, and you use it during your normal operation, just as you would now. And you'll also consult that when we ask for the view controller. And it turns out if you structure your code this way, it becomes much more natural to do this, and it doesn't cost you anything more to do it like that. It's also a good place to set the restoration class. So if you're using a restoration class to encapsulate the creation, you don't really want to have multiple sites that create view controllers and forget to set the class in one of them.
So let's look at the session details and see how we did it there. So I got some code up here. Okay. So here's a shared initialization method. I'm setting it up. Now, the view controller will need some things to come into existence fully formed so that it can operate and function. So we're telling it what session it represents.
And this is also a nice place, as I said, to set the restoration class. Now, why am I setting the restoration identifier here? I could set it in a nib. Well, in this case, I've got an init method. I'm not actually using a nib. I'm actually creating this just in code.
And that's a common thing to do. So in that case, don't forget to set your restoration identifier in the code. Why am I using response to selector? Why am I bothering to do that? Well, when you implement this for iOS 6, you get a great new feature, but you don't want to lock out users on previous versions that haven't been able to upgrade yet.
So by doing a response to selector check, you won't erroneously try to set a property that doesn't exist on iOS 5.1 or older versions, and your code will continue to work there. You just won't get the feature, which you don't lose. Now, let's say you did use a nib or a storyboard. We still need to set that restoration class.
How do we do it when we're pulling an object out of a nib? I recommend putting it in Awake from Nib. Just set the restoration class right there. Easy peasy. Okay. Now, in the previous slide, I showed an init with session. If you pull things out of a nib, you're going to want some shared initialization that you can call just to make this object functional. So here we have it right here, just a simple method. So that replaces creating it in code entirely.
So with that in mind, let's look at what we did to restore everything. Now I mentioned you can phase your development in. So we're going to just take off where we left off from the first demo where I had just simply set some restoration identifiers, opted in the application delegate. We'll add some more code to that. We don't have to really do anything different. We just do more. So first I'm going to have to find those additional view controllers, the session details, and that filter. And then we'll just restore their state.
So let's look at it. Here's our session details. Remember that guy? I was going to go back and read the description. So how do I create it? We have a new protocol, UIViewControllerRestoration. It has one method, ViewControllerWithRestorationIdentifierP ath. This is implemented on the restoration class that I described before.
So here's my view controller. He's set up as the restoration class. He implements the protocol and here's his method body. So we've got to create that session. So how do we do I'm going to set up a view controller. I'm going to initialize it to nil in case I can't create it. It's always kind of good practice to initialize this to nil. That way you don't return something that didn't get set. Okay. Let's look in the coder for the session ID. So you remember when we saved our state for the session details, we just saved the session ID.
I'll take that and I'll check to see if that session still exists. And this piece of code is particular to our application. We're using core data. So I'm looking in the database to see do we still have a session. If we do, there's that shared method that we just looked at before. So I've got this nice shared init method that I was able to use when my app was creating it in the first place. I'm just leveraging it again here.
Finally, we return the view controller. Note that we might have returned to nil. So if this session no longer existed, if it was canceled, and you went back to the application, we wouldn't bother showing you something that was irrelevant. How about the filter? The filter's a little easier because you don't have to look up any specific information for it, although we did wire it back to another view controller. And I'm going to pull that out now so that I can create this thing fully functional. But it's just two lines of code. So these methods are typically very easy, leveraging your application structure as it already exists.
So let's restore all the state, and we're done. Remember, it was Monday. I changed the day, loaded some different sessions in the table view. What do we have to do there? Well, we saved the day, literally. Okay, so we pull that back. We just get the day out of the coder.
Now, when we set the day as an instance variable, that's great, but we have to make sure that we remember to reload the table view with the correct sessions for that day and update any UI. I already had a method that did that in our application. When the user went and tapped on it to change the day, we'd call this method, and we'd go, "Okay, great. Let me reload my table view with sessions for this day, and let me go and update everything correctly." You know, the title.
So we're just gonna use it here. So we don't really have to write a lot more code. And we call super. So that's not really very much. So that was pretty easy. We found it in a couple lines of code, restored state in pretty much one line. We're done. Okay.
So now let's look at restoring session state. Remember that when I created it, the views hadn't been created yet. So when this method is called, we've now got our views, and I'm gonna go and I'm gonna keep track of the fact that I was launched from inside of the schedule view or the favorites view. So I'm just restoring only what's necessary when I create the view controller.
In the previous slide, when I went and found it, I just want to create what you need to get a functioning object and then defer restoring the rest of the state until I actually need it here. Call super, and we're done. Okay, how about the filter? We don't actually have to do anything for that guy.
That's because it was just created fully functional. There was nothing else that we really saved. So we're good to go. So not a lot of code there. Let's look at the control flow for this. Okay. So we've introduced a new method when we start your app up. Application will finish launching with options. This method is called before we do state restoration. We then ask if we should restore state. Again, because this is all opt-in, we're going to ask your delegate if we should even do it.
If you say no, we skip right ahead and we call did finish launching. If you say yes, we'll go through state restoration. So let me just talk about "We'll finish launching." The idea here is you'll want to set up state before we go and do any state restoration. You make your main windows visible, you've got your main nib or storyboard loaded and you might want to go and tweak that or create some view controllers. Whatever your application does when it normally starts up, your default state gets created here.
You can pretty much hoist all your initialization code from did finish launching into will finish launching. Just do it right up front, then we'll go ahead and do state restoration on top of that. But you might still want to do a few things in did finish launching. There's a couple reasons. One of them is you might want a login or password.
And if that's the case, if you're going to force users to enter a password to unlock some critical data, or if you want them to log into a server, you want to make sure you do that last. You don't want to restore that panel and then have us restore something else on top of it. You want to wait until everything is set up, make your decision, and do that in did finish launching.
Also, you might want to deploy on earlier versions in iOS 6. And I showed you about using response to selector for the property, but how do you get this method to be called correctly if you run on iOS 5, which doesn't even have it? Grant Central Dispatch has a really cool feature: Dispatch Once. You can give it a block of code. It will only execute it once. You can call it repeatedly, but it'll be a no-op every subsequent call.
And we're going to leverage that. So I just used a comment, whatever comment's set up. That could be quite a lot of stuff you're doing. I'm making my window visible, doing whatever it is that I would normally have done and did finish launching. It's in this block, inside of a Dispatch Once.
So here I am in my new method, "WillFinishLaunching." So I just call the method that does the dispatch once. On iOS 6, this method gets called, so "WillFinishLaunching" will come up before we do state restoration. We'll set up our state. We're fine. On iOS 5, this method won't be called, older versions. Okay. How about "DidFinishLaunching"? On iOS 6, it'll get called, but we've already done our initialization. So when we call this, it'll be a no-op.
So that way, nothing weird happens on iOS 6. We get exactly what we want. And importantly, on previous versions where we didn't have application "WillFinishLaunching," this time it will execute that code. And I put the comment "Other stuff." So if you wanted to show your password or something like that, you'd do it here.
So let's look at the order of restoration now. We have a list of view controllers we want to bring back. We have to look them up. We're either going to find them implicitly, ask the restoration class or the app delegate. If we don't find them, no worries. That's fine. But if we do, we'll load their view. And we'll put them in a list that we're going to consult to then restore their state.
We then go through that list of every found view controller. We tell it to restore its state, and then we take every view with a restoration identifier, and we give it a chance to restore its state. When we're done with all that, we tell the delegate, "We've restored your state." We call Application, Did Restore, Codable State, and give it a chance to pull back anything at the top level for the app that it might have wanted to save.
Okay, I've got one more demo, and this one builds on the other two, as I've been doing. I'll show you one more thing that we do inside of the WWDC app. So here I am on Monday, and let's say that I enjoyed a session from this morning and I want to go back and find it. And it was one of the new sessions. So we've got a search bar here. I'm going to go in there and I'm just going to search for new session.
So you can see here I am typing away, and I get a set of search results. And I really liked what's new in Cocoa Touch. So I'm going to go in there, and I actually want to leave feedback. Because I thought it was a great session, but I do have one comment.
I think you could have used more Cowbell. So I'm doing that. And now the phone rings, right? And you can probably see where this is going. Okay, so the app's in the background. No worries. Interesting phone call. Time passes. Gets killed. Uh-oh. So now a few hours elapse. I come back to the application. I don't even remember what I was doing.
When am I going to get back? Hey, look at that. I've still got my text. It still has my very profound comment. It remembers what I was doing. What happens if I back out of here? Let's say I sent that comment. Still showing me the correct session that I was looking at. Still even has my search context. So the search is active, the keyboard is up, exactly as if I hadn't left the application at all.
So what did we have to do in order to get that? We had additional state that we wanted to save, the feedback, the comment we were leaving, the fact that we were showing that view controller, and of course, all the search context, right? We're going to have to find that feedback controller, similar to what we did with the session we were looking at. and then we'll restore our state for them. So this should start to feel a little familiar. I'm gonna kind of reiterate some of the things we already did just for some new view controllers. So here's our feedback.
When we save the state, you'd think, well, we just have to save that comment. But don't forget, it's for a particular session. So we're going to do the same thing we did with the session. We're going to save the session ID. That way, we remember what we're sending feedback for.
Now, this object also referred to another object, so this is just another example of how you can encode a view controller and will just wire up a reference to it. So we're going to save the view controller that presented it so we can tell it what our feedback is when we're done. We want to make sure we come back wired back together in the application. Okay, finally we get to the comment. So I grab it out of the text field. I'm just coding defensively here to make sure there is a comment. I save it.
We're done. Pretty easy. OK, so not a lot there. How about search state? Okay, to save you from trying to read a whole bunch of code on the screen, I just put comments here, but it's really this simple. We just remembered if the search bar was active. It might not have been. We also remembered if we were editing.
The user might have started scrolling in the search results, and we're no longer editing. The keyboard goes away, so we want to come back correctly. They typed in some text. We'd save the text. Now, sometimes with search, you'll notice this in Mail. You have something called the scope bar, which allows you to specify what you're searching. In Mail, it's from, to, subject, all.
You could just grab what scope setting that the user had selected, save that as well. We don't have it in this example. Remember before we kept track of what day it was. This is our schedule view. We called super, so we're out. So not a whole lot you have to do to get back all that search state. And let's look at the flip side with restoration.
Well, we have to find the feedback view controller. We're going to ask for it. This is very similar to what we did when we looked for a specific session. We start off with the view controller, predict failure, even though we're going to succeed. Session, we look it up, check to see if we have a session, and then we create it with our shared initialization. Don't forget I had a delegate here. I pull it out of the coder and I wire it right back up here. So now I've got a fully functioning object, or nil, but hopefully we've found the object.
Okay. Then we're going to decode its state. So why am I decoding the comment here? Why did I set everything else up in the method that found the view controller? I try to defer as much as I can until the decode method. When I showed you the control flow, the view isn't loaded when we ask for the view controller. That's because the view controller hasn't even been created yet.
But after we get back the view controller, we do load its view, and it's guaranteed to be loaded when we call this method. So here with the comment text, we're going to go and set it in the text view. We couldn't have done that until the view had been loaded and it existed. It also sort of separates your concerns. In the other method, we need to create something that's functional, but it's not fully initialized yet in terms of the context it's running in. Here we set it up exactly as it was when it left.
All right. How about the search? In the grand old tradition I started two minutes ago, I'll just show you the comments, was to search Was the search bar active? Were we editing? Get the text back. Don't forget, we want to remember what day it was, just like we did in the earlier example, and we're done. So all that search stuff, really easy. One thing, though.
When we were looking at the search, I actually had gone and selected something, and then on top of that, I selected feedback. So I didn't really have the search results up at the top. It might not be a good idea to be messing around with the view hierarchy, setting up the search table when I'm not topmost. So I'm going to defer that.
The way I do it when I decode my restorable state, I'm just going to slap that into instance variables. So I'm going to keep track of whether or not search was active, whether the keyboard was up and what the text was, all in some instance variables. I won't directly apply it yet.
And then when the view is going to appear, Don't forget to call super. I'll check, did I restore some search state? I might not have. The user might not have been searching, but if I did, I'll apply it right there. So I'll set the bar active. I'll figure out if I was editing. And then I'm going to clear that. Don't forget to clear any state that you pull into instance variables. You may then go from this view controller to another one and back. Every time it appears, you don't want to redundantly execute the same code.
So there's one little tiny twist to this, though. What if the user saves their state, they're searching, they start the app back up, we restore it into instance variables so we can apply it when the view will appear, but the view never appears? The user doesn't back up to it. And then they save their state again.
So we just have to make sure we get the state from the right place. So the encode routine, tiny bit more complex. We check to see if we had restored some state but had not applied it yet. And this might be a common pattern that you'll use if you have anything with state that's, you know, sufficiently complex that you do need to wait until the view will appear before you apply it.
If you do something like that, make sure you grab the right state. Otherwise, if the user comes in and out of the app a few times, you'll lose the state. Now, if we didn't have any state saved, we'll just use whatever's current in there and we're good to go.
So I have one more thing to talk about. And this is kind of neat. This is important. So here we are in the schedule view. We've got a bunch of table view cells. We've got index paths to get to them. So I'm scrolled to the keynote. Notice I didn't say I'm scrolled to index path 00 or index path 04, wherever that is. I said I'm scrolled to the keynote, because that's the semantic state. That's what the user thinks they're looking at. They don't know anything about index paths.
And if I select a cell, let's say I tap that, and I start showing the details for it, I want to remember what cell was selected. On the iPhone, when you back off from something in the navigation controller, it will sort of fade the cell out, which is a subtle but nice visual cue to the user about what they were looking at.
More importantly, on an iPad, you usually have a split view. So you have your table view on the left, this, and on the right you have the details, so you're still showing that table view. So you want to see what's selected. And when you restore your state, you want to restore what was selected.
Now keep in mind, these might move around. So when the application starts back up, even though you had the second cell selected, that cell may have moved. We may have inserted some more labs in the front. We might have moved the time of the session. So in that case, we'll consult our data source and we'll keep track of what we really care about, the semantic identity of that. So we have a new protocol, UIDatasourceModelAssociation. You implement it on the data source for the table view. It has two methods. The first creates an identifier that has meaning to your application from the existing index path.
On the back end, when we restore a state, we'll call the opposite method. And we'll say, okay, given this identifier, where's the index path now? Now, hopefully, in your application, or I'm sorry, hopefully in this application, we're not going to change the sessions on you, so we're not going to ruin your week or screw up your Friday. But imagine that this was a bunch of events over the course of a year.
And we canceled the picnic because of rain, and we moved it out to next month, we changed the date of a concert, we inserted a couple different dinner parties, things like that. So things can move around. If this also managed some media or a list of items that it fetched off the internet, things could change from day to day. When you load the app back up, you kind of want to go back to where you were.
So let's look at how it's used. Here's my data source for the table view. I implement the protocol. Here's the method. So we're going to automatically call this when we're saving the table view's state. It's called actually out of the table view object. Now, table view subclasses scroll view. So if you don't implement this by default, we'll just scroll by points back to where we were. So better than nothing. But if you do implement this, you have a better shot at getting back exactly where you were. Pretty simple. Remember, I had a session object.
I find the one at that index path. I'm just going to use its session ID. That's unique. So I'm good to go. If I return nil, there's no worries. Maybe I couldn't find the session, some odd thing happened, or maybe I don't even implement this method. Again, we used a scroll view. Here's the reverse, okay? I'm handed that identifier.
I'm going to look up a session for it. Now, again, remember, the session might be gone. So if it's gone, I won't be able to get anything back. But if it still exists, then I'll try to find the index path for it, and I'll return that. Otherwise, I'll return nil.
So if the session moves, we go back to the right place. If you scroll to a certain point, we come back to a meaningful point. Now, something interesting about this app, I'm coming back to the session you were scrolled to. I certainly want to come back to the right session if it was selected. It would be weird if I didn't come back to what you had selected.
However, for scrolling, your methods here, you might make the change. You might make the change to the app that you're using. You might make the change to the app that you're using. You might make the change to the app that you're using. You might make the change to the app that you're using. You might make the change to the app that you're using.
You might make the change to the app that you're using. You might make the change to the app that you're using. I'm going to keep track of what time it is. So if I was at 10:30 or 10:15, maybe that's what you remember, and not the session for the scroll position. So when the user comes back into the app, if session's moved around, you come back to the right time, which is what they would expect. This is all up to you, depending on the semantic meaning for your app.
Okay. I mentioned that we have some default behaviors on view controllers. We remember the selected tab. We bring back the navigation stack. If you presented a controller, we automatically bring it back. Also, split view controller will make sure that its children get added to the list of objects to be encoded.
I talked about a couple of the views already. We bring back your scroll position automatically. Remember the selected table view cell? We just talked about that. Couple more. If you have an image view and the user's been pinching, rotating, and zooming, we'll bring all that back. So we'll restore the image transform.
And for web views, we'll keep track of everything that had been happening inside of that web view. Users can load multiple URLs. They can select URLs and load another page. And they can go backwards. And they have a stack of history both backwards and forwards. We also remember the scroll position.
And if you had pinched and zoomed the web page to make it easier to read, all that will come back. So it's seamless as if the app had been running the whole time. Okay. You might decide you don't want default behaviors, though. So we mentioned it's important to call super. And I think in most cases, you always want to get the default behaviors.
But if you do want to override, you have a couple things you can do. First thing you can do is just don't give the object a restoration identifier. If you don't want to present a controller to come back automatically, don't give it a restoration identifier. We won't save it.
If you don't want a view to remember its image transform, let's say you have an image and it literally changes every time the user goes into the app. It's like the surprise, you know, I'm feeling lucky image of the day. So the app comes up. You show a different image. It would be kind of weird if you remembered the image transform of the last one. So you don't give it a restoration identifier.
No problem. The other thing you can do if you have a subclass of one of our classes, just don't call super to override it. If you do that, make sure you do it consistently. If you don't call it on encode, don't call it on decode. And make sure you have a good reason, because in general, you're going to want to get the default behaviors.
We just want to make sure that we don't trap you and force you to. A couple more things. You might want to know what version of the application saved state. And you might want to know this way, way down decoding a view controller somewhere. You don't want to have to go fiddle around with awful globals.
In all the coders, we're going to keep track of some information to make it convenient for you so you don't have to have a global that you consult. So we'll remember what version of the app saved state. Let's say that a user is running version one of your app or version two.
And three years goes by, they've hardly ever used the app for some reason. You come out with version five. Version five totally rocks. They've got to have it. They update to it. But version five is so different than version two, they're almost not even the same. They're the same app.
So when we come back in and we call your state restoration code and ask if you want to restore state, you can look at this key and say, "Forget it. I don't even want to do it. It's such an edge case. It's not worth doing." Or maybe you do want to do it, but certain view controllers need to take into account, "Wow, my state was saved to the really old version." So they look at this key. Another one that we keep track of is the idiom.
[Transcript missing]
Okay, so in summary, I think this is a great feature. I'm kind of running out of time here, so sorry I'm rushing a little bit at the end. But something that we really want to see everybody implement. We've tried to make it easy to adopt, made it so that you could phase it in and do it a piece at a time. By structuring your code with this in mind, we think you can get a lot of leverage out of it, and we think that users are going to really love it.
We've got a lot of documentation. Please consult that. We're going to have some really nice stuff that tells you about all the ins and outs of this. Jake Behrens would love to hear from you. If you're thinking of doing this or you have questions, please contact us. And that's it. Thank you for coming.