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-233
$eventId
ID of event: wwdc2012
$eventContentId
ID of session without event part: 233
$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 233] Building Ad...

WWDC12 • Session 233

Building Advanced Gesture Recognizers

Essentials • iOS • 50:29

Gestures are an integral part of the user experience on iOS. Learn key practices for controlling gesture interactions in your applications and avoiding common pitfalls. Tap into advanced techniques like curve smoothing and low-pass filters to create fun and intuitive interfaces in your apps.

Speakers: Andy Matuschak, Josh Shaffer

Unlisted on Apple Developer site

Downloads from Apple

HD Video (195.4 MB)

Transcript

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

All right, good morning. Today, we want to spend some time talking about some advanced things that you can do with UI Gesture Recognizer. And to really get into it and have a good idea of how we got to where we are today from what gesture recognition was before UI Gesture Recognizer, we want to do a little bit of backstory and just give you a brief overview of the history of the gesture recognizer system and sort of how it came to be from more of a technical standpoint, the issues that were involved in coming up with it.

And then from there, once you kind of understand what the gesture recognizer system is about, we want to talk a little more about gesture interaction because this is one of the biggest points and the biggest reasons for the gesture recognizer system to exist at all is to manage the interaction between multiple different gesture recognizers. And then after we've gone over that, Andy's going to spend some time talking about subclassing fundamentals.

Once you understand really what the gesture recognizer system is and how gestures interact with one another, hopefully you'll have a really good idea of what the gesture recognizer system is. And then after we've gone over that, Andy's going to spend some time talking about subclassing fundamentals. So, I think it's a really good idea of why you also want to be writing your own UI Gesture Recognizers when you're thinking about building your own custom gestures.

And a big part of that is going to be talking about the fundamentals for what you should be doing while subclassing things. Now, most of the focus of that section is going to be about some interesting things that you should be thinking about and keeping in mind while you are subclassing, rather than some of the basic mechanics of which method to override. So, if you're already familiar with how subclassing works, there's... There's still some even more interesting stuff that we'll be talking about there.

And then finally, we're going to end by talking about some pretty advanced signal processing techniques that you might want to use when building your own gesture recognizers. Some things that we've found that we've used in our own gestures in certain places, and stuff that hopefully when you're going to write your own, you'll find these things to be pretty useful and interesting. So, let's start by talking about the gesture recognizer system. A big part of the recognition of gestures is thinking about state machines.

And state machines are a pretty easy and common way to end up thinking about how gestures work. So, let's take a simple example of just a collection of gestures that we might decide that we wanted to recognize from a basic app that you've seen on iOS, you know, plenty of times, the Maps app.

So, Maps implements a few different gestures and supports a number of different things. So, your users can put a finger down on the screen, and they can move it around to pan the map. They can put two fingers down and then pinch to zoom in and out on that map. that.

and then there's also a collection of other things that interact with those as well or that are on the side. So you've kind of got your pan, you've got pinching, you've got taps, double taps, two-finger taps, but that's kind of the collection of them. So if you were to stop and think about how you might implement this set of five gestures in an app like Maps, and you didn't have gesture recognizers or didn't really know about them, you might start thinking that you have to build a state machine to keep into account what fingers are on the screen, how long they've been there, and what they've been doing, that sort of thing. So you might start adding some states, maybe a start state when nothing's happened yet, a couple of states for tracking when one finger's down and when two fingers are down.

And that sort of gets you started, but then you have to start tracking when things have not happened. So you have to know that a one-finger tap is not possible even though a finger is still down. Or maybe two fingers are still down, but that can't be a tap anymore.

And then maybe you have to handle also the case where there's more than two fingers. And we're starting to get pretty many states, but we're not even done adding states yet because now you might also have to be tracking things like one finger was down, but it's not a double tap, but maybe it might still be, or maybe it's not anymore.

