Video hosted by Apple at devstreaming-cdn.apple.com

Configure player

Close

WWDC Index does not host video files

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

URL pattern

preview

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

$id
ID of session: wwdc2012-223
$eventId
ID of event: wwdc2012
$eventContentId
ID of session without event part: 223
$eventShortId
Shortened ID of event: wwdc12
$year
Year of session: 2012
$extension
Extension of original filename: mov
$filenameAlmostEvery
Filename from "(Almost) Every..." gist: [2012] [Session 223] Enhancing U...

WWDC12 • Session 223

Enhancing User Experience with Scroll Views

Essentials • iOS • 53:50

Scroll views can be used in many different ways to create familiar and immersive user experiences. Come see what's new with scroll views, learn how to present scrolling content in a page view controller, and even how to enhance your OpenGL games with scroll views.

Speakers: Eliza Block, Josh Shaffer

Unlisted on Apple Developer site

Downloads from Apple

HD Video (503.7 MB)

Transcript

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

Good afternoon, everybody. And-- Thanks for coming back to another session about UI Scroll View. For those of you that have come in previous years, welcome back. And for those of you that are new this year, thanks for coming. We've got some very cool things to talk about today again, I hope.

And if you've seen the previous sessions, either in 2009, 2010, 2011, you're probably familiar with how we're going to begin the presentation today. But that's just going to be a couple minutes, and then we'll get into a lot of more interesting things. So first off, we're going to start about just a little bit of review about how to configure your UI Scroll View and how UI Scroll Views work, just the basics. So we're all on the same page.

Once we finish that, we're actually going to look back at something that we talked about at WWDC 2010, which is building a great photo browser that feels just like the photo browser that you find in the Photos app on iOS. And the reason we want to review this a little bit more today is because with the introduction of a scrolling mode on UI Page View Controller in iOS 6, building this photo browser has become significantly easier than it was when we last talked about it.

After we get done with that little bit of review, then we're going to talk about something brand new that we've really never even talked about at all before at WWDC. And that's integrating UI Scroll View into your OpenGL games and applications. So if you're sitting there thinking, well, you know, I don't have an OpenGL game or an app, so maybe I'm just going to get out when that comes up.

Don't worry, there's actually a lot of really important things that we're going to discuss in the context of the OpenGL that are actually broadly applicable. So if you use UI Scroll View, UI Table View, UI Text View, UI Web View, or even the new UI Collection View in iOS 6, the kinds of things we're going to talk about with run loop modes and touch delivery and responder chain, they are true for all of those different kinds of UI Scroll Views with or without OpenGL. So stick around. And then at the end, actually, we're going to get into something that was new in iOS 5 last year, but that we didn't have time to talk about.

And that's controlling the stop offset of your Scroll View after the user scrolls and it comes to decelerate. You know, when your user scrolls a little bit and lifts their finger while it's still moving, the Scroll View comes to rest. The Scroll View has decided where it will land.

And with a new delegate method introduced in iOS 5, it actually tells you where that will be before the deceleration starts. But even better is you can adjust that offset, which lets you do some interesting things. So let's get started with the quick review of UI Scroll View.

So let's talk about the UI Scroll View configuration. So the basics of UI Scroll View, obviously, are that you have more content than can fit on any one screen and you want to allow your users to scroll around within it. And you do that by just setting one property on the UI Scroll View, and that's your content size. And the content size is just the width and height of your content. And when you set that property, UI Scroll View knows exactly how much it can scroll in any direction. And it all just works with just that one property set.

But sometimes you need to know which part of the content is currently visible programmatically during the execution of your application. And the way that we refer to that in UI Scroll View is as the content offset. The content offset is the point in your content that's currently visible at the top left of the scroll view's frame. So in this case where we only have vertically scrolling content, the distance from the top to the point that was visible is your content offset.

