Application • 48:46
This session will explain in great detail how to move your existing resource-based user interface to Nib files. We discuss how Interface Builder can be used to build a great Aqua user interface and go in-depth on how to modernize your Carbon code by switching to HIView, using the Carbon standard event handler, and using Carbon events to simplify your existing code.
Speakers: Curt Rothert, Bryan Prusha
Unlisted on Apple Developer site
Transcript
This transcript was generated using Whisper, it has known transcription errors. We are working on an improved version.
Thank you guys for coming out to learn about replacing your old style Windows and Dialogs with NiB files. Now, what are we talking about when we're talking about old style Dialogs? Well, specifically, this refers to using the Dialog Manager and the Resource Manager to pull in your Dialogs, and then using APIs like Modal Dialog to go into a modal situation and handle all the dispatching yourself.
So some of the things I hope you learned today is how to move your existing resource-based user interface up to NiBs by getting them into Interface Builder. Then we'll talk about getting into simplifying your event handling using the new technologies that are available in the high-level toolbox. I want you also to start using the standard window handler if you're not already. And this is so the toolbox can provide standard behaviors for you and eliminate a lot of code that you need to take care of. And also, you'll learn why you'll need to adopt HIView.
The steps of getting your user interface up to a modern-- into a NiB and using the modern technology in the toolbox are pretty simple. First, you need to import your dialogs into NIBs. And you can do this using an importer that's built into Interface Builder. Now, I specifically talk about getting it in from your resource files, but the same techniques will apply after that, regardless of how you built your interface using a flattened interface format.
Then at runtime, you'll actually need to load in that interface and display it on the screen for the user. Then you need to rewrite how you handle events, because the old way of handling events using modal dialog isn't going to work once you import it up to Nibs.
And then if you're using modality because you're putting up a dialog that needs to interact with a user in a modal situation, there's a different way of dealing with modality that I'll talk about as well. And then finally, if you have custom content that's being drawn in your dialog, we're going to encourage you to start using HIViews, specifically for performance. If you switch into compositing mode, this has to be done.
Why would you want to convert to Nibs? Why do you want to make this transition? The single biggest reason is because we're doing a lot of work in the toolbox to improve performance, give you new features like new widgets that are now available, and the Dialog Manager and using all the resources to build your interface, it just won't cut it.
Localization is also easier. If you've built your interface with resources, generally how you ship those is by packaging the resource with the application. And when you need to localize, you need to build a separate version of your application to deploy. Using NIBs, and actually specifically using application bundles, this is so much easier because you can localize your application in the NIB, and then you can deploy one binary or package across from different localizations.
Maintenance of building your interface and modifying it is so much easier when you're using NIBs. If you're using the Dialog Manager right now, you're probably aware of all the problems if you need to delete an item that's in your dialog or add an item, and you have to keep the dialog item list in sync with the numbers that your code thinks that those dialog items are. And that can just be a nightmare.
Also, we're adding new features in the toolbox. And as we do this, these features will be added to Interface Builder, and you can just use them in your interface. Building your interface with IB is so much easier, also. We have Aqua interface guidelines that are built in. So previously, when you were building your interface, you would have to look at the HI guidelines, figure out exactly how many pixels the button needs to be from this other button and to the edge of the window.
Well, all of this is built into Interface Builder. And I'll give you a demonstration of how you can do that. You can also test your interface immediately. And then once you get your interface loaded and start handling events, it's just so much easier. And I'm sure you'll agree.
Some of the things that Guy mentioned in the last session was about compositing mode. Now, this is a huge win in terms of performance. Compositing mode is based on invalidation model, so you're not doing unnecessary drawing and redrawing controls more often than necessary. It also allows overlapping of views, and if you saw the demo before the machine crashed in the last session, you were able to see all these views that were rotating. Some you could actually see through because they were translucent. And it just gives--it's a better experience for the user, and it also gives better performance.
So the first step of getting up into the modern toolbox is to get your interface into Interface Builder. You can do this one of two ways. If you have a resource-based user interface, you can import that, like I mentioned, in Interface Builder from the Import menu. Or you could go about building your interface manually. I'll talk about why you might want to do that in a moment. But if you need to import your resource-based interface, it's just really easy. From the File menu-- You select to import your resource file. You select which objects to import.
with the dialog that pops up. Once you select which objects to import, you hit it, and then Interface Builder will create a new NiB and pull in your interface. It's also really nice because if you have controls that are embedded in your dialogs, this will all just be pulled in automatically by the importer.
Alternatively, you can build the interface manually. And you might want to do this because your interface was perhaps poorly designed, or maybe it didn't quite follow the Aqua interface guidelines, and you want to start taking advantage of the guides that are in Interface Builder. Or maybe you just want to start taking advantage and adopting new widgets that are available right now.
Either method you choose, once it's in Interface Builder, the steps are the same. You can bring up the Inspector window and start editing control properties. You can also take advantage of a technology called HIView Layouts. And this means that you can set layouts in the sense of how they are bound to its, like for a view, how it's bound to its parent. So as you resize a window, these can all be handled by reflowing the contents of your window. This can all be handled using HIView Layouts, and you don't have to write any code to do this.
Also, in the interface, you can assign HIView IDs, and that's so you can access your controls, and HI commands, which are going to be sent when certain actions are performed in your interface. Also make sure, once your interface is in Interface Builder, that you have turned on the standard window handler.
Now what this does is it allows the toolbox to take care of all of the mundane stuff, the stuff that everybody has to do, like resizing the window or dragging the window around. And it will handle control tracking, like when the user clicks in the window, it will handle what controls are tracked and then let you know that something interesting has happened. And then you can also test the interface and make sure that everything works out the way you want it to work.
So that step is fairly easy. Just get it into Interface Builder, save out that NiB file. Now, at runtime, how do you actually load that interface? There's just really easy steps. You have to first get a reference to the NiB. Then if you have a menu bar that's in the NiB, you just go ahead and you load that in and set that for your application.
You also extract the windows by just using a very simple API to get your window out. Now this will pull in all of the controls that are associated with that window and instantiate them at that time as well. And finally, after you get the information out of that Nib, you release the reference to the Nib and you're done.
And you do this using these APIs. To create a NiB reference, you just call the API, createNibReference, and you pass in the name of your NiB minus the .Nib extension. If you have a menu bar in there and you want to set that menu bar for your application, you call the API, setMenubarFromNib.
Now I want to pull into one of my windows. So I call createWindow from NiB, passing in the name of my window. This window is going to be pulled in from your NiB, but keep in mind that for performance reasons, because maybe you need to pre-flight the dialog, you know, add some information into some text fields, for instance, the window is initially invisible. So if you want it to show, you have to call showWindow.
Finally, when you're done, just use the API DisposeNibReference. You don't need to cache this NiB reference for later use. In fact, it's just better. When you're done using it, just dispose it. If you need to use it again, go ahead and create another NiB reference to that NiB.
So once you have your interface loaded, how do I actually access the items that are in there? This too is just really easy using Interface Builder. If you select on a control or any item in your interface in Interface Builder, you can bring up the Inspector window and go look at the control segment of it.
And you can assign a control ID or an HIView ID to that control. Now a control ID is a signature, which is four character code. And keep in mind with four character codes, all lower cases reserved for Apple use, and all mixed and upper case, you can use as much as you want. And then also an ID. So you can use signatures to represent all of one type of control and an ID for each individual control in that segment of your interface, for instance.
And then at runtime, programmatically, you would use the API HIView find by ID. And that's so you can actually find that control in the interface and grab a reference to the control. In code, this is how you do it. You define an HIView ID, which represent the signature and ID that you set in Interface Builder.
You call the HIView findByID API, and it returns your control reference to you, and you can just go to town, do whatever you need to do with that control. So let's go take a quick demo, and I'll show you those initial steps of importing your interface and then loading that interface into your application.
Before I get started, I have to give you a standard disclaimer. I am not a user interface designer at all. I just sit at my computer and I punch numbers in to make this stuff work. So while what you see may amaze you and astound you of how brilliant it is, I really highly recommend you don't design your interface like this. So let's say that I wanted to have a really fast music editing application. So I just whip something up really fast in Carbon. And it looks something like this.
Okay, now, actually, I mean, this is just a mock-up. It doesn't really do anything. I mean, the only thing it does really well is not get the spinning cursor. But if I want to get information about a song that I've created, I go down to my Song Info, and I bring up a dialog.
Now this dialog is loaded in from a resource, and then it's using modal dialog to actually do some information. So any song info palette wouldn't be complete without having a really cool equalizer view in it, so you can see the equalizer. So at this point, I go here. Now this is my user interface skills, like, behold, Quick Draw rectangles.
Don't do this in your interface, it's just a demo. But you can choose different equalizer settings. Oh, it's so beautiful, so cool. And because last time you blew out your dad's speakers, you want to be able to clip the sound at certain areas, so we go track when you want to, you know, how loud the volume can get before it starts clipping the music off. I don't know if this is how real music programs work, it just makes a really good demo.
So while the interface may look simple, clicking around does some things, there's actually a lot of code going on behind the scenes to actually get this up and running. And then your boss comes in and says, I need you to create some new summary information in there, like a text field that you can type as much as you want in. And you're thinking, man, using the Dialog Manager, how am I going to do that? This might be a great opportunity to start learning about NIBs.
So let's actually go look at the code, see what I'm doing in here. This is a big wait-next-event-based application. And I just want to prove to you that you can do all of these steps in piecemeal. You don't have to convert all of your application at once. You can do one dialog at a time. So I'm going to maintain this as a big wait-next-event-based application.
When I create that dialog-- so you can see there's a lot of code that's being run here-- I'm using the Resource Manager to get a new dialog, I set up the dialog, and when I go do that, I actually-- I took that area where I was doing the custom drawing, that's just a user pane. So I'm installing a user pane draw proc, so when the dialog needs to repaint that area, it calls my draw proc, which is where I'm doing my custom drawing.
Then after it's set up, I call modal dialog. Now I pass in a modal filter proc because I actually want to handle behavior in that custom area as well, and that was to modify that clipping thing. Then when ModoDialog returns, I'm switching off of the item that was returned in order to figure out how to modify the interface. So there's a lot of code going on in here, and I want to eliminate that, especially since I have to add new features because my boss told me to. So let's learn how to do that.
In Interface Builder, we go to the File menu, Import, Import Resource File. We select our resource. It asks which dialogs and menus do I want to pull in. For this example, I only have one dialog, so I only want to import that single dialog. Hit Import. Creates a new nib, slams that window into there. Let's go look at that interface now.
So you can see it did a very good job. That's exactly what the interface looked like. Now we have to do some triage. Maybe there's some things that didn't import correctly. In my case, when I set up the resource, I had to go through some hoops to actually get that pop-up button in there.
And Interface Builder displays this as a custom control. So I could go in and rebuild that by creating a new pop-up in here, but I've already done that in a separate NiB to keep this demo kind of rolling along. So I'm just going to put that in here.
I also decide that I want to pre-populate this stuff so I don't have to do this in code. So I can do all of this within Interface Builder by setting this stuff up. I also encode, initially, when I was setting up the dialog, I'm calling setDialogDefaultItem and setDialogCancelItem in order so the dialog manager knows which items are the default and cancel items.
Well, I can do that with an Interface Builder by selecting just a button in this little interface inspector window. This is going to be the cancel button. I want to make sure that I have the standard handler on, because this is what's going to handle all of my low-level events for me for free.
And then I want to start mucking around and see that you can see these interface guides. Like, you can see that my window was probably laid out incorrectly in the first place. So you can see that Interface Builder presents these guides, tells you how far away the controls need to be from each other. Some new features I want to take advantage of, perhaps, are to make the window resizable. So I click that.
[Transcript missing]
Binding to the right and the bottom.
Doing the same for this view, the right and the bottom. Binding to the right and the bottom. Doing the same for this view, the right and the bottom. Binding to the right and the bottom. Doing the same for this view, the right and the bottom. And just so it's not confusing, I'm going to add a text field here that says Notes.
We can run the interface-- we can test the interface with the Interface Builder by just allowing it to run. It brings up a simulator. You can see that there's the window. I can go ahead and type. Now, I've done no code yet. This is just interface construction. One other thing I recall that I want to do is these text fields are using the old Edit Text Control, and I want to get some really cool anti-aliasing, so I want to use the Edit Unicode Text Control, which is just a checkbox over here.
And finally, I decide that I want to save this. This is my interface. I need to save it out. So I hit Save. Give it a name, where to save it. And the integration between Interface Builder and Xcode is excellent. It knows that I have a project up already and that I want to embed that NiB in that project. So I go ahead and add it. Now, I need to actually write some code to drive this interface. Now, in my main code, I was pulling up that, that dialog. Now, in my main code, I was pulling up that, that dialog.
Now, when I say new way, I say new, but I don't mean it's like a lot of work to do. It's very simple. I create a NiB reference to my NiB. I extract the window from that NiB. I show it, dispose the NiB reference, and we're done. Let's cross our fingers and make sure this builds. There we go. We have my same familiar interface. I want to bring up my song info.
And boom. There it is. The interface works. I wrote no code to actually initialize these text fields. All the behavior works. I can drag these things around. You can see that my pop-up is here and does correct tracking. And I've written no code. So let's move back to the slides and move on.
I can drag these things around. You can see that my pop-up is here and does correct tracking. And I've written no code. So let's move back to the slides and move on. Make sure that the standard window handler is on. That allows you to resize your window, move the window around without writing any code to do that work.
Make sure that the standard window handler is on. That allows you to resize your window, move the window around without writing any code to do that work. Now it's not, I mean it's kind of brain dead. There's only seven APIs in there, and three of them are associated with NiB management. So there's only four APIs getting your interface out of there.
So we had an interface that was up and running, but it didn't actually do anything interesting. So it doesn't do exactly what the interface was doing when it was dialogue manager based, because I had a whole bunch of code that was behind that running it. So now we need to start handling events.
Now the old way of handling events, whether using modal dialog or wait-next event, is using this model. You, the application, go sit in an event loop and start pulling events out of the queue. Then you figure out where that event needs to go, so you start dispatching it. Was it in a window? By calling findWindow. If it was in the window, is it in the control? You call findControl. And then you start tracking the control if you need to. And then you tell, finally, tell the toolbox what it is that you did.
Now, this is cumbersome because you're writing a lot of boilerplate code. Everybody's got to do it. It seems like a really nice place that this gets implemented in the toolbox and let us do the work. So this is the new model of doing events. And when I say new, I mean, like, Carbon event forward.
You tell the toolbox what events you're interested in hearing about. The toolbox sits in its own event loop, figures out where these things need to go, and then dispatches them to you if you're interested in it. So it makes the model so much easier. You're not doing all of this extra boilerplate code that you don't need to, and you get notified only about the events that you're interested in.
So events are separated into different classes and kinds. There's about a dozen or so different classes of types of events, like control, window, command events. And then they're split into different kinds. So what kind of event that class referred to. I hope that made sense. So for example, in the case when you need to be notified when the enabled state of your control has changed, you register for a K event class control, K event control enabled state changed event.
So it's interesting, but how do you actually go about doing that? Like I mentioned earlier, you specify the events that you're interested in. Then you install a handler on an event target. Now, an event target is where these events are going to be sent to. And there's various targets, like there's control targets, user focus, window, application. And when I say installing a handler, what you're really doing at that point is telling the toolbox, I'm interested in these events. Call my callback when these events come in.
So then you go implement the handler. So when your callback is called, what do you need to happen to your interface at that point? And then events can go through different propagation routes. So for example, when commands come into your handler, if you don't handle that event, you want it to continue out and propagating, because maybe the toolbox will give you some free behavior. So the return value controls how that event is propagated. If you return event not handled error, propagation will continue, and maybe the toolbox will handle it for you. And any other value of return will terminate propagation. It's as if you handled it and we're done.
So let's walk through an example of doing that. Like I mentioned, an HI command is a high-level type of event to signify that some command has happened. You don't care where the event came from, you just want to handle this type of event. A good example is if you had a new button in your interface.
Within Interface Builder, you would select the new button, you'd go into the Inspector window, and you can assign a command to that button. So when the user clicks on the button, this new command is sent to your application. Anu is a standard HI command, so you can select it from the pop-up. You can also create your own types of commands.
Then in code, you specify what events you're interested in. Now, you pass in an array to our APIs, but in this particular example, I'm only interested in an HIV command event. So I'm interested in the class command, command process event. Then I want to hear this on the application event target, because like I mentioned, the commands can be sent to the user focus target, and if that doesn't handle it, it'll go out to the window, and if the window doesn't handle it, it'll go out to the application. So the application is kind of a catch-all for commands.
So I'm going to install my handler onto the application event handler, on the application event target, excuse me. And then I tell it that I'm interested in it, or I want it to call my callback handler at this point. So you load your interface in, the user is using it, and then he clicks on the new button. The HICommand is sent, the toolbox calls your command event handler.
There's different information packed in that event, and all of this is documented within the CarbonEvent.h header. So it'll tell you as events come in what type of information is packaged in the event. In this case, there's an HI command. So we call getEventParameter to extract the HI command from the event. And then we can switch what kind of event it was. In this case, we see that it was a new event. So we go and we create our new document window.
So now we talked about dealing with events within your user interface. How do you deal with Dialog modality? I'm sure most of your Dialogs have had to deal with modality. Basically, in the old way of doing things, you were calling Modal Dialog in a loop and handling updates using a filter proc. And for one reason or another, we changed things so that didn't work, and you had to install update Carbon event handlers on your non-front windows in order to get the update events.
So you'd have to write these Carbon event handlers for other Windows. And if you're going to do that, you might as well just start doing things the way we expect you and want you to do. Go through the steps I've talked about already. Convert your dialog to a Nib. Use the standard OK and Cancel commands for your OK and Cancel buttons.
Make your interface Carbon event driven by using the standard HI commands, or you can even use your own event command classes that you define yourself. And when you need to be modal, use the API runAppModalLoopForWindow. This will put your window into a modal situation, and that function will not return until quitAppModalLoopForWindow is called. And I'll show you an example of how to do that. And then, eventually, you'd start using Sheets. Instead of going AppModal, you might just need to go WindowModal. So where it's appropriate, we encourage you to use Sheets to get Window modality.
So let's walk through an example of using modality in this fashion. This is very similar to the previous HI command example I gave. In this case, again, we only will go out of the modal situation when the OK button is clicked, which sends out a command event. So in this case, we're doing the same thing by registering for a command command process event.
And also when we install a handler for a target, one of the parameters is a void star. So we can pass anything we want there, and then when our callback is called, that void star is returned to us. So I'm taking advantage of that in this particular example by passing in the window as that void star parameter.
Then I call run AppModelLoopForWindow. Now as I mentioned, this function is not going to return until quit AppModelLoopForWindow is called. So the event system takes over, starts dispatching events as they come in, and then when we're in here, perhaps the user clicks on the OK button. At that time, your event handler is called.
And as I mentioned, we passed in our window in the void star parameter. So we cast that to our window because we know that's our window. Again, we extract the event parameter. We check to see that it was the OK, with a standard OK command, which it was.
And we go ahead and we call quit app modal loop for window at that point. When this function returns, your previous call to run app modal loop for window will return, and you can continue processing with your app. So that's how you would deal with modality in this new world.
So, custom content. In my example, I had that really cool equalizer area over there in that dialog. And how am I going to start dealing with that now, using this new way of handling dialogs? You need to think about two different paths here. Do you want to remain non-compositing or go compositing? Now, the advantages of compositing are great. In terms of performance, it's an invalidation model, so you're not constantly redrawing when you don't need to.
You also can have overlapping views. It doesn't look ugly because the controls are erasing behind themselves. If you want to continue using non-compositing windows and you're doing all of your drawing and user panes, it's okay. But we prefer that you actually start using HIViews because this is what we're focusing our energy on moving forward.
Especially if you're using compositing or drawing directly to the window, you have to start taking advantage of HIViews. So you'll have to write your own custom views to do this custom content and then embed that in the window. I'm not going to go into any detail about how to write your own HIView.
Actually, David's going to cover that tomorrow in Session 433, Modernizing Your Custom Controls to Use HIView. So I'm not going to go any further about describing how to do that, but I will use this in my example and just show you how to use a pre-made HIView as custom content.
So, like I mentioned when I was walking through my example, the old way of dealing with your dialog was during initialization, if you had this custom content, you would set a drawing proc, and that was to deal with a drawing. And then when you needed to go into the event loop and start handling events, you would call modal dialogue with a filter proc if you needed to handle any tracking in your custom content.
And so at that point, you'd be handling some behavior. You'd be handling the behavior of tracking. And then once you return from modal dialog, you'd get this item hit, and you'd start handling behavior there, too. So there's different areas of code. And then in some cases, you'd actually be installing an action proc to deal with live actions, too. So there's different areas of your code where you're dealing with this behavior. And all of the behavior you write for one dialog, you can't easily rip out and just go throw into another dialog.
So like I've been saying, the new way of doing things is using HIView. I mean, I just can't stress enough how cool this technology is, because it encapsulates the behavior and the appearance of your custom view all in one module. So I don't even have to know what the module does. Like Joe Schmo can create one for me, and I can pull it in, add it to my project, and it'll just work, because I don't care about tracking myself or anything like that.
So you can create the HIView in one of two ways. If you take your HIView and you install it or register it with the HIObject subclassing system during app initialization, then you can provide a class ID in Interface Builder. So in Interface Builder, you can pull in a custom HIView. Interface Builder knows nothing about this view.
But you add a class ID, and then when you create that window in your application, it will go through and instantiate that view. It's really cool to do that. And then alternatively, you can embed it programmatically. You can create one yourself and then embed it in the dialog or window using one of the HIView add subview APIs, for example.
Now I should note here, Guy mentioned this in the previous session also, but I should note that, you know, creating your own custom views, there's a lot of the same code that you have to do, and there's a lot of setup associated with it. So we've written something we call HIFramework, which is a C++ mini framework for the toolbox, and it makes the task of creating your own custom view really simple.
And it's provided as sample code, so you can go through all of the code, see what it's doing, extend it yourself, add modifications if you need to. So let's go back to the demo machine, and we'll start plugging in the behavior and pulling in our custom area in order to get the custom content drawing.
So I'm going to go back to Interface Builder. I'm going to assign standard commands to my interface. So I told you earlier that I want you to start taking advantage of these standard commands that are available. So for the OK button in the Inspector window, I'm going down to the Command pop-up, and I'm going to select OK because it's the OK button. For the Cancel button, I'm going to go select Cancel.
So I told you earlier that I want you to start taking advantage of these standard commands that are available. So for the OK button in the Inspector window, I'm going down to the Command pop-up, So I told you earlier that I want you to start taking advantage of these standard commands that are available.
So for the OK button in the Inspector window, I'm going down to the Command pop-up, and I'm going to select OK because it's the OK button. For the Cancel button, I'm going to go select Cancel. I want to install a window event handler on it to handle the OK or Cancel command.
So I install my Window Event Handler, telling it to call this call back when that event comes in. Then I call runAppModelLoop for window. This function does not return until quitAppModelLoop is called. So it'll go around dandy, you know, handle events, do everything on its own, and then the user clicks Cancel or OK. My event handler is going to be called at this point.
So it'll go around dandy, you know, handle events, do everything on its own, and then the user clicks Cancel or OK. My event handler is going to be called at this point. So it'll go around dandy, you know, handle events, do everything on its own, and then the user clicks Cancel or OK. My event handler is going to be called at this point. If I click Cancel or OK, the dialog goes away. So we can see that that worked.
Hooray! But there's still that custom area that we want to deal with. So, it's pretty obvious that I'm just using Quick Draw rectangles, and I have no idea how to make a really cool interface. So, I went off and I wrote this really cool Aqua interface, and it's just encapsulated by itself. I don't even need to know what the code itself does. I just pull it into my project. and it's called EQView for Equalizer.
[Transcript missing]
You need to register the view at application initialization time. So during initialization, I would call that function, and that goes through and registers the view with the HIObject subclassing system. And then it provides me the signature that this view is going to be. So let me walk back over to Interface Builder. I can't use user pages anymore, so I ditch that.
I bring up this control, pull over an HIView, throw that in the interface, And then if you go look here in the Attributes field, it has a field for a Class ID. And then if you go look here in the Attributes field, it has a field for a Class ID.
The other thing that the writer of this module has told me is that this view is going to respond to certain commands that are sent to the window. So I don't have to plug anything in, it's going to just handle events on its own. And these are the events that it's interested in handling, when you change the equalizer settings, for instance. So that's why I did some hand-waving when I pulled out this pop-up button. I've set all of this up already. So when you select the classic item, it's going to send out this command, rock, et cetera.
[Transcript missing]
Let's go ahead and save it. Let's build it and run. Let's go check out my interface. And now we get this custom area drawing. Now that too, I mean, this is still me making it, you know, it's still pretty ugly, but it's a lot better because when you select a different equalizer setting, it just automatically responds.
It's a new equalizer setting. That's just like, that's cool. And then for sound clipping, if I turn this on, it responds to that too. And then furthermore, it handles tracking on its own. I didn't have to write any special code for this. This is all handled by the view itself.
And something I didn't show earlier, but as I resize the window, because the HIView layouts were set in the NiB, the OK and Cancel buttons are also just reflowed automatically. I mean, this was very easy code to write and get up and going. So you have to adopt this. It's just-- it's the greatest thing ever. So let's go back to slides.
In that last step, all I did was I made my interface Carbon event driven, and I did that by adding commands within the Inspector window in Interface Builder. And to go modal, I use the API run app modal loop for window, and this is how you would deal with modality rather than using modal dialog. In fact, when you pull in a window from a NiB, it's not a dialog anyway, so you can't really call modal dialog using that.
Implement your custom content as HIViews. That new view I just pulled in. Somebody gave this module to me. I plugged it in. I added a few features to make it respond to commands or make commands sense that it's going to respond to. And it just worked automatically. And that custom view was written, so I'm told, with HIVramework, which made it just really easy to write that as well.
Of course, using Dialogs in this type of modality, this is just a really small case of what we would encourage you to do for your application also. Rather than using WaitNext Event for your event processing, we want you to get onto using Run Application Event Loop. This is going to start dispatching events for you and help you eliminate your use of WaitNext Event. We want you to start eliminating all of these old APIs because we have really excellent replacements for them using the new technologies that we put in the toolbox.
These APIs encourage a more efficient use of resources. When you start using run application event loop, you don't get idle time using that API, so you install Carbon event loop timers, which will call you back. So you can give fine-grained control of when you need to be called back and make your application more efficient on the system. And then we want you to use new technologies that we've created, such as Sheets. It makes your interface just so much better.
So I've been talking a lot about this stuff and encouraging you to do this, but you're probably thinking this transition might be really difficult for me to do for my interface. And I just want to tell you that that's not the case. In fact, Navigation Services went through this transition in a Panther time frame. And I'd like to bring up Bryan Prusha to talk about that right now.
Thank you, Curt. Now, as Curt just mentioned, as I'm sure you all know, Open and Save Dialogs went through large amounts of changes in Panther, and we added several new features, including the sidebar, list view, and the view history forward and back buttons. Now, we knew this was going to be a major change for navigation services, and we wanted to take this as the opportunity to move forward from resources to NIBs. And once we made that change, being based on NIBs made it easier to adopt new features.
When we began the conversion, we wanted to take an iterative approach. The idea is to start with just the simplest dialog possible and convert it over first because it doesn't have to be done all at once. Convert that one dialog over and just work out any kinks that there may be in the process. Just get used to the idea and then go on to more complex dialogs.
We were able to very easily write a little bit of wrapper code that converted all our old dialog item IDs to control IDs. So we can continue referring to all our controls as we always had in the past and So continue referring to them as we always have in the past, minimizing code changes.
The next step is then taking views that we created manually in our code and having Interface Builder do the work for us. So when the window is pulled out of the NiB, those views are loaded automatically. But the trick is we had some custom views in there, like the sidebar.
Interface Builder doesn't know anything about the sidebar, but as Curt showed us earlier, we can use a custom HIView control in Interface Builder, add the HIV object class ID, and Interface Builder, even though it knows nothing about our custom views, could load it automatically. So that was just code we didn't have to write.
The next step, as Guy alluded to in the last talk, was the most fun: adopting the standard event handler. The reason this is fun is because we could just click a checkbox in Interface Builder and rip out tons of custom event handling code. For the most part, in Navigation Services, we're just interested in the default behavior happening.
But in those cases where somebody clicks on the sidebar or the default button is hit, something like that, in those specific cases, we just write a Carbon event handler to listen to those events and can ignore everything else. We let Toolbox do the driving for us. Now, the next step with adding compositing was definitely the largest step, but it had the most benefits in terms of performance.
Uh, because Navigation Services handles, uh, has all--pretty much all standard controls, except for the couple of custom controls that we designed specifically with compositing in mind, we could just go ahead and, uh, click the checkbox in Interface Builder and adopt compositing, with two caveats. The first was a change we had to make.
We had to do a little bit of special code, um, to deal with the change in, uh, parent-relative, uh, coordinate spaces. So we were no longer window-relative, and we had a lot of, uh, embedding in Navigation Services in order to make sure that we were always, uh, paying attention to what our frame was relative to our parent view and not the window.
The second thing is that Navigation Services offers the ability for clients to customize the dialog. So there's a custom area in the bottom of the dialog. and clients had always been used to this being non-compositing, so we needed to maintain that behavior. So we did have to add a custom compatibility area that remained non-compositing.
Now that Navigation Services is based on NIBs, we can take advantage of new features in Interface Builder, like HIView layouts. and going through that process, we could keep the, you know, reflow the entire dialog as it's resized without having to write a whole lot of code at all.
Now, the results for Navigation Services were great. Compositing, in particular, gave us about a 20% speed boost loading the dialog. There was so much redundant drawing going on before that it was just simply eliminated by the one-pass draw model of HIView. The speed boost is also apparent in resizing the dialogs, especially in the sheet mode, despite the fact that sheets went translucent, because compositing mode is the only way to get translucent sheets.
And really a great side benefit of moving to these new technologies was that bugs in navigation services were fixed. Because we were removing code and adopting a better drawing model, little visual glitches and some performance problems went away. We handed all the code off to the toolbox and let it do the driving. So there was less code for us to test, less code for there to be bugs in.
and overall it was a very straightforward process, actually smoother than we thought it would go. There were no real strong sticking points or gotchas beyond just a couple of the compositing changes that you need to be aware of that I mentioned earlier. So now that Navigation Services is based on NiBs, we can go ahead and adopt new features as Interface Builder provides them.
So we're in a great position going forward. We really recommend that you go through this process. I think you'll find it easier than you might expect, and it will give you a lot of benefits in the ability to add new features now and in the future. With that, I'd like to go back to Curt.
Basically, the reason I wanted Bryan to come up and talk about the Navigation Services transition was because of a comment that he made to me on the side was, the biggest surprise about the transition was that it wasn't that hard at all. So I really encourage you to go through this transition as you add more features or when your boss says you need some new lyric dialogue in your song info panel. As you need to start adopting these new technologies, start converting it to the new stuff, because this is where we're focusing our attention.
So just start getting up to Nibs, start handling HVAC commands for your event dispatching, make your whole interface Carbon event driven, and then start using all of these APIs that do all the driving for you, like Bryan mentioned. Like run app modal loop for window to go modal, or run app application event loop once you get your entire interface to be Carbon event driven. And then also keep in mind that you will have to convert your custom content to HIViews in order to take advantage of compositing in your dialogues.
So please check out some more information that's available. Something that was really useful in preparation for the demo was on how to move your projects from Code Warrior to Xcode. Because once it was in Xcode, the flexibility of actually using Interface Builder with that, it was pretty easy to use. And then also the Interface Builder services reference describes actually how to get your interface going within Interface Builder. So with that, I'd like to bring up that French guy over there.