I don't know. Even more states. So it starts to get kind of out of hand pretty quickly with even a fairly small number of gestures, only five. But we haven't even really reached the worst part yet because we didn't even talk about the transitions between these states. You have to start defining which ones of these states you can transition from at any given time, which ones are valid to transition to from another state. And now all of a sudden, you pretty much are screwed. There's really nothing you can do here. So this has gotten incredibly complicated.

And honestly, that's actually the kind of thing we were dealing with with gesture recognition in the Maps app in the early days of iOS before gesture recognizers. So we took a step back and thought about what's the simplest state machine that we could build that would represent the recognition of gestures without getting into that crazy spaghetti thing that we could never maintain over any period of time.

And so what we've got is the gesture recognizer state machine. And every gesture recognizer starts in state possible. From state possible, often, and most commonly probably, we'll go to state failed, indicating that the user did not recognize or perform whatever gesture it was that we were looking for. And in the simplest case, you'll have one state that's state recognized.

And this is for sort of discrete gesture recognizers, ones that they recognize at one period of time after some certain event, and then they're done. So like a tap gesture recognizer or a swipe gesture recognizer, either it happened or it didn't, and that's all. So for those types of gestures, this is really the entirety of the state machine.

And that's way simpler than everything we were talking about before. There is a slight bit more complication when you start talking about more continuous gestures, things that recognize over time, like pinches, rotations, pans, that sort of thing. They report updates over a period of time as the user keeps interacting. So those we add just four more states, began, changed, and ended, and then a canceled state where we can, if we've already started recognize, we can still cancel.

But so with that, that's all the possibilities. So we've got a lot of possibilities in the UI gesture recognizer state machine, just those seven states, so significantly fewer than we would have ever had if we started trying to build it before. And a pretty small number of transitions that we can easily track and keep in mind. But the really great thing about this state machine, and the part that makes it so much better than what we were talking about before, is that this state machine represents one of those gestures. So that's the state machine, for example, for pan.

And you can think of that and build that data, and build that gesture completely independently of all the others. Each of those other gestures that we're trying to recognize has its own version of that state machine that's independent from each other one. And you don't have to have a lot of crossover between them. In fact, they're all independent. So when you're thinking about how you'll write your individual gesture recognizer, you just think about that one, and then worry about interactions with other gestures after the fact.

So it really lets you focus on just the one thing you're trying to recognize at any given time, and keeps the code much, much simpler and cleaner. So once you've got your gesture written then, which, in the case of all of these, UIKit provides all these gesture recognizers, so you don't even have to think about the implementations of these, because they're already being handled by UIKit. All you end up thinking about is the five gestures that you want to recognize, and then defining the relationships between them. So for example, pinch and pan.

You really want the user to be able to pinch and pan at the same time. Now the default behavior with all UI gesture recognizers is that any UI gesture recognizer that's recognizing will be recognizing by itself. No two will recognize at the same time. But that may not be what you want in all cases, so we could set up that pinch and pan should be allowed to recognize simultaneously.

[Transcript missing]

With that new behavior in place, when the user taps in the UI button, the button will perform its action and the tap gesture recognizer will mod. Now, that's probably actually what you want in most cases. What we've done in UIKit and iOS 6 is actually made that the default behavior. There are a few different controls that have adopted behaviors like this. I just want to go through them real quick. We've got UIButton, UIPageControl, UISEgmentedControl, and UISTepper.

The primary point of all of those controls is to look for taps inside of themselves. So they will always prevent single-finger, single taps that are attached to a super view from recognizing if the tap is also in this control. This is probably what you wanted anyway, and now you'll just get it by default on iOS 6.

Sorry about that. And then one other thing that's similar to this is UI slider. The primary point of UI slider is to look for fingers that are down in the slider's thumb and slide left and right to track the slider. If you have tried to put your sliders in places where there are swipe gesture recognizers or pan gesture recognizers, you have probably found that your sliders stop working. You may have found this, for example, in UI split view controller in iOS 5 with the new swipe behavior.