Now, scroll view supports scrolling, but it also supports zooming. And to add zooming to your scroll view, there's just two things that you have to add on top of that. First, you have to have the content that you want to have zoomed as a subview of your UI Scroll View, and return that from a delegate method called ViewForZoomingInScrollView.

That's the first thing. The second thing is to set two properties, the minimum and maximum zoom scale, that tell the scroll view how much the content is allowed to be zoomed either in or out. And with those two things done, your users can pinch to zoom in and out on the content, or you can program the scroll view to zoom in and out on the content. And you can also programmatically control the zoom scale.

So it's pretty easy. The basics are really simple. If you're not already familiar with those things or if any of that is a little surprising, I recommend that you watch some of the previous sessions from earlier WWDCs or check out the documentation at developer.apple.com. There's a lot more there about the basics, but today I just want to get into some of the more interesting advanced topics that we want to talk about. So first, let's take a look back at page scrolling and that photo browser that we built in 2010.

If you've seen the 2010 session or are interested in the 2010 session, you can go to the page scrolling page and click on the page scrolling button. If you've seen the 2010 session or if you've downloaded the photo scroller sample app from developer.apple.com, then you're already familiar with the kind of thing we're about to be talking about. But if you haven't seen it, just a quick review of what we're talking about here.

You want to build the photo browser that feels just like the Photos app. Your users can scroll back and forth between photos, zoom in on them, and then scroll back from the zoomed-in photo to photos next to them. So just that exact same kind of photo browsing experience that you see on iOS.

If you've seen the sample code or watched the session, then you're familiar with the view hierarchy that we built in order to make this happen. It looks something like this. We had an outer paging UI scroll view that was the base of our view hierarchy, and we used that for scrolling back and forth between photos horizontally.

Then each page, each individual photo, had its own zooming UI scroll view. So we created one UI scroll view per page, and that was what we used to zoom in and out on the photos. And then the photos themselves were each UI image views added as sub views to those UI scroll views.

So we spent basically an entire session talking about how you put all this together. And there was really, there's quite a lot of code behind it. And if you've looked at the sample code, you're probably familiar with that. So the great news with iOS 6 is that that bottom part, the paging UI scroll view, if you're using UI page view controller instead, that can go away entirely. UI page view controller takes care of that entire part of this view hierarchy for us.

So now we're left with a little bit less. We just have these two separate collections of views that we have to build. But you can simplify this even further now because UIPageViewController is a view controller, and view controllers are really great for building screenfuls of content. And really, each one of these photos is a whole screenful of content.

So all we have to really think about now is building a view controller that knows how to zoom and display a single photo, rather than having to worry about all of them. And once we have a view controller that can display one photo, we can just allocate many of those and keep handing them to the UIPageViewController to display as many as we want. So really all we have to do now is think about building a single zooming UI scroll view that knows how to zoom a single image view. So much simpler to conceptualize and really work on than what we had in the past.

The other thing that we spent a lot of time talking about in 2010 and that there's actually quite a bit of code devoted to in the photo scroller sample, and not just quite a bit of code, but kind of slightly confusing and unclear code, I guess, is putting extra space between those photos.

It'd be really nice if they weren't abutting each other like they are in this picture right now, because these greens are just blending together. And it'd be nice if you had some black space between them to just give the user a good sense of where one photo ends and the next one begins.

So we just like to space them out just a little bit. And it really was, well, let's say a little bit of a hack the way it was done in the original photo scroller sample. So it's much cleaner now. And in fact, with UI Page View Controller, not only is it almost no code, but it's really just one property. So when you create a UI Page View Controller, you use this new key, UI Page View Controller Option Interpage Spacing Key.

And it may still be hard because you can't remember that. But that's what completion is for. And that will let you specify exactly how much space you want between those photos. And really, it's as easy as just passing one option when creating the Page View Controller. So that's how we're going to configure it.

