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: wwdc2008-392
$eventId
ID of event: wwdc2008
$eventContentId
ID of session without event part: 392
$eventShortId
Shortened ID of event: wwdc08
$year
Year of session: 2008
$extension
Extension of original filename: m4v
$filenameAlmostEvery
Filename from "(Almost) Every..." gist: [2008] [Session 392] Cocoa Tips ...

WWDC08 • Session 392

Cocoa Tips and Tricks: Using Leopard Features Effectively

Essentials • 53:14

It's often a small quantity of code that uniquely solves a problem or turns a good application into a great one. Learn how the amazing features introduced in Leopard can improve your Cocoa application. Hear some of the latest tips and tricks directly from Cocoa framework engineers.

Speaker: Ken Ferry

Unlisted on Apple Developer site

Downloads from Apple

SD Video (650.2 MB)

Transcript

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

So here, because it's so simple, at least to begin with, there's not much in it. It has the draw rect, which does the drawing, and then it also has an accessor pair set up for storing and getting the color, which it wants to put up. So we're going to talk about both of these, at least briefly. I'm going to start with the drawing, even though really the accessors are where I'm going to spend more time, believe it or not. So let's look at this first. So is there anything interesting about this? Well, yeah, a little bit.

So for drawing, what are we doing? First, we're setting a color into the context. The way graphics works on Mac OS X is that there's a state machine, which is called the graphics context. You set attributes like the fill color or the stroke style or things like that, kinds of joins, and then you draw, and the drawing is always through a suspect to the current state of the machine. So the first line is setting a color into the machine, and then the second line is using it to fill a rectangle. And what's interesting, maybe the first thing is that, yes, it's a rect fill.

That part makes sense. But what's this compositing operation, composite source over? Well, let's talk about that briefly. So compositing operations are ways of combining drawing. So we talk about there being a destination and a source whenever we're doing new drawing. And what that refers to is that the source is whatever is new, the new pixels, and the destination is what was already there. And compositing operations tell you about how to combine them, what formula to use.

So the normal mode is called composite source over, and it's just exactly what you expect. It just looks like draw it, and it just looks completely normal. And that's going to be the main theme for this little tip is just normal is composite source over. That's what it means. Some of the more abnormal modes like this, this is the kind of thing you see if you use something else.

This is what you see if you see destination atop. Fairly obvious that while you can come up with cases, certainly, and interesting operations where you can make cases, you can also make cases where you can make a destination. So this is what you see if you see destination atop. Okay. So this is what you see if you see destination atop.

good use of these extra modes, for the most part, that's not what you want. For the most part, you always want to be using composite source over. So if you ever see a compositing operation argument and you don't know what to pass, you almost certainly want to pass source over. Okay, so take it to heart.

And one of the reasons I want to bring this up is because a somewhat attractive method that you might want to use instead of NSRectFill using operation is NSRectFill. It looks simpler, right? Well, it is, I suppose, but it very likely doesn't do what you want because it actually is the same as NSRectFill using operation, but it passes copy mode.

And copy mode is not source over. It's not normal. What does it do? Well, copy mode completely replaces what was already in the context, in the destination, with the new source pixels, including the opacity information. So what you see is something like this. Wherever your new drawing is transparent, the context actually becomes transparent rather than just showing through what was there.

[Transcript missing]

So let's just a quick, quick, quick rundown of what key value observing is for people who may not have used it too much. So it's a way of getting property change notifications. So when you hook up a controller layer, say, and you say, I'd like to observe the color property of some model object, you can receive a notification every time it changes, like here. And then when you receive this notification, you can just do whatever you want. So for example, you might update some view.

And then after that happens, control returns back to the caller. It's all synchronous. That's something that some people don't always realize. It's not like this happens later. All of the changes by observers are made before the method even returns. Okay, but now we want to actually use this to fix our problem. So what is it that we want to do? Well, we're going to keep a list of those keys, those properties, which when they change, we want to update our display.

[Transcript missing]