In iOS 6, UI sliders will prevent any swipes or pans for fingers starting in their thumbs if they're on the same axis as the slider itself. So that problem is now fixed. So anyway, hopefully that gives you a really good idea of why you want to be playing in the UI gesture recognizer system.

If you use the system itself and follow its state machines, then you'll get all these interactions for free and you won't have to build all that stuff yourself. So to talk a little bit about how you can subclass things, Andy's going to come up and talk a little bit here.

Thanks Josh! Now you folks are in a talk that begins with the word advanced, and so I'm not going to tell you how to subclass things, per se. It's more just that we've been building a lot of gesture recognizers over the last couple years, and you all have too. And in building those gesture recognizers, we've run into some issues. And I know from being in the labs and reading on certain mailing lists that you all have as well.

And so rather than telling you which methods to override and how the colon works in the @interface declaration, I'm going to talk about some of the issues that we and you all have run into so that I can help you avoid them when you're implementing future gesture recognizers. We're going to talk about three issues, and the first one is what should you do when your gesture recognizes? It seems like a really elemental question, and it seems like you should have a tremendous amount of control and liberty over this.

I mean, you can do whatever you want when your gesture recognizes. You can execute arbitrary code. That's what code is. But there are some issues if you do just anything. So say that you have a gesture recognizer that's going to try to recognize when the user draws in a circle.

So it's a discrete gesture recognizer. And if you implement it in this fashion, where you're just going to tell your delegate, hey, I found a circle. The user drew it. That might seem like a really straightforward approach. Because on Cocoa and Cocoa Touch, we have this delegate design pattern wherein objects tell other interested objects when interesting things happen.

And so you might think, well, the user drew a circle. That's what this is for. So that's what's interesting. And so I'm going to tell the interested party about this thing. The problem with this approach is that you don't get to take advantage of all of the fanciness that Josh just got finished telling you about. So the whole point of the gesture recognizer system is that you get to write these self-contained little components and then define how they interact with each other.

In order for those interactions to work properly, like require gesture recognizer to fail and should recognize simultaneously, and just the standard exclusion behaviors, the system needs to understand what's going on with your gesture recognizer. Is it recognized? Is it failing? What's happening? And so the number one responsibility for you as a subclasser is to move your gesture recognizer through this state machine that Josh was talking about. You need to tell the system what's going on.

So that it can make the other gestures, which maybe you don't even control, react accordingly. For the example we just had, it's pretty simple. Instead of messaging your delegate, you just set the state of that gesture recognizer to recognized. And what was your delegate can subscribe to an action resulting from that by just adding a target action pair where the delegate is the target.

You'll also have to import this UI Gesture Recognizer subclass header if you weren't already, because set state is not in the standard header. It's only for subclasses. But actually, we aren't done yet because we told the system when the user is drawn a circle, but we haven't explained to the system when exactly it is impossible that what the user is doing presently is a circle.

In particular, what if the user lifts all his fingers and he hasn't drawn a circle yet? Or what if he's drawing with his fingers and it turns into the system crumple gesture? That's not a circle anymore, and we need to tell the gesture recognizer system about that so that any gestures that are waiting for the circle-recognizing gesture to fail are allowed to proceed.

Andy Matuschak, Josh Shaffer In particular, what if the user lifts all his fingers and he hasn't drawn a circle yet? Or what if the gesture recognizer system about that is not allowed to proceed? That's not a circle anymore, and we need to tell the gesture recognizer system about that so that any gestures that are waiting for the circle-recognizer system to fail are allowed to proceed.

For that circle recognizer, for basically every recognizer, you're going to be doing a lot of math with the locations of your touches. That's how you're going to determine, is this the gesture that I'm looking for? When you're doing that math, you have to pick a coordinate system in which you do the math.

It's essentially a reference frame. And there's a number of different choices you can make and a number of different consequences that will happen if you choose any particular one. Let's look at this example here. Say that you've got a scroll view, and in each of those little squares, there's a tap gesture recognizer.