Next, let's just take a quick look at how the page view controller will ask for the photos so that we know how things will behave once we have our single view controller configured. So let's put this into an iPhone. Now, let's assume that the user puts their finger down on the screen and starts scrolling to the next photo. At that point, UI page view controller will ask for the next photo using a delegate method, view controller after view controller.

And at that point, you just allocate a new copy of that same type of UI view controller that you created to display and zoom a single photo. Put your new photo in it and return it to the page view controller. Page view controller deals with deciding when it should be added to and removed from the view hierarchy.

It makes sure that it doesn't live longer than it should so that if you scroll through a whole bunch of pages, you don't end up with unbounded memory growth. It takes care of all the details of when the view should be added and removed, and you don't have to worry about any of that. And that was actually a big part of the code that we had to write before.

And then similarly, when we scroll in the other direction, we'll get another delegate method, view controller before view controller. And we can just return a new instance to represent the previous photo. So it's pretty simple. With those things done, all that's really left to implement then is zooming in and out on that single picture that we have.

And with the introduction of UI page view controller, this hasn't really changed too much. So we're not really going to get into the specifics of how this works. If you're not really familiar with how to set up a zooming UI scroll view, the sample code is still available, and there's actually new sample code this year based on the new stuff.

And you can go check that out, but we're not going to get too much into the details right now. But UI page view controller, once it's zoomed in, does handle scrolling back out to photos next to the one that you were zoomed in on. So with that in mind, Eliza is going to come up and do a demo of how we can build this.

Hi, Eliza. I'm an engineer on the Passbook team now. And I'm going to show you the demo or just the sample code that we had from the demo two years ago. I've added one new feature to it. So let me switch over here.

[Transcript missing]

All right, we're going to make a UIViewController subclass, and I'm going to call it ImageViewController because its job is to display an image.

I'm going to add that to the project. I'm just going to move it up here so I've got it handy. Okay, so what is this image view controller going to do? Turns out it's going to do pretty little. It's going to be really easy to write. So the first thing that I'm going to do is declare a couple methods that this class knows how to implement.

The first is a class method to return an image view controller for a particular page index. And then we're also going to have these image view controllers know how to vend the page index that they are displaying. So in the implementation for this, let's get rid of some of this excess stuff.

Okay, we need to import my image scroll view subclass. Now, this is the same scroll view subclass that we were using before. I'm not going to show you much about it, except to note that the class vends this image count. So this scroll view subclass is actually handling all of the logic for figuring out what image to display on any given page. So we'll just keep that just the same.

So we're going to need in this image view controller class to have a page index IVAR, and then we're going to need to implement just a few methods. So we need an initWithPageIndex method just to set the page index on the new instance. We need a page index method to return the page index for a given instance. Now let's go ahead and implement this image view controller for page index method that I declared in the header.

It's going to be pretty simple. What we want to do is we're going to have a page index that's going to be a little bit more complex. So we're going to have a page index that's going to be a little bit more complex. So we're going to have a page index that's going to be a little bit more complex. So we're going to have a page index that's going to be a little bit more complex.

do is if the page index that was passed in is a reasonable one, if we actually have an image for that index, then we're going to return one of these view controllers. If not, we're going to return nil. And you'll see in a minute that that will turn out to be really handy when we're implementing the data source methods for the UI page view controller. So if the page index is within range, if it's greater than or equal to zero and less than the image scroll view's image count, then we'll return a new instance, initialized with the page index that we got asked for. And otherwise we'll return nil.

All right. We're almost done with this class. Just two more methods -- rather, three more methods to go. So we need to make a load view method. And in that method, we're going to do nothing fancy. We're just going to create one of these image scroll views, same class we were using before.

Set its index to be our page index. Set its auto resizing mask to give it flexible width and height. This is really important. If you notice that when we initialized the scroll view, we actually didn't give it a frame. We are not responsible for sizing our view ourself. The UI page view controller is going to do that for us as long as we make ourselves flexible.