So the method where you receive those notifications that I was mentioning whenever a key value pair changes is observed value for key path. And what you want to do, often the implementation of this method is not quite right the way people look at it. In particular, you see how the first line here, what I'm doing is I'm checking if the context is one I recognize. If the context is color view needs display context, then I'm going ahead and invalidating the display. Otherwise, I'm calling super.

This is important, and this is something that people don't always get right, that the KVO context is a non-optional argument. It's the only way you can distinguish your observation from observations that somebody else is doing, which is unusual in Cocoa. Usually a context is optional, and it's usually just something you pass in and you receive back later that you can do whatever you want with it.

But here, the issue is that if NSObject, say, was observing some particular key path, and it happened to be the same key path you were observing, you need to not block those observations. You need to make sure that all of your superclasses are also getting a hold of the notifications they expect to be receiving, or else you might break the system. And the way that you need to do that is this. This is the way to do it, is you check the context argument. So I suggest you pass that in.

Okay. So something that makes this a little bit easier is that Xcode these days, I think just in 3.1, if you type the beginning of the... the word observe out in the gutter right where you would want to implement the observe value for key path method, well, it'll actually complete it, and it'll give you the right form.

And then if you fill it in, then you'll be good. So that's nice. And... So that's what our observe value for keypath method looks like. What we do when we notify, we also need to actually set up the observances. That's what this would look like. Let's just go straight down the page and look at it.

First is just a little aside a little bit. So what is this pound pragma mark line? This is a way of just organizing your source code and Xcode. What it does is that in the function pop-up, you'll see a bold separator, which is nice. It really helps you to see what's going on. It really helps when you've just got it, especially in large classes where you've got a lot of methods. Okay, but moving on. Okay, so now we have this context argument that I was talking about before. We just made it a static NSString global.

There's just a single one of it, and it has a unique name. It has something, well, unique in that it's unlikely anybody else in the system would try to use the same context for anything because we called it, we prefixed it with our class name. So we wrote it color view needs display context. Seems reasonable.

Okay, then when it comes time to actually set up the start of the system, we would call self at observer key path with the property and the context is the needs display context. And you see here I'm calling a method to return the list of properties, which I want to invalidate display, which I've called more or less key paths for values affecting display. However, I gave it a prefix. Why did I give it a prefix? Well, it's too generic.

The problem is that this is a sufficiently useful method, I think, that, for example, AppKit might actually end up supplying it for you someday, possibly. So, and it would be bad if you defined a method that did something like this, and we defined a method that did something like this, but they actually had slightly different semantics.

Or if we wanted you to call super or something like that, it could end up breaking your application. So the suggestion is when you're implementing something that's really generic, that's not specific to your application, that you try to do some prefixing or something to keep it out of our namespace.

Or to keep us from, excuse me, I shouldn't call it our namespace. To keep it from conflicting. So a good example of that is, when the setHidden method was added, I don't remember if it was either 10.3 or 10.4, that really caused a lot of trouble, because it turned out a lot of people had tried to define setHidden on its view, which did something, wasn't quite the same thing as ours. So there was some hacking actually necessary to make sure that we didn't break people. Okay, I don't want to get too caught up on that.

So that's why we do our setup. We also need to do teardown, of course. That's pretty straightforward. You just remove the observer, which is self in this case. Okay, that's great. However, we haven't actually called them anywhere. Those were just convenience methods I set up. Where might we want to call them? Well, we can call them in init.

That's a possibility. So because this is a view class, I'm overriding init with frame, and I'm overriding init with coder to do the setup. So that whenever my properties change for the entire lifetime of the object, the setNeedsDisplay call will happen. So why particularly these two methods? Why did I do it in init with coder and init with frame? Why didn't I override init, for example? Well, these are the designated initializers. And this is something that people sort of know about, but don't seem to quite have the right idea on.

So with designated initializers, first of all, a class has not one designated initializer, but any number of designated initializers. And the contract that they adhere to is that every object that gets initialized is going to go through one and only one of the designated initializers at each level of the class hierarchy. Okay? Which means that if you want to To do initialization, you need to override all of the designated initializers of your superclass.

Here's an issue that people run into and why this fixes it. Interface builder will create objects in a variety of different ways. Under some circumstances it will use a NIT with frame. Under some circumstances it will use a NIT with coder. Under some circumstances it will use a NIT.