Let's talk about how we would implement the Tap Gesture Recognizer, and I'm going to let you in on a bug that we had in our initial implementation of Tap Gesture Recognizer. We made a mistake here, and I'm going to try to help you avoid making the same mistake. Let's say that we're going to use the views coordinate system to consider whether the user is tapped or not. We'll define a tap as the user's put his finger down and then lifted it in mostly the same place.

So if the user puts his finger down here, remember we're considering the location of that touch in the view's coordinate system. That's the teal square. And consequently, the location of that touch will be considered relative to the upper left-hand corner of that square. So we write down that location, and then the user scrolls his finger up and lifts it, but relative to the upper left-hand corner of that square, it hasn't moved at all.

So the system thinks, "Oh, the user tapped." Not quite. That was a pan, not a tap. So in order to solve this issue, we need a better reference frame for our touch than the gesture's view. We found that in most cases, using the screen coordinate system to do your touch math makes the most sense.

So here, in order to accomplish that, I've converted the incoming touch's location to the window of the gesture recognizer's view. And then I've used this UI window method to convert the point from the window to the screen which contains the window. So now when the user puts his finger down, we're considering that location not relative to the upper left-hand corner of the teal square, but rather to the upper left-hand corner of the screen. And so when the user moves his finger... The tap gesture recognizer correctly sees that it didn't arrive and lift in the same location, and it says, "Okay, that's not a tap." It gets it right.

So in general, when you're doing a touch map, it's usually best to use screen coordinates, not only for processing incoming touches that are arriving and the touches began, moved, ended methods, but also for any touches which you're saving as an instance variable in your gesture recognizer to do math with later. It's best to save those in screen coordinates.

Shifting touch sets. They're a tricky issue because you might have implemented a gesture recognizer to deal with a single touch or maybe two touches, but what happens if the user changes the number of touches while your gesture is in flight? Let's consider a novel gesture, not included with UIKit, called the tap, pause, and pan gesture recognizer.

So the user puts his finger down in this square and pauses, pans across the screen. What we'd like to have happen is have the square follow the user's finger. Maybe we're in some kind of outlining application where this is the sane way to move items around on the canvas. So it's pretty easy to do this for the single finger case. You just move the box by the difference between the user's present location and his starting location. So all is well.

But what if the user puts two fingers down? What does current point mean? What does start point mean? It doesn't really mean anything anymore. You can't just pick one of them. So instead, we consider this thing called the centroid, which is just the average of all of the user's touches. And now, as the user slides those two touches across the screen, we can consider the current centroid's location minus the initial centroid location and move the box by that amount.

But that issue with Accentroid is actually not the one I wanted to talk about today. It's just some background. The real issue is, what if the user puts a finger down, pauses a moment, moves with that one finger to the right, and then puts a second finger down? Well, the current centroid is now below the start centroid, so that pox is going to move down.

And then if the user lifts that second finger, it'll jump right back up. I don't know about you, but when I see this happen in applications, I like to just sit there messing with it, putting one finger down and then lifting it up again, being very, very sad. Very, very sad. So let's talk about how not to make this happen.

A little bit of math. What we'd like to have happen here is for the translation of the box before the user changes the number of touches to be equal to the translation of the box after the user puts that new finger down. But we can't just set these two equations to be equal to each other. We know that that start centroid term is going to be the same in both cases.

And we just saw the old centroid and the new centroid are not the same. It moved. So there's no way to make it work with just this. We have to add an extra term that here I'll call shift. It's going to account for the change over time in the centroid as the user changes the touch confirmation.

So we have this extra term, and now if we set these two equations to be equal to each other, we can solve for this shift term and see that every time the user changes the number of touches, we're going to shift the shift term by the difference between the old centroid and the new centroid.

I think this will make some more sense graphically. The user puts one finger down. And then when he puts another finger down, you'll see that arrow there. That represents shift. So the system's calculating the current centroid minus the start centroid, which would move the box down and to the right. But the shift balances that by moving it back up and to the left.