So we're going to do that. We're going to set our view -- because we're a view controller here, right? So we're going to set our view to be the scroll view. And that's pretty much it. We need to then also say that we can auto rotate, just to continue to support rotation. So the way that UI page view controller works is it asks its current page permission to rotate when it itself is asked whether it's going to rotate. So we're going to say, yeah, we support all of the interface orientations. All right.

So that's pretty much it for our image view controller. So as you can see, it's a lot less code than we just deleted. And now all that we have left to do is back in this app delegate, we need to import our new class that we just made, our new header.

And then we need to go ahead and make this first page as we promised. So page zero, it's just image view controller, image view controller page index zero. And then we need to set this page on our UI page view controller instance. So it's another one of these many argument methods. Set view controllers direction animated completion.

So the view controllers is just going to be an array consisting of only page zero. The direction is forwards in this case. And we don't really want to animate this or -- and we don't care about when it completes because this is all being done before the app is actually even visible on the screen.

So we're going to set this page as page zero. So great. So we've got a first page. Now, if I were to run this now, you would see the first page. But we would crash when we tried to scroll because we haven't implemented our data source methods. So we need to implement those. We need two data source methods. Page view controller, view controller before view controller, and view controller after view controller.

And these are going to be incredibly fast to write because we just need to grab the page index out of the view controller. We don't want to have to do that. So we're going to return the page index to the controller we were given. And in the case where we're trying to make the one before it, we just need to subtract one and return the page for that index. So I'm going to return image view controller, image view controller for page index, the one before.

This is where it becomes really handy that this method, image view controller for page index, returns nil if the page index is out of bounds. Because the way that you tell a UI page view controller to stop allowing scrolling is by returning nil from this method. So if the page index is out of bounds, we'll just automatically make it so scrolling comes to an end at that page. And then in the after case, we do exactly the same thing but plus one. And now we can go ahead and run it.

And here we go. It looks just the same as before, which is a good sign. I can scroll from page to page. I can rotate and scroll back, rotate again. And let's see, I can still zoom in, zoom out. Everything's working just as before. So minus a lot, a lot of code. So back to Josh.

So hopefully that is going to be a really nice benefit for everybody and make it easier for everyone to get scroll views and photo browsers that feel just like the Photos app on iOS. The next thing that we want to talk about, I'm actually really, really excited about. I think this is very cool. We're going to talk about integrating UI Scroll View into your OpenGL games and applications. So you may wonder why you might want to do this.

A common thing that you end up finding is that you've got something in your game, usually in a setup screen or a browser of some sort at the beginning of a game, where you have to be able to scroll through some content. And it's at first glance not obvious how you might use UI Scroll View in order to actually scroll the content in your OpenGL games, because the way UI Scroll View works is that you put the scrollable content into the UI Scroll View, and UI Scroll View moves it around.

That's obviously kind of at odds with your own OpenGL stuff, where everything exists in one UI view that displays all the OpenGL content. So let's take a look at how we might do this. I really wanted to get everyone really invested in this and excited and pull you all right into the experience here. So I have some really amazing graphics to really just illustrate this well.

So let's assume that we've got a game that we're going to build that is going to be a racing game. And of course, in your racing game, you need to allow your users to pick which car they want to race as. And so let's imagine that we're going to do that on some screen where we have a podium, and we're going to place cars on it that the user can scroll through.

So we've got this immersive 3D game with a podium that's going to display our cars. And I've drawn this myself, so you know it's good. Now let's place our car on the podium, and then we want to allow our users to scroll between different cars to pick the one that they want to race as.

If you were to do this in just a regular 2D game, the way you would probably do that is by taking this 3D scene, pulling the car out of it, and putting it inside a UI scroll view so that you can scroll between that. But of course, in a 3D world, this might not be possible. Because maybe you have some other parts of the scene that interact with these cars. You might have some lights that are on top shining down on the cars that you're trying to display. And maybe those lights are causing reflections off the windows.

