Development Tools • 47:34
Interface Builder enables you to design your user interface quickly using a wealth of components provided by Mac OS X. You can also package your own user interface elements to integrate with those from the system frameworks. Learn how to drag, connect, and configure your own views and controls in Interface Builder and easily share them with other developers as custom palettes.
Speakers: Jon Hess, Matt Firlik
Unlisted on Apple Developer site
Transcript
This transcript was generated using Whisper, it has known transcription errors. We are working on an improved version.
Good morning, my name is Jon Hess. Today I'm going to speak to you about integrating your custom views into the new Interface Builder 3. So let's go ahead and start with a quick feature recap of what's new in Interface Builder 3. In Interface Builder 3, we added a new document window with three new display modes, an updated inspector panel that we've sliced up into show the attributes of your classes, a new connections heads-up display that allow you to quickly make connections and see what's connected to and from your objects. We've added support for all the new Cocoa controls and Cocoa animations, and we've added a library for accessing all of your controls, objects, and views.
So the interesting part about what's in Interface Builder 3 for this talk is going to be the library. And specifically, we're going to talk about how to integrate your objects into Interface Builder's library. So the library is a set of categorized, pre-configured template objects. And the user just uses the library to drag and drop your objects and duplicate them into their document.
So why would you extend Interface Builder and bring your own objects into our library? Well, if your application has a customized look and feel or you have some custom views that you use in your application, you're probably familiar with Interface Builder's NS Custom View that you drag in and set the custom class, and when you run your application, your view appears in its place.
Well, it's hard to lay out and set up your view just the way that you want it with just the custom view. If you integrate your view into Interface Builder, you'll be able to use WYSIWYG editing to position your view just exactly where you want to see it in your interface. So if you follow our steps today and build a plugin for your view, it's going to be really easy for you to set up your interface and design its layout and style and get everything just right.
Another reason why you might integrate your view into Interface Builder is if you go through all the steps to integrate your view, you'll package it in a framework, and now your view will be loading into two applications, your application and our application. And that's going to mean that you've gone through the steps to modulize and well-factor your view classes, and the well-factored code is going to be more maintainable and easier to use in your application.
So as we talk about integrating views into Interface Builder today, we're going to refer back to a couple of samples. And the first sample we're going to refer to is the fragment view. You can think of the fragment view as a simplified version of the segmented control. It only has a couple of properties.
It has a set of fragments, and it has a label for each of them. We also have one other... Sam, we're going to refer back to you today as we talk about the different ways that you integrate views in Interface Builder. This is the fill view. It's just a simple box. It has a fill color property and a border color property. It also accepts other views as children, so it has a content area. Here we go.
So in order to go through all the steps of building a plugin today, we're going to start by talking about how you should design your view. What are some good principles that you should have? In general, these aren't specifically for Interface Builder, but if you follow them, it's going to make integrating your view into Interface Builder very easy. It'll also make it very easy for other applications to use your view once they're in your framework.
We're going to talk about the anatomy of a plugin, like what belongs in your plugin bundle, what classes you'll need to have, where your plugin bundle needs to live, and how the Interface Builder architecture and framework is structured so that you can build your plugin. We're also going to have a checklist that you can go through one step at a time for how to build your plugin and integrate your view into Interface Builder.
So first, the design philosophy. What are some general practices and tips that you should follow when building your object to make it easy to integrate them into Interface Builder? Your object should provide archiving. This is standard NS-- if you adopt the NS coding protocol and implement the standard methods, initWithCoder and encodeWithCoder, we'll need to have you do that to have your view integrate into Interface Builder. You also need to be key value coding compliant and key value observing compliant for a number of key pass, and we'll talk about which key pass a little bit later, but doing those is going to make your object work great in Interface Builder.
You're also going to want to use proper accessors and mutators. So just accessing your instance variables with either set value for key, get value for key, or just using your accessors is going to ensure that you're KVO coding and observation compliant. Then also correct display and validation. You need to make sure that whenever any of your properties of your view change that affect the display of your view to properly call set needs display or set needs display and rect.
This is going to ensure that undo and redo are going to always keep your view looking just the way that it should. So if you do all these things and adopt these standard These standard protocols, you're going to get undo and redo for free in Interface Builder. Your view is going to be copy and pasteable. We're going to be able to use the lift and stamp tool that we talked about on Wednesday in the Interface Builder session. Your inspector is going to refresh properly, and your widget is always going to look correct across undos, redos, inspector changes, resizes, and everything.
So next let's talk about the Interface Builder plugin architecture. What classes live in what places? What do you need to subclass? How does that all work? So we have Interface Builder structured with an application, the Interface Builder app, the Interface Builder kit. This is a framework that the Interface Builder app uses and that your plugins will use. And then plugins. All of our views are integrated into Interface Builder with the same plugins that I'm going to teach you how to make today.
So let's go ahead and take a look at the Interface Builder kit. The Interface Builder kit has three classes that we're going to be focusing on today. The first is IB plugin. IB plugin is how you tell Interface Builder about all of your widgets, views, objects, controllers. The next is the IB inspector.
This is how you-- you'll want a subclass IB inspector, and you'll use this to show the user all of the properties of your objects, views, and controllers when the user selects them and brings up the inspector. Next is IB document. You can use IB document to ask Interface Builder about all the other objects that are in a document along with yours.
In addition to those three classes that I just mentioned, Interface Builder makes heavy use of categories to decide what are the runtime capabilities of your object. And two of them that are going to be important to us are IB Object Integration, this category on IB Object, and it's going to give you an opportunity to declare what the attributes of your object are.
Those are the attributes that you'll need to be KVO compliant and KVC compliant for. It's also going to give you an opportunity to list the inspectors for your object. We also have category methods on view. And one of the things that they do is help provide precise geometry information to Interface Builder so that we can give you a nice WYSIWYG experience with the layout and the Aqua guides and all the snapping and proper rules.
All right, so let's get to the meat of the presentation, the Interface Builder plugin checklist. We're going to go through a set of steps that you're going to do one by one in your Xcode project to build an Interface Builder plugin. So the first thing that we're going to talk about is the plugin class.
So our diagram from earlier, once your plugin's included, it's going to look something like this. We'll have the Interface Builder application linking the Interface Builder framework. We'll have your plugin that links the Interface Builder kit framework, and your plugin will link your framework. And so your view is going to live in your framework, and by having your plugin link your framework, all this is going to come to life inside of Interface Builder. And Interface Builder is going to access everything to do with your view through your plugin.
So you should put your view in your framework. Your application will link your framework. Your plugin will link your framework. All of your Interface Builder logic that has to do with integrating your view into Interface Builder belongs in your plugin. None of it belongs in your framework. And so you should have categories on your objects that I just talked about to override the category as we talked about earlier, and we're going to go into further detail later in the presentation. And your subclasses of IB Inspector, for example, belong in your framework.
So how do you define your plugin? We've overloaded the term plugin. There's the plugin bundle. This is the on-disk representation of your plugin, standard NSBundle, and then there's a plugin subclass that you're going to write. So the plugin bundle being a standard NSBundle needs to have a unique bundle identifier.
This is the bundle identifier in the InfoP list. We use this for uniquing your plugin, and we also use it for tracking dependencies. So that once a document begins to use objects from your plugin, we make sure to load your plugin in the future to edit that document.
In your bundle that you have on disk, in your Info.plist, there's a key NSPrincipleClass. You're going to need to set that to be the name of the subclass that you provide for IB plugin. And that's how Interface Builder is going to know who to talk to when it loads your plugin for the first time.
So as soon as it loads your plugin, it's going to begin talking to the shared instance of that principal class. And as resources of your plugin bundle, you should have class descriptions. We're going to go into those in section four of the checklist. But the class descriptions tell Interface Builder how to connect the objects that you integrate into Interface Builder with each other. Like what outlets and actions they have.
So, in the plugin class, as opposed to the plugin bundle, there's two methods that you're going to want to override. One of them is label. Label is there to help you provide a user-readable name. So, this is what's going to show up in the preferences or during dependency analysis if, you know, should a plugin fail to be loaded.
We're also going to have a method, libraryNibNames. You override this method and return a list of names of nibs that are in your bundle's resources that Interface Builder is going to load and inject into the library. So, this is how you're going to get your views into the library. So, this is the most important and first method that you're probably going to look at when you build your plugin.
Okay, for all the file types and projects and bundles that we talked about, we've got Xcode templates available. So there's a new project template in the Interface Builder SDK section. It's in the standard Apple plugin section of Xcode, and it's the Interface Builder 3.0 plugin. If you create a new instance of this project from this template, it includes a framework to put your view into, and it also includes a target to build a plugin for Interface Builder.
And that project's ready to build, run, link, and load into Interface Builder right after you instantiate the template from Xcode New Project Assistant. We also have new files in the Xcode New File Assistant for the class description files that we talked about earlier, for Inspector NIBS, and for the Inspector class. All right, so that's how we build the plugin.
Now let's go ahead and take a look at how you build your library nibs because once you do this, then you'll be ready to actually see something in Interface Builder. So these library nibs tell Interface Builder about the objects that you're going to integrate into it. They actually contain the objects. So you use Interface Builder to build your plugin for Interface Builder.
You might have many objects that you're going to integrate into Interface Builder. Inside of these library nibs that you build, you're going to put library template objects. You might have noticed in Interface Builder's new library, there's an IBA SDK section underneath the Cocoa section, and it just includes one object, and that object's the library template object.
You can drag that into your interface, and it's a container, and you'll put your view inside of it, and then Interface Builder will load that into the library when you specify the name of this nib in the library nib names method I mentioned earlier. And we're going to show you exactly how to do that in a demo in a little bit.
So the library object template needs to provide a couple of things that are mandatory. The first is it must have a single sub view. This sub view is what the user is going to drag from the library to deposit into a document. It needs to have a path to categorize the template into the tree of available controls in Interface Builder. It also needs to have a label to identify the template to the user.
To set the content of the library, just add one sub view to the library object template in Interface Builder. To set the label and the path, there's an inspector for the view. So when you select an Interface Builder, you can bring up the attributes inspector, and it will be placed right there to set the label and path.
So there's also two optional properties of the library object templates. If you've played with Interface Builder, you might have noticed as you drag our views off of the library, they animate into larger, more discernible forms of themselves. The way that you set this up is that we have two outlets on IB Library Object Template. The outlets are Dragged View and Represented Object.
If you connect the Dragged View of the Library Object Template to another view, then as the user drags your small view of the content of the Library Object Template, it will animate to the view that you set as the Dragged View. You can also set a Represented Object outlet for your Library Object Template.
This is how you can drag something other than a view. So you might put an image or a view representation of something that's not actually a view, for example, a controller or subclass. And then when the user drags your view off of the library and deposits it into a document, it will actually deposit your controller object.
Once you've built your plugin, you'll need to load it into Interface Builder. After you define these library object templates, you're going to have something that's ready for the user to see or for you to test and interact with in Interface Builder. And the way you'll do that is you'll bring up Interface Builder's preferences.
In the Preferences, there's a toolbar across the top with different preference sections, and one of them is Plugins. In the Plugins section, there's a small plus button underneath the list of plugins, and you can go ahead and click that, and you'll be presented with an open dialog. You'll be able to go find your plugin, load it, and see your objects appear in Interface Builder's library. So with that, I'm going to hand it over to Matthew Firlik, and he's going to give you a demo of the first steps in creating your Interface Builder plugin.
Thanks, Jon. So to give you guys an example of what we're trying to talk about here, up here you see the view that Jon was describing. This is the fragment view. And I just put it into a little demo application, and I centered it and gave it four or five different little fragments.
Now, how many of you guys have written custom views before? So I think you guys probably all know that in order to get that on the screen, I had to write a little bit of code. And so I'm just going to show you that here's the little app delegate code that I wrote, just a simple await from nib.
And I had to go through and actually create the different fragment names, go ahead and allocate the view, put it inside, and then raise your hand if you like the crazy math where you've got to figure out the coordinate space and get it all fitting and the strings and struts and stuff. Yeah, exactly.
Everybody loves that. You guys shouldn't have to do this. So this is what we're going to remove for you is the ability to take this view and just put it into IB and make this a lot simpler. So I'm going to go ahead and close this. And on the left-hand side here, I have my IB fragment view project.
This project has two targets in it, as Jon described. This is from the Xcode project template. It has a framework target, and it has a plug-in target. So the framework target has the two files for this particular view. It has the IB fragment view and the fragment itself. And I've already built that and just put that into.
I'm going to go ahead and create a new class here. And we're going to call it MyPlugin. And I'm going to go ahead and create a new class here. And the plugin is going to use that framework to put the view into Interface Builder. So I'm not going to do any magic here. We're just going to go ahead and do the real thing. Let's just go ahead and create a new class here. And we're going to call it MyPlugin.
And go ahead and add that to the correct target, to my plugin target. And there's my class. So this is going to be the class that is the principal class for the plugin that helps tell IB what the name is, where the library files are. A couple things you need to make sure you do is you make sure to bring in the Interface Builder kit framework. So we'll do that. And this should be a subclass of IB plugin.
That's all I need to do on that side. Go into the actual implementation. As Jon described, there are two methods that we really need to implement. The first is the label for this particular plug-in. And this is what's going to show up inside of the inspector when I go ahead and load my plug-in. And we'll just call this My Demo Plug-in.
And the next thing is the array of library nib names. This is the names of all of the nib files that we want to load into the library when this plugin is put into Interface Builder. So I'm just going to go ahead and type in I'm just going to put in any name that I want right now. And we'll call this My Library Nib.
So that's it. That's all the code that I need to put in for my plugin. So now let's go ahead and since I said that the library nib name, I'm only going to have one, it's called my library nib, let's go ahead and create that nib file. So I'm just going to go down here to Resources.
New feature we haven't talked about, but some of you might have noticed that if you could bring up the new file assistant, there are now nib files in the wizard, so you don't actually have to go to IB and create them anymore. You can just do them right from Xcode.
So I'm going to go ahead and create a new empty nib file, make sure I name it what I called it in my plugin, click Finish, and let's go ahead and open that up in Interface Builder. So I open it up and you see that there's nothing in my nib file right now. What we need to do is put in a view in order to put the elements for the library. So I'm just going to drag a custom view into the window here and open that up, and here's my custom view.
So as Jon said, there's a section in the library, you can see it up here, called the IBSDK. And if I click on that, there's one view inside of it. It might be difficult to see with the lighting here, but there actually is an element here. And when I pull it off, you can see it's this little white square.
This is the library object template. It just looks like a little white view, but this is the element that's going to contain your view that you want to put into the library. So now at this point, I have the template for it. Now I need to put my view in it.
But now we're kind of at the catch-22 of I can't put my view in the library until it's somewhere where I can get at it, but I don't have it here to work with. So what I'm going to do is I'm going to go back, and I'm going to grab another custom view and just drop that inside of the library object template. And I'll bring up the inspector here and just resize a little bit so that it fits a little better in the window.
So this is going to be the representation for my view. And the only thing I need to do is to bring up the inspector and to tell it here that it says right now that it's an NSView, but I know that's not right. I want this to be my IB fragment view.
So now I've set the custom class for that, and that will be the element that's dragged off when I do it from the library. Now, Jon also described that you can do this for things that aren't views. So we have the Fragment View, but we also have a fragment. And a fragment is just an NSObject subclass. So I'm going to put in another template.
And in this case, I'd go back to the library and try and figure out, well, what am I going to drag in here? Well, the thing is that I don't really have a view I can drag in here. What I really want is an object. So I'm just going to drag an object into my document window here. And much like I did before with the Fragment View, I'm going to make this a custom subclass. And this is just the IB Fragment.
And now I want to connect these two up. So I'm going to go ahead and bring up the HUD here. And I can grab this and bring it over here. And now it's saying, well, it's not letting me connect that. Why would that be? Well, it's doing that because it doesn't really know what kind of object this is yet.
What I need to actually do is come over here into the framework project and just grab the header and drag it in here. And now that's going to tell it, oh, IB Fragment is actually an NSObject subclass. It's OK that you can go ahead and connect that. So now it'll connect up.
But I still have nothing in the library object template. If I brought it up, it would be an empty view. So I'm just going to go back to my library and we'll grab an image. And we'll drop the image right in here. And we'll make this a generic object.
And we'll make sure that it's large enough. So that's the image for the view. So we have the image and the represented object. If I decided not to actually put the represented object in, I'd be dragging an image into my project, and that's probably not what you want.
So as a final step here, we're going to go ahead and select both of these. And you'll see in the inspector here that there's a label. What I really want to set, though, is the library path. This is the section in the library that these views are going to show up in. So I'll just call this Demo. And go ahead. And now I'm done. Nothing else to do for my library. Let's go back here and just build this project.
And it succeeded. So Interface Builder is still running, but now I just need to load my plugin. So I'm going to go ahead and bring up the preferences, go to the Plugins tab, and let's go and load the plugin that I just built. So we'll click the plus sign. There's the plugin I just built. Click Open. And you'll see a couple things happen. First, it put my demo plugin in here, and there's the My Library nib.
And when I click over here, there's the demo section, and there are the views in Interface Builder. And if we just go ahead and create a new window here, you'll see that very easily I can just go back and drag this view off and drop it on my window, and there's my fragment view. So I can go ahead and resize it, and there's my view. Integration in about two minutes, 30 seconds. It's actually that easy. So... With that, I'm going to give it back to Jon, and we'll talk about creating the inspectors and everything else you need. Thank you, Matt.
All right, so let's go ahead and take a couple steps through the rest of our plugin checklist and see what it'll take to build all the rest of the Interface Builder support for our plugin. The inspectors are probably the most interesting piece that's missing right now, so let's jump right into that. So, a quick review of the inspectors in Interface Builder 3. We've sliced up our inspector a little bit differently in Interface Builder 3 than we did in Interface Builder 2. There's some modes across the top to inspect things like attributes, connections, size, bindings, and animations.
And inside of the different modes, we have some slices to inspect the different properties as they are owned by the classes that represent the objects that are selected. So, today we're going to talk about putting one of your inspectors into the stack of inspector slices on the attributes inspector mode.
So to create an inspector, you're going to make a custom subclass of IB Inspector in your project. In your inspector, you're going to want to override at least two methods. One of them is going to be ViewNibName. ViewNibName tells Interface Builder which nib to load to load the interface for your inspector.
In that nib, you should have the file's owner set to the custom Inspector class that you've just written. And you should have one connection from the file's owner to a single top-level custom view in the nib file. And that's going to be the Inspector view that Interface Builder loads into its Inspector when one of your objects is selected. Next is a refresh method. Refresh is the method on IB Inspector. You're going to override that method. It's going to be called for you very often.
It's going to be called whenever the selected objects change, meaning either the set of objects changes or the properties of any of the objects in the set change. When that happens, you're going to have an opportunity to update all of your Inspector UI to show the current properties of the objects that are selected. You should also have action methods in your inspector. When the user clicks on the widgets in your inspector, your action methods will fire, and this will be your opportunity to set the properties of the inspected objects.
So the inspector stack, how does this work? There's a category method that we talked about on NSObject, and that category method is IB populate attributes inspector classes. We're going to invoke this whenever one of your objects is selected, and this is how you're going to tell us who your inspector is. So when this method gets invoked, we're going to pass in an NSMutable array as the single argument, and you're going to invoke super.
Super is going to take the opportunity to invoke its parent all the way up to NSObject, and each object in turn is going to put its inspector class into the array of inspectors, and you'll do that last, meaning your inspector will show up at the top. So the order that the array is populated with inspector subclasses is the order that the inspectors will show up in Interface Builder's inspector panel.
So to implement your Inspector class, I talked about actions and the refresh method earlier, but you can also do it automatically with bindings. And that's by far the easiest way to implement your Inspector. So if you build your Inspector in Interface Builder and you have a number of checkboxes to control, some Boolean properties and maybe some text fields to input some numbers or strings, you can bind them all.
And what you want to bind them to is IP Inspector, which is your files owner, has a method, has a property, inspected objects controller, and this is an array controller. So you can bind all of your Inspector widgets to the files owner's inspected objects controllers dot selection keypath.
If you choose to do your Inspector manually, you override the refresh method. Every time it's called, you should iterate over your Inspector objects and refresh all of your Inspector widgets. And then you should also use actions and outlets so that when the user clicks on the various Inspector widgets or types into them and presses return, you'll have an opportunity to take the user's input and apply them to the properties of the selected objects. And again, you can actually mix the automatic or manual Inspector. So you can have actions and outlets and override the refresh method and bind some of your controls.
So, multiple inspection, that's a new thing with Interface Builder. And it's fairly difficult to implement if you do things manually with a refresh method, and it's really easy with bindings. So I'm going to encourage you to use bindings here. But if you do it manually, the common case is that the common cases you'll run into is either all the objects that you have selected are going to have the same property, the same value for some specific property. And if that's the case, you'll just reflect that property in the Inspector widget that reflects the property.
If at least one object differs in a value for the representative attribute, then you'll want to put some kind of mixed state into the control. So, for example, you might use the placeholder string of a text field, set a pop-up button to have no selected item, or put a checkbox into the mixed state. There are some uncommon cases where it's really hard to deal with multiple selection. If you have an attribute that's multi-valued, for example, think if we had a person object that might have an array of aliases as a property, too many relationship to aliases.
If the user selects two people with two different sets of aliases, there's nothing that we can really show the user to reflect that. Even if the two properties agree, it would be confusing to somehow reflect that into a table view or some other control. It's perfectly acceptable to just bail out at this point and mark that widget that you can't support for multiple selection as disabled and don't populate it when there's multiple inspection.
If your entire inspector UI is populated with values like this that just can't deal with multiple inspection, you have an opportunity to override the method supportsMultipleInspection on your inspector. And should you do this and return no, then your inspector will just stay out of the stack when there's a multiple inspection.
So let's take a look at how this all plays together. When the user selects your object for the first time, when the user selects your object any time, Interface Builder is going to call the method ibpopulate attributes inspector classes on your object. You're going to call super. For example, if we were the fragment view, that would be NSVU. NSVU is going to call super. We're going to populate the stack of inspectors. So as we go one at a time, we build up the inspector stack.
Then the user's going to enter some value into your inspector. Your action method's going to be fired. Here we've just changed the number of fragments from three to four. So we're going to do whatever it takes to change the number of fragments from three to four on our view.
And when this property changes, Interface Builder's going to notice that for one of the key paths that we're going to declare a little bit later, the value has changed because it's observing it with key value observing. And because the value's changed, it's going to call refresh on your inspector, and you're going to take this opportunity to update all of your inspector state. So, actually let's skip this demo and go a little bit further and talk about other things that we can, We'll come to this later. I'm going to talk more about building your Interface Builder plugin before we do that demo.
So I mentioned the class description a couple times. This is maybe the most unique of the Interface Builder integration aspects. A class description is how you tell Interface Builder about the properties of your class, like about the connections that it can make to other objects. So here's a sample property description for NSL. And in this class description, we're saying that NSL is a class that exists. Its base class is NSObject, and it has one output formatter that's a type NSFormatter.
So if your object that you integrate into Interface Builder has outlets and actions, you'll need to provide one of these class description files also to tell Interface Builder what those actions and outlets are. And that's going to make it so when a user right-clicks on one of your objects for control clicks and the HUD comes up, those actions and outlets will be available to connect. So that's the class description, pretty simple. And then next is going to be the IB integration categories. Those are the category methods that we mentioned earlier.
IB object integration is perhaps the most important category. It's filled with methods that are interesting, but all these methods have default implementations, and these default implementations are almost entirely sufficient. You only need to override these methods when you want to change behavior. So you shouldn't look at the list of methods that we have and be worried that there are 13 methods that you need to override.
You might override one or two of them, and you might override none. When you do choose to override these methods, make sure only to do it in your plugin. So for example, if we were integrating the fragment view, we might put a category on fragment view in our plugin and override some of the object integration methods.
So let's take a look at what some of these methods are. The Fragment View has a set of children, which are its IB fragments. And it's going to tell Interface Builder that these children exist so that the user can select them and inspect them and just utilize them in Interface Builder. And to do that, we're going to override a method, IBDefaultChildren, on our Fragment View.
And we're just going to return the array of fragments. The next method we're going to look at is IB Populate Keypaths. Earlier in the presentation when I talked about what things you should do in general to make it easy to integrate your view into Interface Builder, I talked about being KVO compliant and KVC compliant for a number of keypaths.
But I didn't say which keypaths. This is where you tell Interface Builder about all the interesting keypaths on your object, and for those keypaths you will need to be KVO and KVC compliant. So for the Fill View example that I brought up earlier, it has two properties: border color and fill color.
We're going to need to declare those key paths in this method, and we're going to make sure to be KVO and KVC compliant for them. The way we declare the methods is when this method is invoked, we're going to pass in a mutable dictionary. And inside of the mutable dictionary, the mutable dictionary is going to be populated with... A number of mutable sets. And each set is filled with a set of key paths that are interesting. Those sets are keyed in the dictionary as the attributes key pass and the two one relationship key pass and the too many relationship key pass and the localizable strings and so on.
And as you have key paths that are interesting, you'll add them to the proper set. So the Interface Builder is able to make things like lift and stamp and undo work for free. And it's also going to make sure that your inspector is updated all the time when things change, either inside or outside of your inspector.
So another method we're going to look at is this method, IB Rect for Child and Window Controller. When you have a child of one of your objects that you integrate into Interface Builder, and you want the user to be able to click onto it and select it or tab through your interface and hit your objects one at a time, if your object's not a view, you need to tell Interface Builder where this object lives on the screen. It's really easy for us to figure out where views live, so if you're just integrating a view, you don't have to worry about this method.
But the IB Fragments that we introduce as children of the IB Fragment View, they're just NSObjects, so they don't have a frame or a bounds that Interface Builder can use to locate the objects. So we would, on the Fragment View, implement this method, IB Rect for Child and Window Controller, to tell Interface Builder where the fragments live in the window. And we do this all in window coordinates. So when this method gets called, we return a rectangle in window coordinates for the fragment passed in as the child.
And we do all this in window space to make Interface Builder work great in high DPI, and to make it so that you can use any kinds of bounds, transform, or rotation inside of your view, and we can keep everything working great in Interface Builder. If you do this, this is going to let the user select your objects by clicking on them, tab through them. It's going to let users drag and drop into your views. It's going to provide the tab navigation. It's going to allow the user to connect to and from your objects. So this method is really important. you integrate a non-view object.
So in addition to the object integration methods, there's a couple of view integration methods that we're going to look at. If you've ever designed a container and tried to integrate it into Interface Builder, you've probably run into a number of roadblocks. We've made this really easy to do in Interface Builder 3.
There's just one method that you need to override, IB Designable Content View. So if your view can accept children, then you should override this method, IB Designable Content View, and that's going to give you, and you'll tell Interface Builder what your Designable Content View is, this can be yourself. For example, for our fill view, we just return self from this method. This indicates Interface Builder that the fill view can have any views added into itself.
This lets the user drag the band select, the children of the fill view. It lets the user drag and drop into the fill view. It lets the user drag and drop out of the fill view. It lets the user hold the mouse to lift and move or pop and drag the children of the fill view.
It lets the user drag the views around with the mouse to position them, and it lets the user keyboard position them with the arrow keys. One pixel at a time. So you're going to get a lot of functionality by just overriding this one method. So last is IB Layout Inset.
IB Layout Inset tells Interface Builder some precise layout information about your view so that we can get great AquaGuide support and you can have a good WYSIWYG editing experience. Lots of Cocoa Views draw with a shadow. And the way they do this is they typically only draw the real content of their view in some inset section of their bounds. So in this sample that we have here, we have a bounds rectangle and some distance inside of that rectangle is where we actually draw our fragment view. Then we choose to have a shadow that's slightly offset to the bottom and to the left.
We don't want to include in the layout rectangle of the view. The layout rectangle is what Interface Builder uses to snap the views next to each other and determine if two views are next to each other visually, if they're right aligned or left aligned. You'll want to override this method and tell Interface Builder just how far your shadow juts out from the side of the drawing of your view. This is going to give you great AquaGuide support.
So with that, let's go ahead and turn it over to Matt for the final demo, and he'll show you how to build inspectors and get everything else working inside of your Interface Builder plugin. Okay, so last we left off, we have our plugin class. We have our library nib class. I wanted to take a moment and admit that I got away with something, and it's not fair, and I should admit it to you guys. I didn't actually set the principal class for this.
I got away with it because it was the only class in my plugin, but this really should be set to the actual class that I used. So I called it My Plugin over here. This should really be My Plugin over here. Otherwise, once we start loading other classes, the plugin won't create. So make sure to fill that out, and don't make my mistake.
So as Jon said, now we have the items in the library. We want to create the view integration and the inspector classes. So we have two views to integrate, the fragment view and the fragment. Rather than create all the files in front of you, I have stubbed out the files here.
And let's do them one at a time. So I'm first going to take the fragment classes and just drag those into my project and let them be added to the project. And it's going to add the inspector class and the view integration class. If we open up the inspector class, you see that it's actually just a subclass of IB Inspector. Nothing interesting there. And really the only interesting part here is defining the name of the nib that represents the view in the inspector. In this case, it's the fragment inspector.
If we look at the integration class, this is where things become a little more interesting. As Jon said, there are a number of methods we can implement to provide different pieces of behavior, and there are two that I want to actually implement here. The first is providing the inspector for my view.
You can see in the first method up here, ibpopulateAttributeInspectorClasses, that it's got a mutable array of classes, and it's going to call super. So that's going to get the inspectors for everything that are above or below, depending on which way you think about it, me and my class hierarchy.
What I want to do, though, is take that array and go ahead and put my inspector class in it. So I know I'm going to call it ibFragmentInspector, so I just add that to the list of the inspectors, and now my inspector is going to be inserted into the view when it's selected.
The next is the populate keypads. This is the method that Jon mentioned about things for undo and redo and for all the other behaviors. Again, you'll see here that we have a dictionary of keypads at this time, and we call super, so every other view in the hierarchy gets its chance to set those up. I need to set up a couple things here. I've listed out all of them. You can see that there's attribute keypads, two one, and too many relationship keypads. I don't need to set up all of these, but I just need to set up the first two.
In this case, we are on the fragment, and the fragment has a title attribute, the title of each fragment in the view. I'm just going to go ahead and get the set of the attributes and add my item to it. I'm just going to go over here and add title.
And now that's defined as one of my attributes. I also need to do that for one relationship. Each fragment has a relationship to its parent, the Fragment View. And that's just called Fragment View. So in the Relationship section here, the 2.1 Relationship section, I'm going to go ahead and type in Fragment View.
That's it. That's the integration for that particular class, just defining the inspector that we're going to use and setting up the properties. But let's now go ahead and create an inspector for this, because I want to be able to go ahead and type in the title for each fragment in Interface Builder and change it. So again, I'm going to go back down here and add a new nib file. In this case, though, instead of bringing up the empty nib, I'm going to use the Interface Builder SDK section. And you'll see there's an Interface Builder Inspector nib.
And we're going to give it the name that was in the inspector, the class, the IB Fragment Inspector. And it's added to my project. And once I open it up, you can see that we actually provide a default view for you. It's got the right sizing, the right width, and it just put in some sample data for you. In this case, all I'm really interested in is one field just for the title. So I'm going to go ahead and change this to title. And I can resize it accordingly.
And now the question becomes, where do I get this value from? How am I going to get the value from the inspector into my object and vice versa? As Jon mentioned, if you use bindings, this is a piece of cake. There is, in the inspector class, something called the inspected objects controller. And that is a controller that holds onto the inspected objects. So anytime you select something in Interface Builder, those objects are going to be in that controller. So for the model key path here, I type in inspected objects controller.
Now, when the inspected objects controller, I need to actually get a collection of objects. And in that case, it's the selection. And for the selection, I want to set the title. So it's title. And I've bound that up. Of course, I also bound it to the shared user defaults controller, which was not correct. What I really wanted was the files owner, which is actually the inspector. So that's the inspected objects controller selection title. And that's it.
Now this view is going to be bound to the selection, which I know is going to be my particular view. Jon Hess, Matt Firlik So that's all I need to set up for the fragment. If I go back and Let's go back and grab the other classes here and show you what these look like for the view itself. I'll just go ahead and add these to my project.
For the inspector, we have the same kind of thing. It's just going to be populating the key paths. In this case, for the fragment view, we have a too many relationship of fragments. So that's why you see here that this is the too many relationship key paths. You see here that we're getting the fragment view inspector and just adding that into the hierarchy like everything else. You'll also note down here we're also defining the method IB_RECT_FOR_CHILD. So this was the method Jon mentioned before about being able to define where our children live. So we can go ahead and click on them.
So now once this is all into the view here, the nib just has some simple UI for it. I can go ahead and build my project again. I already had Interface Builder up and the plugin was already loaded, so I need to quit that first so that I can reload the plugin.
And now you'll note that when I come back, I can create a new window. And we'll come back here and click on the demo section. And I can drag this in. But now you'll note when I bring up the inspector that now there's the slice for the fragment view.
And if I come over here and change this value, three, I should make it a little wider so you can see that. There's three, there's five, there's two. I can change the number of fragments. You'll also note that if I hit undo and redo, it just walks right through those, so you get all that for free. And I can go ahead and select an individual fragment and change its title. So maybe this is two, and maybe this is three. So it was just that easy, just creating the inspector and setting it up.
I want to take one moment to show you something that's actually kind of fun. Once you actually have your view inside of the library, you can actually go back and customize your library nib. So let's go ahead and do that for a second. I'm going to go back and open up my library nib. And this was the one I started with originally. So here's my custom view that I put in. I can actually take that out now and I could drag the real view in here and drop that into place.
I can go ahead and if I wanted to change the title for it, maybe I could just put it as one so you don't have to see the real view. that The shortened text. But I can also do something a little bit better, which is when I dropped it in, I only had one fragment, and that's probably not really useful to me.
So I'm going to go ahead and drag in another fragment view here and make it a little bit wider. Set it up with, I think maybe every time I use it, I use four fragments, and for some reason I always want the last fragment to be called end.
So just like I did before with the first, or the fragment, when I connected up the represented object, we can do that with this view as well. So I can go ahead and, just to show you that we can do connections here to other views, let's go ahead and drag connection down to the fragment view. That's interesting. All right.
and I connected up to my fragment view. That connected to the fragment. That wasn't right. Oh, you know why? Because I'm connecting it to the wrong window. Don't make that mistake. So let's go ahead and try this again. What I really want to do is connect this to the Fragment View. That's better. Go ahead and save that now.
If I go ahead and rebuild my nib, and we're going to go ahead and quit IB and start it up again so it gets my updated plugin, now you'll note that when I go back in and drag out the Fragment View, Now you'll get the nice spiffy animation going to the full view, and it also has the customized view.
So once you put your views into the library, you can actually go back and customize them. This also demonstrates the ability to go ahead and customize any view that's in the library. So for example, if you have a table view and you always have four columns, or you always want it set up a certain way with sizes or springs and struts, you can actually just create a plugin that takes existing views, customize them to the way you want to use them or use them most often, put them in the library, and have them available for use. So it's not just with custom views. It's customizing any view that you want to use. So with that, I'll give it back to Jon.
Thank you, Matt. All right, so we're actually done. We're at the end of our presentation, and we've integrated the Fragment View into Interface Builder. So a summary of what we did today. We separated a class out, a Fragment View, into a framework so it would be ready to be loaded by Interface Builder. We built a plugin from our Xcode plugin bundle template. We subclassed Inspector, and we added it to the set of inspectors for our object by overriding one of the Interface Builder category methods.
We used bindings to implement the properties of each of our inspector widgets. And we overrode some integration category methods in our object to make things like undo work for free. And with that, I'm going to point you to Matt Formica is available for more questions. And you can see documentation and sample code from our presentation on the WWDC 2006 website.