These situations are actually documented and you can learn them if you want. You know, I mean, I suppose it's always good to know things. But if you override all of your designated initializers, it doesn't matter how the object was created because it's always going to go through one of them.

That's the contract. So I suggest you do that. And this pops up on the mailing list sometimes. Okay. We also need to... This is a little bit of a difference from the NS Notification Center. You may know that if you're using NS Notification Center notifications, you do not need to unregister yourself and finalize. It will sort of happen anyway. That's not true with KVO. You need to do it explicitly.

Okay, but then there's something else I want to do here, which is that you may have heard that it's not really a good idea to have a lot of code in DLAC or especially in finalize. And that's true for a variety of reasons. But let me get to those in a minute.

For now, I want to say from almost from a performance point of view, I mean, display doesn't really make a lot of sense unless you're actually in a window. So instead of doing it with DLAC, something we can do is we can implement the method viewWillMoveToWindow, which is a method of NS View. It's very useful. And we can just say, well, we -- so we're being told that we're moving to a window.

We're going into a window that exists if the new window is non-nil. We're already in a window if when we call self-window, we get something non-nil. And we only need observations when we're in a window. So if we're currently not in a window and we're going into one, then we need to set up the observation. And if we're coming out of a window, we need to tear it down. And this is nice because, yes, we do get to remove the Niala code.

But it's also, like I said, good because we're not going to have to do it again. And we're going to be able to do it again. And we're going to be able to do it again. And we're going to be able to do it again. And we're going to be able to do it again. And we're going to be able to do it again. And we're going to be able to do it again.

Because DLAC is -- DLAC and especially Finalize are --

[Transcript missing]

So up at the top, we have the part of the code that I was claiming that we care about. So the draw rect, the key paths for values affecting display, and then the synthesize attribute. Then we have some stuff that I'm skipping because I'm going to come back and do that in a minute. And then we have the infrastructure part. So we have the declaration of the new context.

We have the part where we set up the auto display. So actually, let's just count them first. So it's just four methods, and all of them are pretty short. None of them are more than six lines of code. And they're really only six lines because the spacing on the screen is so funny. So we set up the observation.

We tear down the observation with removeObserver, and then when the view's moving to the window, we want to do the setup sometimes, tear down sometimes, and then we have to actually implement the method that gets called when the values change, check the context, and actually invalidate the display. Okay, so that's that. And that should work, so I'm going to run it. Gorgeous, eh? Woo. Okay.

We didn't save a whole lot here, right? Because, I mean, we didn't have to implement an accessor. Instead, we implemented four methods. That doesn't seem like a huge win. But the win... The nice thing is that they're totally generic. So now if we want to change things, we can do that pretty quickly. So I'm just going to comment this out.

Let's say we wanted to draw a gradient in the window instead, okay? So we would need to, instead of having just a single color, we want to have a top color for the gradient, a bottom color for the gradient. Let's put a title in the middle of the thing, too. Then I don't want that attribute. Excuse me.

I want these three instead. I'm going to comment out this chunk of code and instead I'm going to say okay I want to synthesize those three new properties. Excuse me, synthesize. And I want to say that whenever any of them changes I would like my display to be invalidated. So there they are just written in a list.

And then I want to actually draw my drawing, write my drawing code. And this is longer but the whole point of this is that, I mean this is what the view is supposed to do. This is the part that I hope you're somewhat interested to actually write because this is what you're doing. I mean we're getting the bounding rectangle for the view. We're creating a gradient using the top color and the bottom color.

[Transcript missing]

And then we're going to center it more or less in the view, but because the centering can cause it to be non pixel aligned, we're going to use this method centerScanRect, which will align the box that you've just made to the two pixels in the view.

And this is particularly interesting because it works well at high resolution. If you're aware that we're trying to get things working for resolution independence so that we can run our displays at higher scale factors so things look just take more pixels on the screen. So if you're used to just rounding and saying, if you want to make things integral and make them look snapped in the right place on the screen, that's not going to work right at high DPI, but centerScanRect will.