[Transcript missing]

You can see here the amazing work of my OpenGL genius. This represents the outer limit of my OpenGL competence, so be impressed. We've got a bunch of cubes here that are rotating, and we have another really big cube here that's rotating more slowly and changing color. So what we want to-- so here's what this app does out of the box.

You can tap on this bigger cube and make it randomly change to a different color and a different rotation speed. You can also tap on these little cubes, and they'll take on the properties of the big cube at that moment. So this way, we can-- for example, if we wait a few seconds and tap on the next one, you can see how the big cube is changing over time, which is really useful. So there we go.

So now we might want to be able to track the big cube's progress over time-- over a longer period of time, and so we might want to have more than just three of these little cubes to record its progress. Okay, so we might want to be able to actually add more cubes, and then we could scroll through them.

So to do this, we can use the trick that Josh just described of placing a scroll view inside the OpenGL view to accept touches, and then we're going to set a new content offset on the OpenGL view to change where the little cubes are being displayed. So let me move over to the code. And basically, we've got a view controller here, which is a subclass of GLK view controller, which is part of the standard setup for making an OpenGL app on iOS.

And then we have almost all of the work is being done in this cube view class, which is essentially drawing all that stuff. It's being updated at every frame by the OpenGL rendering machine, and then it also vends a few properties that are going to be useful when we want to set up this scroll view. So it vends a scrollable frame, which is the frame in the OpenGL view that we want to make scrollable, and it also gives us a scrollable content size telling us how much room there is within that scroll view for scrolling.

It also has a read/write property, scroll offset, that we'll be able to set when the scroll view scrolls so that the cube view will update its view accordingly. All right, so that's the setup. Let's go ahead and do it. So here in my viewDidLoad method, I'm going to add a scroll view. I'm going to set the scroll view's frame to be this scrollable frame vended by our view. And I'm going to set its content size to be the scrollable content size.

[Transcript missing]

All right, my clicking is broken. So in fact, I've actually broken taps. So as you can see, taps are actually still working in the rest of the app. So I haven't broken them altogether. But in the area where the scroll view is, taps are not making it through to my OpenGL view. So to explain why that's going wrong, I'm going to bring Josh back up.

There's actually one other mode that Eliza didn't mention, and that's the NSCommonRunLoopModes. Now, NSCommonRunLoopModes is actually a combination of the other two modes, the default and the tracking RunLoopMode. So it's one constant, NSCommonRunLoopModes, that actually encompasses both. So if you didn't want to schedule in just one or the other but wanted to schedule for both, you could just use NSCommonRunLoopModes.

Now, Eliza mentioned all of this in the context of OpenGL views, but what she didn't mention is that this actually affects all UI scroll view subclasses everywhere, such as UI TextView, UI WebView, the new UI CollectionView, all of these things. So if you're using any scroll view anywhere, you may have run into this in the past.

One common example of other places that you'll see this are places where you're using NSTimers. The way that you would normally schedule a timer, the most convenient way, is to call scheduled timer with time interval target selector repeats. And what this will do is actually schedule this timer on the RunLoop on your behalf. But what it does is schedule it in the default RunLoopMode. So if you schedule a timer like this for one second from now, and one second from now the user is scrolling a scroll view, that timer's not going to fire at the time you expected.

So you may have actually come across this and not been entirely clear on what was happening. That's why it happens. So you can work around this with NSTimer in particular by creating your timer using the other class method, the timer with interval method. It takes all the same parameters, but it doesn't get scheduled into the RunLoop automatically. Now the reason you'll do that is because then you can use the NSRunLoop method to schedule the timer yourself in a particular mode.

So we can call NSRunLoop main RunLoop, add timer for mode, and pass in, in this case, NSCommon RunLoopModes to indicate that we want this timer to fire regardless of whether we're in the default or tracking RunLoopMode. So we can call NSRunLoop main RunLoop, add timer for mode, and pass in, in this case, NSCommon RunLoopModes to indicate that we want this timer to fire regardless of whether we're in the default or tracking RunLoopMode. or tracking run loop mode.