And what it's doing will become even clearer as the user slides his finger across screen. Really, it makes it so that what you end up considering is not the current centroid of the current touch configuration, but rather where the centroid of the initial touch configuration would have been had it stayed the same and moved around the screen in the same way.

So that translation arrow is always going to point to the tip of the shift arrow. Even as we continue changing the touch set, recalculating the shift arrow, you see that now it's balancing the current centroid being just that lower right touch to point to even further away so that the translation doesn't change at all. We've talked about how to make subclasses of gesture recognizers that work properly. Now that we've got them working. Let's make them awesome. Josh is going to give you some great techniques to make your gesture recognizers even better.

All right, thanks, Andy. So what I want to talk about now for a little bit is low-pass filters. So what? Why? What does that mean? So what you may find sometimes when you're writing your gesture recognizers is that certain values that you're computing or things that you're looking at over a period of time might have some jitter in them, things that aren't quite as smooth as you would hope that they would be.

And one common technique for removing jitter from a value that you're processing over time is a low-pass filter. So let's take a look at what I mean by that. And some common things that might use this are things like pinch gesture recognizer, pan gesture recognizer, rotation. And the reason that you end up seeing them in this case pretty often is just because of the fact that there are multiple fingers involved.

The presence of multiple fingers and the fact that you would be computing values based on the positions of those multiple fingers can introduce, additional jitter into some of your computations than you might have initially had if you were just looking at one. So to really understand why that might happen, let's take a look at how we would calculate pinch velocity, for example, in a pinch gesture recognizer.

So let's say that this is an incredibly enlarged version of a screen and that each of those little squares is a pixel. And let's have our user put two fingers down somewhere on our screen. Now, granted, this is a little bit expanded beyond what you'd actually have in real life.

But at some point, that finger has to be determined to be at a particular pixel. The positions that are reported to you for individual touches, while the type of the position is CGFloat, well, CGPoint, which is two CG floats, those floating point values have only the granularity of individual pixels for their accuracy. So we don't actually report locations mid-pixel.

So that means that even though here it looks like our fingers have come down near the edges of two different pixels, the values that you're going to get are going to be the same as the values that you're going to get when you're looking at the screen. So that means that even though here it looks like our fingers have come down near the edges of two different pixels, the values that you're going to get are going to be the same as the values that you're going to get when you're looking at the screen.

are just going to represent the whole pixel in its offset. So if we tried to calculate how far apart these two fingers were, we would have to calculate the difference between these two pixels, because that's all the information that we have as far as the actual locations of the fingers. So in this case, we've got three pixels horizontally and seven vertically.

Now, if we go and calculate the actual distance between those, we would do that just using a squared plus b squared equals c squared. So we've got square root of the sum of the squares of the sides of this triangle gives us the distance between those two. So we've got two touches.

And that's, in this case, just 7.6. So 7.6 pixels is about our distance. But of course, what we were trying to measure was that blue line there, the solid blue line. But what we've actually measured is the distance between the centers of those two pixels, which is really bigger than the truth.

Now, as the user moves their finger closer together, let's say that they're going to cross some pixel boundaries here. And that's going to cause these next two pixels to light up, because those are the ones that we now think we're in. Now, of course, we've moved a pretty small amount, but yet we thought, we've moved a whole pixel, because that's all the accuracy we have in our reporting. So if we do the same thing again, now we've still got three pixels horizontally, but now we've got five vertically. So our distance that we'd calculate now between those two is about 5.8 pixels.

Again, now let's keep the user moving their fingers closer and closer together. And they've moved about the same amount, not very much more than before. It's roughly equivalent. And let's say that they were moving at a constant speed, so the time between those two movements was about the same. Now let's calculate another delta. This time we only have one pixel difference horizontally, but vertically we've got five. So if we calculate that delta now, we've got about 5.1 pixels of movement.