So and it's actually not really necessary for text. In fact, it's probably better if you don't do it, but I'm just demonstrating. So that's that. So we can do all that. We run it. And again, now we find that we have a gradient. The top and the bottom color can be changed. So we can do all that. We run it. And again, now we find that we have a gradient. The top and the bottom color can be changed.

[Transcript missing]

Last, I did just want to point out center-scan-rect because I don't think it's very well known. It's been in the operating system since 10.0, but nobody's had a reason to use it until recently. So, you might want to take a look at that. Okay, that is most of what I have to say about that, but I would like to take a little moment here to talk about some garbage collection issues. So, in particular, when we were using that KVO context, the context is typed void star, all right? And what we passed in was not a void star, but it was a string.

It was a static string. So, this is going to have some repercussions because our garbage collector does not scan through all of memory and keep every single object alive. That would be a conservative collector. What ours does is that it knows how things are laid out, and it only looks for possible objects within variables that are typed to be object type.

So, when you have this void star, if you're passing something in there, our system is not going to know that that's an object if it is an object, and we're not going to keep it alive, which makes it unsafe to pass objects there unless you take extra care to make sure that it is safe.

So, what are the various different ways you can make it safe? Well, you can use something that you're pretty sure is not going to go away. So, in the case of the static and a string, there are two reasons why it's not going to go away. First of all, it's stored in a static global variable.

That's enough. Another reason it's not going to go away is because it's not going to be stored in a static global variable. The other reason it's not going to go away is because it's just constant. I mean, at quoted strings, unless you're doing something fancy with bundle unloading, never go away.

One thing people sometimes try to do is they want to pass self-class as the context. I suggest you not do that. That is too generic. It's important when you choose the string to choose something that nobody else might try to use as an observation context. And your class maybe doesn't satisfy that property. And also, even if you do self-class, that's not getting your class, that's getting whatever class is currently active. I mean, it might be a subclass, basically, if somebody has subclassed your code.

So that's not very useful. Another thing you can do, and this is the part that I really want to talk about, is you can CF retain it. Because under garbage collection, what CF retain means is it means, hey, garbage collector, something outside of the scope of the things that you know about is using this object. So you cannot destroy it. It's a hard retain.

What I want to say about this is that it is harder to get right than it looks. First of all, something which you may or may not realize is that when you're not running with garbage collection, cf.retain, which is the core_foundation.retain function, is absolutely interchangeable with the retain message in foundation. To the point where you can take any cf object, say a ct font-raft, a core text font descriptor, which is not bridged to any Objective-C type, you can auto-release that if it's useful.

Because it will eventually end up sending out the release message, and the release message is equivalent to the cf.release function. So they're completely the same thing when you're not running with garbage collection. However, when you are running with garbage collection, like I said, they are different. You can't mix them. You need to keep them balanced independently. And it has subtleties. So suppose you have something like this. Suppose you have a little object network of objects referencing each other. Well, under garbage collection, this is fine.

If nobody else is referencing the little network, then it will be seen to be unreachable, and all of the objects will eventually be destroyed. Now, suppose that you replace one of those references that you think of as being, you know, I'm using this thing, with a cf.retain. Well, suddenly you have a problem.

This is not going to work. This section of the graph is going to leak. And a good way to think of this is that the cf.retain, it's not directed. The system does not know that it's this thing, cf.retaining, this other object. It's just this global reference that says keep it alive.

And so you're in this kind of situation where the collector is not going to be able to tell that the object needs to be destroyed. And in particular, cf.release and finalize, that's when it's going to get you into this kind of problem. Because finalize is never going to run because the collector never collects your object.

So when is it okay? It's not like you can't use it. It's okay if you're pretty sure the cf.release is going to happen. So if the cf.release is not in finalize. So, for example, if you have a block of code and at the top you cf.retain it, some object, then you pass the object off somewhere in code that you feel like is not necessarily going to keep it alive or whatever, I don't know.

And then the bottom of the method you cf.release it, that's fine because you're sure that cf.release is going to run. It's also okay if you can be sure that the cf.release is going to run. It's also okay if you can be sure that the cf.release is going to run. sure that there aren't going to be any cycles created, meaning that if the object that you're CF retaining does not have any references back up into arbitrary parts of your object graph.