So that's one common case. Another one then is the performSelectorAfterDelay method. performSelector with object after delay also schedules into the default run loop mode. So if you call this method, even though it's not necessarily obvious that it's having anything at all to do with run loops, if your user is scrolling one second from now, the selector that you asked to perform isn't gonna get called.

This one's actually even easier to fix in that there's just a slightly longer version of the same method called performSelector with object after delay in modes. Now, it's slightly different in that this actually takes an array of modes. So here we've used the new array syntax introduced in iOS 6 to have a NSCommonRunLoopModes as the single element in our NSArray.

So if you happen to find that when you're using your UI scroll views, something that you think should be happening right now isn't happening until the scroll view finishes, take a look at your run loop modes 'cause that's probably what's going on. All right, so that's run loop modes. Now, let's see if we can't help Eliza realize her dreams. So, UI responder and event delivery. This is... is really the foundation of the basics of touch handling, ignoring UI gesture recognizer for a minute, on iOS.

So let's take, for an example, a really simple case, ignoring the OpenGL view and the UI scroll view. Let's just assume we have some plain UI view, not a subclass of anything, and that this thing has one child UI view. So we'll put that right in there. And now let's let our user put a finger down inside of that child view.

So the way event delivery on iOS works is we hit test from the window out towards the user and find the deepest view that we would have hit, in this case the child view, and we deliver the touch to that view. So touches began with event will get called on that child view.

If you don't implement touches began with event, and it will automatically get forwarded by the UI responder methods up the responder chain. And the next responder from a view is its super view. So it would get forwarded to the parent UI view. Now, if the parent view didn't implement touches began with event, the responder chain would forward to the next responder, and the next responder in this case would actually be, because let's assume that this parent view has a view controller, would be the view's view controller.

So child view, and the next responder in this case would actually be child to parent to that parent's view controller. Now, if the parent view controller didn't handle it, the next responder is the UI window, because there's no other super view. And if the UI window didn't handle it, it would go to the UI application object. And finally, if it's not handled there, it would go to the UI application delegate that you would have set on your application.

So that would be the normal responder chain path, and any of these views would have seen that touch if one of them didn't handle it. But clearly that's not what's happening in Eliza's case, because the parent view, which in her case was her NSOpenGL, or sorry, her... OpenGL view, that subclass of UI view, is where she implemented her touch handling logic.

So according to this diagram, her parent view, that OpenGL view, should have received the touch. So to understand why it didn't, we have to take a look at the actual view hierarchy, which is a UI scroll view embedded on top of an NSOpenGL... I keep saying NSOpenGL... on top of an OpenGL view. So let's do the same thing here, and have the user put a finger down in that UI scroll view.

Of course, we're going to deliver it right to the UI Scroll View first. But the problem in this case is that that's actually where this responder chain ends delivery. Because UI Scroll View does implement touches began with event and doesn't forward it up the responder chain. So it stops right there, and her OpenGL view never sees it.

So obviously we'll have to do something in order to figure out how her OpenGL can get this touch because that's where her touch handling logic lives. Now, one thing that I've seen a number of times that I really, really, really want to discourage is something that looks like this.

You implement touches began with event in your subclass of UI scroll view and manually call touches began with event on some next responder or some other object. Now, this is really, really not supported. We don't support forwarding touches manually between views that are owned by UIKit. UIKit can no longer reason about the flow of events once you've started manually forwarding views, manually forwarding touches. It's just, it leads to bad news.

It would actually turn your view, or your responder chain into something that looked like this, where you kind of manually forwarded it to the OpenGL view. But because UIKit didn't know you were doing it, it would stop there and not get forwarded on the rest of the chain anyway. It really isn't what you want.