So the difference between our first two samples was about 1.8 pixels. The difference between our second two was only about 0.7. So it's less than half, but we moved about the same amount of physical distance and we were doing it at the same speed. So if we were calculating velocity here, we would have calculated two pretty wildly different velocities for those three individual samples, the two velocities we could have gotten, even though physically the fingers were moving at about the same speed.

So we kind of have a limitation here in our ability to accurately... So if we end up with that and we were just to graph it over a period of time that was longer than those three touch samples, you might find that your velocities look something like this for what would otherwise seem to the user to be continuous physical movement. So that's kind of not what you want.

And if this physical movement somehow became visible to the user somehow, whatever the output of this calculation was, was something that was reflected back in the user interface in some way, say by tracking an object or something, it might be noticeably jittery to the user. The fact that these are the calculations we ended up performing. So we could apply a low-pass filter to this data, which would smooth out some of these high-frequency vibrations in the data and make it a much more consistent value.

And that would end up looking something a little more like that, which is much, much closer to a constant velocity that the user actually thought they were moving their finger at. So clearly, that would improve our situation as far as the quality of the data that we got. So how would we do that? Like this. It's really easy. No, actually, I probably... I promise it actually is really easy.

I just threw a bunch of math up there to, you know, scare you a little bit. But we're going to calculate the velocity based on a particular time. So here we've got an equation for the velocity at time t. All we have to do is pick some constant that we're going to use as a multiplier.

And here we've called that alpha. So alpha will be some value between zero and one. And that's going to represent a weighting towards the previous velocity. So you might pick any value between zero and one, depending on how much you'd want to weight towards the previous velocity that you had been calculating before. And we'll multiply that times the previous velocity, v at t minus one. So what we had calculated one sample ago. So we're taking some percentage of the previous velocity and using that as the new velocity.

And then to that, we're going to add the opposite percentage of the new velocity that we just took as the current sample. So one minus that alpha. So if the alpha before had been 0.75, this one would be 0.25. And we'd multiply that times the current velocity sample.

So the velocity we calculated based on the difference between our... our most recent two touch points. And we'd add that in to our new velocity. So basically, we've taken some percentage of the old velocity and added some percentage of the new velocity. And now we've calculated what we'll actually use as our value.

So if that's not entirely clear from the math, let's look at what it would look like in code. You'd probably have some constant value to represent alpha. So let's say we'll call it previous velocity weight here. And that's just going to be a constant CG float. Here I've picked 0.75. So then we'll add a new method that we're going to call... every time we calculate a new velocity.

So every new touch move that we get, we're going to calculate an instant... a current velocity sample from the current point and the previous point and the time between them. And then we'll pass it in to this add velocity sample method. And what that's going to do is just perform this alpha calculation. So we're going to multiply the previous velocity weight times whatever our instance variable was that was tracking our velocity.

So let's say we had a... Let's say we had an instance variable called... current velocity. That's what we're using to track the velocity over time. So that's our previous velocity. We'll multiply that times previous velocity weight. And to that we'll add in the factor of the current velocity sample. So we're going to add to our current velocity 1 minus previous velocity weight. That's the 1 minus alpha.

Times that new velocity sample that we just took. So it's really just two lines of code. It's not as complicated as it seems like it might be. And really that's all there is to implementing this low pass filter. And that would give us that smoother line that we saw on that previous sample. previous graph.

So we talked about what it would look like if the user did this while moving their finger at a really constant velocity. But what happens if the velocity changes? So let's say now that the user was moving their finger, constant velocity, well, really two, because we're talking about pinch velocity. And then at some point, they just changed their speed, but they moved to a new, faster, but still constant speed. The data, the raw data, might have looked something like this. We had a velocity that was pretty constant at the bottom, but we were getting jitter.

And then they sped up, so it moved up, but still jittery up there. So if we used our low-pass filter and picked that value of 0.75 that we've been talking about, what we'd actually end up getting would look something more like this. So as you can see, it's kind of now taking a little while to catch up to the new speed that the user's moving their finger at. As I said, in this sample, the user actually increased their velocity instantly. They just quickly started going faster. But our sampled low-pass filtered velocity seems to take a while to get up there. This is the trade-off that you get.