If it does, you're really asking for trouble. And leaks in garbage collection are kind of bad because they tend to leak really large parts of the application graph because almost everything is a strong reference. So it is going to cause leaks if you have your CF retained and finalized.

And if the CF retained object is too complicated to understand, if you don't know what it is, if people are passing arbitrary objects, you really don't want to be CF retaining them and CF releasing them and finalize. Okay. Let's go back and just, that's all I have to say about garbage collection, but let's crystallize what we said. So first of all, a static and a string that you use a fairly, you know, you use something that's specific to your situation makes a good KVO context. I suggest you do that. Secondly, CF retain.

[Transcript missing]

happens in a more determined order. If you have some tree of objects that are being deallocated, you can be sure that the root of the tree will be deallocated before the leaves if it's releasing the leaves as it goes. With garbage detection, that's not true. The whole tree is found to be on reference at the same time and they'll get finalized in some arbitrary order.

And you shouldn't be trying to message those other objects that are being finalized at that point. And particularly, if you accidentally store one and two, you'll start getting resurrection errors. If you see in your code that things are complaining about being resurrected, it's very likely because you've got code and finalized that's too complicated.

Okay, that's enough. So we talked about key value observing with this color property. Now I'd actually like to go back and talk about making it bindable. Okay, so what do I mean by this? So what's bindings again? Bindings is a way of establishing a two-directional connection between say, a view and a model, okay? And what you do is you say, I'd like to bind some property of the view to the model.

And what it does is yes, same as before, the view is going to observe the model. And whenever the model changes, the view is going to get updated. But instead of receiving a notification of the update, that's, it's true, it does receive a notification. But do you think of bindings as being from the outside? It actually does something with the notification. In this case, it would update its view, okay? So you see we're almost there, right? But then you also think of bindings as being a two-way connection.

So that if the user comes along and changes the color in the view, which in our code is not actually possible, because we haven't implemented anything for that, then it gets pushed down, oh, excuse me, The two-directional connection that uses KVO. In our case, it's fairly clear that it would be nice if this color property were bindable. What's going to be involved in implementing a custom binding for this object? It turns out that nothing is involved. This is already bindable. We've already written all the code we have to write.

So that's worth noting because custom bindings are sometimes thought of as being difficult. And there are cases where they're difficult, but there are also cases where they aren't. And during this talk, I'm going to discuss the easy case, and I'm also going to discuss one step up from the easy case. And show that really writing custom bindings is not such a big deal.

But first, so let's just see how you end up using the binding. Okay, well this is what we might do. We might take our color view, we'd bind the color binding, that's what we define, to the user defaults controller. And then we're going to use the key path, which is the key path that's specific to the way user defaults works.

And then we have a transformer, keyed on our archive from data transformer name that we're passing as an option for the binding. What does that mean? Well, the only object you can store in defaults are of six different P list types, I think. So you can store a number, array, dictionary, date, and data.

And that's it, NSData. But, and a color is not any of those, so you cannot directly store a color in the preferences database. However, it's fairly easy to transform a color to a data using the fact that it conforms to the coding protocol. So if you put it through a keyed archiver, what this code is doing is it says every time you need to update the model from the view, first run it through the keyed archiver to make data, and every time you're pulling data out of defaults, run it through the keyed unarchiver to produce the actual color.

Okay, great. Oh, right, so I was saying that some people think of implementing custom bindings as being difficult. Well, the converse of that is that some people before they looked at it very much also think of custom bindings as being extremely easy. Because they think it works like this. They think that you should make your view property observable, you should make your model property observable, and you're done. And that's not true.

So the thing to understand is that bindings are actually asymmetric. The view observes the model, that's true, and whenever the model changes, the view gets this notification and it updates appropriately. It gets observed value for keypath. But the other direction, that's not how it works. When the view needs to update the model, it just does it. It directly messages. I haven't used an observing line here in the direction from the view to the model. I've used a hard line to indicate that it just has this thing, it just uses it directly.