So, that's not going to work. We could try one other thing, which is to put the OpenGL view inside of that UI scroll view, because that would fix the responder chain. Our OpenGL view would start getting the events then. But as we discussed earlier, we don't want this view configuration because we want our scroll view to only be on a small part of that OpenGL view. So, once again, it seems like we've kind of gotten to an impasse. What can we possibly do? We can't have the OpenGL view in the scroll view, and we can't have the scroll view in the OpenGL view.

Well, this is another place where we have to stop and think a little bit differently than we would in any other non-OpenGL application. And again, it goes back to the fact that we don't actually want UI Scroll View to do any of the drawing. All we're trying to do is get its event tracking, its deceleration, and its bouncing. We just want the behaviors that UI Scroll View is providing. So really, we don't even need the scroll view to be in the view hierarchy visible at all, because we're not relying on it drawing anything.

So we could just try and hide it, or move it off to the side, or add it as a subview somewhere where you can't even see it, and it wouldn't actually change our visuals at all. The only problem we'd have left then is that, well, obviously it's not going to get any touches if it's not visible and on screen. So we would have to come up with some way to make sure that it's still got touches if we were to try and do this. And it turns out that we actually can do exactly that.

So let's put a dummy UI view in its place just to provide the sizing exactly where we had had it in the view hierarchy before. And this dummy view can just be a plain UI view. It doesn't have to be a subclass at all. The most important property of this view being that it doesn't implement touches began, moved, ended, or canceled. And the reason for that is that we've now fixed our responder chain. We've got a child view, and it will forward events to the OpenGL view if it doesn't handle them, which it won't, because we're not going to have it.

So we've got the responder chain back in order, but we still don't have our UI scroll view receiving any touches. But we can fix that by taking advantage of the fact that UI scroll view does its touch tracking using UI gesture recognizers. So UI scroll view has a number of UI gesture recognizers attached to itself.

And those gesture recognizers are exposed through properties on the UI scroll view. So we can grab them and move them to another view. Once we've attached them to that child view, they're in the view hierarchy in a place where we want them to receive touches, and so they will start receiving touches.

When those gestures recognize, they'll notify the UI scroll view that they have been recognized, and the UI scroll view will begin scrolling despite the fact that it's not actually visible on screen. And really, this will let us implement exactly what Eliza was trying to do. So in order to show how that works, she's going to come back up and do one more demo.

Okay, this is going to be a really easy demo. We just need to replace our scroll view with a dummy view and move the gesture recognizers over. So the first thing I'm going to do is move the scroll view out of the way so that it stops trying to usurp all my touches.

And I can do that by moving it aside, by sticking it under everything, but probably the easiest way to do that is just to hide it, because hidden views don't get hit test. So I'm going to go ahead and hide it. And now I'm going to add a dummy view. I'm going to make the frame of the dummy view be the same scrollable frame, because that's where we want to be able to detect our panning gesture.

I'm going to add the gesture recognizers from the scroll view. So here I'm accessing the scroll view's pan gesture recognizer property. I'm just taking that gesture recognizer and I'm moving it over to the dummy view. I only care about the pan gesture recognizer in this case because I'm not trying to allow zooming on my view. And then finally I'm going to add this dummy view as a subview of my OpenGL view. And that's pretty much it. So that ought to solve our problem.

So here we are scrolling. So I can still scroll, first of all, which is pretty cool because the scroll view is actually not even receiving touches now. And then I can actually now tap these views and it works. So this actually does solve the problem. Now, it solves it in an odd way, right? We have this view here that's kind of performing this weird function and it's -- It's a little roundabout. So I want to just mention that under some configurations, you wouldn't actually have to do this.

So as it happens, my cube view is detecting touches using touches began, moved, ended, and canceled, which makes it vulnerable to this responder chain problem. So we wanted to show this because we think that people who already have existing OpenGL apps and have already got their touch handling written might want to be able to adopt this method.