When you start implementing this low-pass filter, it's going to take a little time for the value that you're actually filtering to track up to the new value. Now, the time that it takes, though, will be based on the value of alpha that you've chosen. So this kind of becomes an individual choice for you, depending on what kind of value you're trying to filter.

If it's something that isn't immediately visible to the user, what the value is, you might be able to get away with a larger value of alpha, because the fact that it's catching up a little slower may not be a good thing. Maybe you're just using it internally for some other calculations.

But if the user is going to really notice directly whether or not, you know, if there's lag introduced by this calculation you're performing, then you probably want to use a smaller value for alpha, because that will avoid some of this. So, for example, if we had chosen 0.5 instead of 0.75, we'd get this green line, which is much closer tracking towards the actual velocity the user was going at. It's, you know, higher up there than the white line. But the downside is that we've lost some of that green line.

So we're going to have to do a little bit more of that. So we're going to have to do a little bit more of that. And that's kind of the benefit of the filter. It gets there faster, but it also has more of the original jitter still in that signal.

If you had chosen a bigger velocity of -- sorry, a bigger value of alpha, say 0.9, it would take even longer to catch up and would look probably something more like that. So there's tradeoffs. And really, if you're using this in your own apps, I would, you know, try different values for your particular use cases. You may find that one feels better than another in a particular case, depending on what it is that you're trying to filter.

But that's the general idea. And hopefully gives you an idea of what kind of things you might decide to try if you're finding that you're seeing some jitter in your applications. So with that in mind, Andy's going to come up and show us another advanced signal processing technique for smoothing curves.

Thanks. So Josh just taught you about how to smooth out data over time. Let's talk about how to smooth out data over space. Say that you're implementing something which requires showing the user a visual representation of the path that their fingers traced on screen. You're writing a handwriting recognition app. You're writing an app for casting magical spells, and you need to draw some ancient rune and show the user what they're doing. So this section is for you.

Say that the user draws a beautiful squiggle just like the one I've created here. That's lovely, and we would love to have our application reflect the beautiful squiggle back at the user so that he can see what he's doing. There's just one problem, which is that your application doesn't receive the beautiful squiggle. Your application receives six points, because that's all the temporal resolution that we have.

and from those six points, it's now your job to try to reconstruct as close an approximation as you can of that beautiful squiggle. Of course, there's no way to actually do this perfectly, but hopefully with what I show you, you can get a little bit closer. This is what most people tend to do, is connect the dots. And if I were the user and I saw this, I would be a little upset. This isn't what I drew. You're making me look bad. This isn't my magical spell at all.

It's very sad. So with what I'm going to show you, it'll end up looking a little bit more like this. No, it isn't exactly tracking the orange line, but that's impossible. You don't have enough information to exactly track the orange line. So what we're going to do is make some trade-offs, and we're going to track it less closely in some places and more closely in other places, and just like with the low-pass filter, make some kind of smooth approximation.

We're going to do that with this thing called UiPezierPath Quadratic Curves. You can see the indication at the top of the screen there. Let's talk in a little more depth about what the heck those are. Previously, when you had a series of touch samples and you just connected the dots, you drew lines between each point.

and the parameters that specified that line were the start and end points. That's all you need to draw a line. But with a quadratic Bezier curve, there's a third point. There's this control point. Here it's a circle. And the line doesn't go through the circle. The control point does something else. It specifies a few things about the properties of the curve.

First, the further that the control point is from the line connecting the start and end points, the sharper the curve connecting them is going to be. And then second, the closer the control point is to one side, the more the curve is going to be skewed that way.

Now that you have an intuitive explanation, let's talk about code. You can make this happen in your application by moving a UI Bezier path to the start point. And then instead of saying add line to point, you can say add quad curve to point. And then there's this extra control point argument.