So what this means, however, is that the easy case, the reason why we fell into this before, is that we didn't have any need to update the model from the view. Data was never flowing in that direction. It was always flowing in the direction from the model to the view, which meant the KVO stuff was just working. And on top of that, it was working because of NSObject's implementation of the bind value. Which I'm going to go through.

So NSObject implements bind. What it does is that in the bind method, it starts observing the model. Okay, fine. And it passes its own context. No, we're going to keep that in the back of your head. Okay. Then it implements observed value for keypath. And when it gets the context that it expects, it dispatches another message to itself, which is set value for key with the value that got pushed up from the model.

Okay. And what does it use as the keypath? It uses the binding. So that means that when under under certain conditions, that means that's enough. Whenever the data is always flowing from the model to the view and you've made a binding whose name exactly matches a property in your view, then you're done.

Okay, and I did want to point out, of course, this means NSObject really is doing some observation of almost anything. And if you don't check your contexts in the observed value for keypath method, then you're going to block it and it's going to stop working. So be sure to call super and observe value for keypath if you don't recognize the context. Then the harder case is when you do sometimes need to update the model using the view.

We can handle that too. Here's our plan for doing that. First of all, we have to handle both directions of data flow. But let's let NSObject handle the data flow whenever it's coming from the model to the view. What that means is that we're either not going to override bind or we're going to call super.

[Transcript missing]

The way this works, sometimes you think of bindings as aliasing two properties. You have some property in the view and you've got a property in the model and you think the bindings makes them the same. That's not really true. It's more like a default implementation of a delegate. And if you think about the way delegates work or target action or any of that, it's not that they're in sync.

It's that sometimes the view says, you know, this is a good time to update the model. Or with the text system, it's not like you get every single character every time somebody types or and you don't get because you wouldn't want it because you only want the text when the user is actually done.

Bindings works the same way. It keeps its own copy and it just updates the model at appropriate times. It's just exactly like a delegate. Okay, that said, so let's, as an example for a custom binding, let's use Sketch. And could I have the demo machine, please? Thank you.

Okay, so Sketch is an application that is, as an example, it ships in developer/applications whenever you've installed the developer tools. And it is a little drawing program. So I'm going to show it off to start. So you can make circles, you can make big rectangles, you can color them, salmon, evidently.

But then what I want to talk about, that might be a bug or something, I don't know. What I wanted to mention is that it has a rather nice class in it, which I'm going to talk about, which is this scroll view here, okay? You see the pop-up down in the corner, it has a zoom property. So you can zoom in on the thing. And it uses bindings to do the zoom property. And it falls into the easy case, actually, because the scroll view, there's no way to update the zoom from the scroll view itself.

There's just also this pop-up button, which can control it. But the pop-up button pushes data down to the model. The model, and then the data pops back up into the view and actually updates the scroll. Okay, that's great. That's how that already works. I was saying that's the easy case. Why, however, am I saying that we're going to do the hard case in Sketch? Well, something that might be nice with this is that in 10.6, we have support for gesture events. In Mac OS X, the same as you have on the phone.

So if you have a new model MacBook Pro or MacBook Air, such as the one that I'm using right now, then if you make a pinch gesture on your trackpad, then you can magnify or back away from whatever you're looking at. So it would be really nice if our scroll view had that ability.

But when we add that ability, it's going to knock us into the hard case because we're going to have to start having to, we're going to need to be able to push data from the view into the model. So that's what we're going to do. First, let's just take a look at what the code looks like for now for the existing binding.

[Transcript missing]

What it's doing here is it's grabbing the, it's taking the, say somebody tries to bind this property, which is a scroll view zoom factor, okay? Then it binds the pop-up button, and then it also calls super. So if I could switch back to the slides for a minute, I'd like to diagram what's going on.

Thank you. Okay, so here's what happens. So when somebody comes along and says, scroll view, please bind your factor to the model. The scroll view has this internal pop-up instance variable, basically, that's opaque. The model doesn't know about it. Okay, so you would think that that means that the scroll view, it does need to sometimes push data down to the model because the user can manipulate the pop-up button.