But if you were writing an app from scratch and you decided to use gesture recognizers for your touch handling, then you wouldn't face the problem that I faced at the end of the last demo because gesture recognizers don't depend on the responder chain to recognize. So had I been detecting taps using a UITap gesture recognizer, we wouldn't have even had this problem in the first place.

So keep that in mind if you're adopting this technique from scratch. In the case where you're using tap gesture recognizers to do touch handling, then you could just leave the scroll view there, not hide it, not have a dummy view, and everything. All right, so turning it back over to Josh for one more topic.

All right. Thanks, Eliza. So the last thing we want to talk about-- just kidding. I don't know why they put that button on there-- is

[Transcript missing]

So let's add this. So in this case, let me switch back to the application running. We don't have any cars, because I couldn't possibly draw one in OpenGL. But let's make it so that every time that you scroll this, you end up perfectly centered with three cubes in the middle of the screen, so that this configuration isn't a possible resting point for the scroll view, just to demonstrate the concept.

So in order to do that, we're going to add a new method that this cube view implements. I'm calling it Scroll Offset for Proposed Offset. So what the cube view is going to do is go look at the offset that was proposed, figure out what's the nearest one that would get three cubes perfectly centered on the screen, and return that. So let's go ahead and implement that.

So switching to my implementation, I'm going to add it here under my scrollable content. So in this Scroll Offset for Proposed Offset method, the first thing that I'm going to do is calculate how much have we overshot. So basically, you imagine that here's where you'd be if the three cubes were perfectly centered.

But in fact, the user has dragged the scroll view over to there. So we want to calculate how much did we overshoot the perfect offset. And I'm doing that by taking, I'm modding the actual offset. So I'm going to take the offset X by the width of one of my little cubes to see how far into a little cube did we get.

So now that I've calculated that overshoot, I'm going to see, well, did we overshoot by less than half or more than half of a little cube? Because if it's less than half, we want to round down, and if it's more than half, we're going to want to round up. So if the overshoot is less than half of a little cube width, then we will make our offset X, we'll subtract the overshoot from the offset.

If it was more than half of a little cube width, then we'll add on the rest of that little cube width. So that's the little cube width minus the overshoot. And then we're just going to return the new offset that we calculated. All right, so let's call that from our view controller. Once again, we're going to add a final view controller delegate method, the one that Josh just mentioned. Let me give us more room.

So in this scroll view will end dragging with velocity target content offset method, we're going to add a new offset. So we're going to take the target content offset here that you can see at the end, and we're going to pass that to our cube view as the proposed offset.

So we're going to say the new target content offset that we're going to return via this in-out parameter is the result of passing the one we were given to this scroll offset for proposed offset method. And that's pretty much it. So if I run this now, you can see that when I scroll, it sort of decelerates slowly to the exact right point.

If I even were to drag a little way into a cube and let go with no momentum, it would still come back to the page boundary that we wanted. So that's a pretty useful method that I think is cool that got added back in iOS 5. So that's pretty much it for my demos, and I'm going to turn it back over to Josh to finish up.

All right, well, thanks again for coming. If you have any more questions about any of the content on the session here, Jake Behrens is the UI Frameworks evangelist. If you want any more documentation about Scroll View, of course, that's the Scroll View link at developer.apple.com and obviously the dev forums. There's a couple of related sessions later still this week. The Introducing Collection Views session. If you haven't seen the new collection views introduced in iOS 6, this stuff is pretty cool. And you can learn all about it in the repeat. Thursday at 9:00 AM.

And then Building Advanced Gesture Recognizers is also tomorrow, Thursday at 11:30 AM, where we'll talk more about awesome things you can do with gesture recognizers, if you haven't seen a lot about that yet. There's also a Scroll View Lab tomorrow morning, first thing in the morning, 9:00 AM. So if you have more Scroll View questions, come see us there. Thanks for coming.