These Bezier paths have a lot of really nice mathematical properties, and those have helped me understand them over time. One that I really like, that helps me get a good grasp on what this thing is, is that the curve which results from the code like this, it's the only parabola that passes through the start and the endpoints, but which has the tangent at the endpoint and the tangent at the start point both passing through the control points. So now going back to this, we have to figure out how to turn those touch samples into a series of quadratic Bezier curves, which means essentially figuring out start, end, and control points, given these six input points.

What should we use as the control points? Well, if we use the midpoints between each of the touch samples that we have, then we'll just get a straight line. Because remember, the further the control point is from the line connecting the start and end points, the more curvy the curve is. So instead, what we're going to do is we're going to use the actual touch samples the system gave us as midpoints.

And sorry, as control points, we're going to use the touch samples the system gave us as control points. And we're going to use the midpoints between the touch samples the system gave us as the start and end locations. And that way we won't exactly curve through the touch samples that the system gave us, but we'll sort of curve around them, taking a little bit on one side and giving it back on the other, creating a more smooth approximation of the user's curve.

This has been a lot of talk, but in our industry, talk is cheap. Let's code. This is a spellcasting application, and here is my magical rune. The only problem with my magical rune is that it's really ugly and blocky, and it makes me look bad. Let's try to fix this situation up using what we've just learned.

To give you a brief tour of the project here, the only code that we really care about is in this curve renderer class. This curve renderer class has one public method, which renders a set of location samples into an image of a particular size. And all that method does is create the image, set some drawing parameters, fetch a path, a UI Bezier path, and then stroke it. So the real part that we care about here is this method which generates the UI Bezier path.

And right now, it's generating a UI Bezier path that connects the dots, like I was showing you in the slides a moment ago. And that's why my path looks so bad. So this generates a path which starts at the first location, then looping through all of the rest of the locations, just adds a line to each one. We're going to replace this with an implementation which uses quadratic Bezier curves.

Now we still want to start with the first location, so this line can stay. And if there's only one location sample, we can just have a dot, which we get by having a line starting at one point and ending at the same point. So this code can stay too.

What we care about is this else statement. So it's actually going to start basically the same way, with a straight line. This is a detail I didn't show you in the slides, but it's one that emerges when you try to actually implement this thing. I said that we were going to use the midpoints

[Transcript missing]

The problem with that is what do we do with the first midpoint? I said the midpoints are going to be the start and endpoints.

So how do we get from the first touch sample to the first midpoint? We don't know how curvy it should be. The easy solution, one solution, is to just draw a straight line. So we're going to draw a straight line from the first touch sample to the first midpoint and from the last midpoint to the last touch sample. And then we'll curve between everything in between.

So that's all we're doing here. I'm using a little helper function here called CGPointMid that just calculates the midpoint between two CG points by adding together each of the components and then dividing them by two. So I fetch the second location. I get the midpoint between the first and second location, and I draw a straight line to that point. Once I've done that, we're going to loop over all of the remaining locations. But because I said we're going to draw a straight line from the last midpoint to the last touch sample, we're going to leave out the last touch sample.

So for each location that the system gives us, we're going to calculate the midpoint between that location and the next location. And like I said, once we've got that midpoint, we're going to add a quadratic Bezier path connecting the last point we were at and that midpoint using the current location the actual touch sample the system gave us as the control point, which will control how curvy the line should be. This is all there is for creating the quadratic curves. Once we've done that, I add a straight line from the last midpoint to the last touch sample. If we build and run, then we'll see much nicer curves when I scribble furiously on the screen.

[Transcript missing]

The Event Handling Guide for iOS contains more wonderful pieces of knowledge and so do your heads, so you can help each other on the developer forums. The link is down below. There's one session that may be useful to you, and it's the session that just passed.

So maybe catch it on video. It's just called "What's New with Gestures?" But it's actually about Mac OS X, which has some new gesture stuff. And so if you're doing cross-platform things or you're an iOS developer who's maybe interested in the Mac, you might check out that video to learn about what's going on over there. Enjoy the rest of your week at WWDC. Thank you for coming.