But what it does is that rather than directly messaging its pop-up, when somebody asks it to bind, it first tells its internal pop-up variable to bind to the model, and then it goes ahead and calls super, which means it's taking advantage of the NS object level implementation of bind in addition to having bound the pop-up button so that whenever the model changes, both the pop-up and the scroll view are going to update. Ken Ferrry This is a little bit unusual. It actually means that the scroll view is going to update. So the scroll view is bindings only. You can't directly tell the scroll view to change its zoom factor because it would not update its pop-up.

[Transcript missing]

Okay, great. So that's that. So like I said, what this method does is it really just creates the pop-up button if it isn't already there. It binds the pop-up button, and then it just calls super unconditionally. And that's how that works. Okay, great. So now let's go ahead and actually implement our gesture support. So I have my pop-up that I did with PagmaMark for event handling. The reason there wasn't anything there is because it was commented out.

So let's go ahead and uncomment it now. What happens is that when the user makes a pinch gesture or something that's supposed to be able to change something like a zooming on your view, and its application and its window will dispatch this method to you, which is magnify with event. It's very easy to use. And what you can do is you can pull the magnification out of the event, which is a change in percent.

So if you get something like 0.25 as the magnification, that means... Increase your zoom factor by 0.25. So if you're currently at 100% and you get that, then you want to go up to 125%. If you get minus 0.25 and you're at 100%, you want to go down to 75%. This, we luck out, and it's exactly how Sketch models it in the first place. There's a factor instance variable, which is the zoom, which is the scaling. And what we want to do with the magnification is we just want to add it.

So if you're at 1, it goes up to 1.25 or down to 0.75. However... We do have to cap it because the app kit is not smart enough to know, hey, this is already zoomed in all the way. You can't go any further. I mean, it might start sending you minus 0.25 events when you're already down to 0.

So I have a method up here, which just... caps the scaling to clamps it to some reasonable values. So it has both a min and a max, and it makes sure that we don't exceed them. Okay, so then we have our new factor, and then we need to set it.

Now, because of the way the scroll view works, like I said, because it's actually bindings only, what we can do is we can just update our model and let those changes bounce back into both the scroll view and the pop-up button. And that's what I'm going to do in this method.

Set value for binding, which I just made up myself, and I probably should have given a prefix because that's too generic. So don't do that. Okay, so how does it work? So you grab the binding info for this binding, whatever it is. Okay, if it's not nil, that means you're bound. If it's nil, something weird has happened, and we just want to ignore that. Actually, I guess it might not be weird.

It might just mean you're not bound. We'll pull out the observer, and we'll just say, okay, you asked for the observed object using this documented key. You asked the binding info for, this is just a dictionary. You ask it for its observed object. Okay, great. You ask it for the observed key path. Fine. All right, and then we just need to update it.

So we will say observed object set value for key path. That's going to update the model, and that's all it takes. And then that will, because of the KVO observation we're doing, will always bounce back into both our scroll view and the pop-up button. So let's just see it.

We're actually done. So if I make this guy, and I make it some nice color, and you'll have to take my word for it that I'm actually going to start using the fancy gesture events, but there they are. So it works really nicely. Okay. And that's it. So, yeah. Pretty easy. I hope that that made sense.

I don't know. Okay, the one thing that I did want to mention is that there are a number of options that people use when they're doing binding. I, in fact, already showed one. I showed using the value transformer name option to say what transformations we want it done. And what I've just, when you're pushing things from the view to the model.

And I haven't done that. So I haven't accommodated any of those options. And the reason I've done that, I haven't done it, is because it's too hard. Because there are a lot of options. There's a value transformer. There's a value transformer name. There's any number of things. You have to handle them all individually.

And there's no way to expand it in the future. So really what I'm saying is that, again, this is a case where just look at what you, you don't need to handle everything completely generically all the time. Just do what your code needs to do. And because we don't need any fancy value transformers at the moment, I'm just not doing them.

And I'll do them as necessary. And hopefully maybe AppKit can try to, in the future, do something that is sufficiently generic that would be able to handle all the options for you. But it's not there yet. So this is probably what you ought to do. Could I have the slides back, please?

[Transcript missing]