Crafting Custom Cocoa Views

Session 141 WWDC 2010

Learn how to build robust, polished, well-crafted custom views that integrate gracefully with Mac OS X's capabilities and user interface. Through examination of a comprehensive example, this talk illustrates the necessary ingredients to putting the fit and finish on any custom view. Leave with a checklist and clearly written example in hand, ready to perfect custom views of your own.

Troy Stephens: Well, thank you.

My name is Troy Stephens.

I'm a software engineer on the AppKit team where I work on the NSView system implementation among other things.

And as you know, right out of the box, AppKit and Mac OS X's variety of other user interface frameworks things like ImageKit and WebKit and QTKit and PDFKit provide you with a pretty wide variety of standard controls that you can often use as is or maybe with a little bit of customization to assemble your application's user interfaces.

A lot of the time, you can build an entire app just out of standard parts.

But every once in a while, you may find the need to invent something completely new, an entirely new kind of view because you want to represent some data in a way that is unique to your applications or you have a very unique kind of data that none of the standard views really can possibly provide for.

So, when you get into this situation and this is the topic of our talk today really is starting from NSView, subclassing NSView directly, building an entirely new kind of view from scratch.

So when you find yourself in this kind of situation, how do you go about making sure that you do a good job of it, that you take care of everything that you need to do in your responsibilities as a view subclasser?

Well, I've often wished for sort of a comprehensive checklist that I could tick down and say, oh, did I remember to do this, did I remember to do this, oh, I forgot that, okay check, did that.

This talk aims to provide that checklist for you.

We have a couple of party favors for you today.

first is a custom view implementor's checklist, and that accompanies a brand new code sample that we're introducing here that I'll talk about in just a moment.

But first to get us in a sort of the right frame of mind to have lofty aspirations for our custom view building, I want to talk for a moment about the essential elements of good craftsmanship.

And to my mind, I thought of about six basic characteristics that define good craftsmanship to me.

The first and maybe most obvious is attention to detail, to the fit and finish of an object.

You know, it's a real pleasure to use a device or tool that shows the designer really thought about every little last aspect of how it's assembled and designed.

And that kind of attention to detail tends to stem from a philosophy of that something worth taking the time to do is worth bothering to do a good job of.

Robustness is another aspect of good craftsmanship that we tend to value.

You know, if you're building a tool, you wanted to stand up to the rigors of real world use.

Otherwise, it's just sort of a pretty object and not particularly good craftsmanship.

Functional completeness, you want something to do everything that you expect it to do, of course, but at the same time you want to balance that with an appreciation for the fact that elegance is the marriage of simplicity and power.

You want something especially in software to be only just as complicated as you need it to be in order to do what you need it to do and no more complicated than that.

As we all probably know from experience, any more complexity brings problems.

And lastly, use appropriate design.

You want something to fit in well in the real world context where it's going to be used.

It's nice to show that you put some thought into how this view is actually going to fit into the context of an application or user's workflow.

So with those sort of lofty aspirations in mind to give us goals to work toward, how do we apply these kinds of principles of good craftsmanship to crafting views?

What are the essentials for crafting views?

The basics start with of course the presentation aspects.

So, how you handle layout and drawing, you know, figuring out what size your content is or how to fit it within the space that you're given, where is everything going to go, and then of course how do you render that content so that the user can see it.

The flip side of that presentation aspect is the sort of input side and event handling.

Of course, how you handle keyboard, mouse, trackpad, and other events that will come into your view to provide ways for users to usefully interact with your content that you're presenting.

Accessibility is really something that I think nowadays we should think of as an essential, as a part of the foundation, the basics of good view craftsmanship by making your views accessible as it has been pointed out in other sessions or earlier this week, including the Cocoa Tips and Tricks session.

You really expand your application and your view to a wider user audience, and there are other benefits as well that I'll talk about later.

Lastly, sort of an umbrella item, supporting standard system features.

You know, we have all these user interface paradigms that nowadays we take for granted.

Things like the ability to select content, cut, copy, and paste, drag and drop, those sorts of UI paradigms that if you support them in your views at sort of a very basic level, then of course that makes them more discoverable.

Your design is more discoverable to users.

They can intuit how to use your custom views.

So once we've got the basics in place, we can look at refinements, and those of course include tweaking the appearance of your view, pushing every last pixel to get things to look exactly the way you want them to.

Planning for animation, in a way this is a refinement.

It's sort of the icing on a cake or the fit and finish from the user's perspective.

And that animates too.

Gee, isn't that nice?

You know, something that's kind of pleasing.

But if you want to do this and do it well and have the versatility you need to do it well, it helps to plan from it for it from the beginning.

And so in a way, this sort of belongs in the basics category.

I'll talk about that in detail later.

Lastly, sort of responsiveness and scalability, you want your view to respond as immediately as possible when the user clicks in it or issues a keystroke.

Any kind of lag in responsiveness tends to make users get frustrated easily.

And of course scalability, you want to think about what's the maximum complexity of your view's content or number of objects you want to be able to handle.

So, I mentioned we have a new code sample today, and that is something that we call TreeView.

And TreeView simply put is a view for presenting and allowing the user to interact with tree graft node structure.

So any we're familiar with these anything that has, where you have a root node and then conceptually that root node may have children and those children may in turn have children of their own and so on recursively.

And to give us a concrete idea of what TreeView is and does, we'll just start with a quick look at demo.

[ Pause ]

So, here we have TreeView running in a sample app, and this code is available for download already.

There is a 1.0 version that went up two days ago I think and there should be a 1.1 version published very shortly today.

So here we have a TreeView and what we're looking at for example content is a portion of the Objective-C class hierarchy as discovered through the runtime introspection facilities.

So here we're looking at NSControl and all of its descendant classes in a TreeView and, you know, we can here this TreeView is the document view.

The scroll view so we could scroll around if it's extremely large as it is right now.

It can get even larger than that.

And the basic API model here is to try to decouple.

When you study the code, you'll notice that this tries to decouple the model from the presentation.

So the API model is we give the TreeView a root model object that represents the NSControl class and then that model object is required to conform to a very simple protocol that TreeView defines, which allows the TreeView to determine that model node's parent and all of it's child nodes.

And so, the TreeView can traverse the model tree, figure out its extent and all of its parts.

And then for each of the nodes it wants to represent visually, TreeView does something very similar to what collection view does nowadays, which is to load a nib file that contains a view, and really, a view subtree that represents a node.

So here we have the NSControl class represented by a node that actually has a container view that draws the rounded rectangle shape.

There's an NSImageView in there, a couple of NSTextFields.

We've got a button in there and these buttons enable us to expand or collapse portions of the subtree.

And as you may notice, that's all animation is built into that so that was designed in from the beginning.

We can click around and we can select nodes, so TreeView supports the notion of selected nodes.

So we can select nodes.

We can I'm using the arrow keys now to navigate around.

That's another thing you want to think about.

Oftentimes, your users will want to be able to just leave the mouse aside or the trackpad aside and can navigate very efficiently using keyboard commands if you give them the ability to do that.

I can hit Spacebar to toggle, expansion or collapse of nodes and so on.

And there's even a little bit of gesture support in here if you have a multitouch trackpad.

You can use a pinch gesture to vary the layout spacing.

Here I have some controls where we can do that explicitly with the mouse so we can vary the parent-child spacing horizontally.

We can spread the tree out vertically if we want to, sort of some basic appearance properties we can tune there and we can set a different background color for the TreeView.

Set a different connecting lines color.

So there are appearance properties that are customizable, and we can choose a different connecting line stye and vary the thickness of the connecting lines if we want to, so the sort of things like that.

And another thing to notice is that TreeView operates layer back too.

And when I click the switch, basically nothing happens.

But there is a lot of interesting stuff going on behind the scenes to optimize this TreeView for layer back mode.

For one thing, the TreeView example is the first one, I think, we've published that demonstrates how to do things like background fill and drawing stroked outlines.

Using CALayers properties let the GPU do the stuff problematically rather than allocating backing stores for all of the layers that constitute these various views.

So that's kind of interesting.

We can click this sort of debug feature here to show the subtree frames, and this shows the structure of the TreeViews content which I'll talk about in detail later.

But basically each subtree, meaning a node and all of its descendants has a green outline drawn around it.

And those are the essentials of TreeView.

So let's look at some aspects of how this is done.

So, I want to have time to examine the code in detail over the course of this talk, but we will refer to various aspects of TreeView's design decisions over the course of the talk.

And again, I encourage you to download the sample code and study it in detail at your leisure.

We'll look at four major topic areas for crafting custom Cocoa views today.

First, designing for animation, from the beginning I mentioned the importance of doing that.

Next, we'll talk about drawing and layout which is related to drawing of course.

Handling of state changes, things that may happen to your view that you may want to respond to, how you can do that.

And lastly we'll look at handling interaction or user input.

So first let's look at Designing for Animation.

What are the sorts of things that you want to think about from the get go when you're designing a view and you might want to animate your content.

Probably the most important top level items to think about factoring your content in ways that enable you to minimize redraw and relayout activity and computations when things are being animated.

And this is especially important for layer-backed mode.

And I think nowadays if you're designing custom view, especially if you're designing it for sort of a generalized use by others you really want to make sure that it can operate well both in layer-backed and window-backed mode.

For one thing you may not have a choice about that.

A view can always opt in to being layer-backed.

If your view does set 1's layer yes on itself it makes itself and all of its subtree all of its descendant views layer-backed.

So, you have control over that but you may get opted in by someone else if your view is used in somebody else's window and they have 1's layer to yes higher up in the tree, you know.

Then everything from there on down is going to be layer-backed and you're going to have to make sure that you can operate that way.

Now oftentimes for most things if you're designing simple controls and so on usually you don't have to worry about this.

Things just sort of the layer-backed stuff does its magic and things just work.

But sometimes you're doing kind of fancy stuff to take advantage of layer-backed mode.

Maybe you're adding your own custom sublayers to the AppKit provided view-backing layer or you're changing properties of layers.

If you're doing things, fancy stuff like that you'll want to make sure that you are robust to backing their tree construction and tear down.

AppKit automatically constructs the layer tree for you when you need it and will tear down those backing layers for example, when your view is removed for a window or when we otherwise decide that we don't need them because the whole idea of the model is that we can recreate them at will.

So if you're doing stuff like that be robust to backing layer tree construction and tear down.

Another thing to point out is the layerContentsRedrawPolicy, sort of a maybe a confusingly I don't know, I wouldn't say it's confusing but it's a method that's not necessarily self-explanatory.

This is a new property on NSView that we added in Snow Leopard and enables you to do something very powerful in layer-backed mode.

Normally, when you tell us view animator set frame size to animate the frame resize of a view we have to assume that at each incremental step along the way during that animation we have to ask the view to redraw its content in its entirety because we don't know better.

We don't know what's in your view and we want to make sure most importantly that the drawing looks correct at each step along the way.

But for animations often that's overkill, and it's too much CPU load.

You don't need to redraw at every point along the way.

If you are doing a quick quarter second animation on the view resize maybe it's sufficient to take the snap shot of the view's content that you already have in it's backing layer or maybe redraw it at it's new size, usually you'll want to do that if you're resizing to a larger size.

You'll have it redrawn once at the start of the animation and then you give us permission by setting the layerContentsRedrawPolicy to just use that snap shot as an image and stretch it over the course of the animation.

And you'll see places in TreeView's example code where we do this.

So, this is I think the first code sample that demonstrates using that.

Definitely something to look into, this can buy you a lot of smoothness in animation when you're running layer-backed.

One more sort of general item, share and reuse repeated content efficiently if you can.

If you have images and other resources that are drawn over and over in your UI, think about referencing the same image rather than copying it of course.

And especially when you're dealing with your own custom layers, you know if you're going to set several layers contents property to point to the same CG image that's something really useful as an optimization.

It saves a lot of space.

And then again plan for scalability.

Think about the maximum complexity of content that you are realistically going to need to handle and make sure that your architecture supports that.

So, thinking about scalability gives a sort of a fundamental choice.

One of the first decisions you get to make is if your designing something that has complex content like TreeView, am I going to use views or non-view objects to represent my content?

And what I mean by non view objects is just really anything that's not in NSView, you can subclass NSObject and define your own sorts of objects that have the basic properties you need such as knowing it's rectangle within the view and knowing what it needs to draw and so on.

It's a sort of fundamental decision you get to make upfront.

And there are different performance implications for this, for layer-backed operation versus window-backed operations.

So you may want to consider whether you're going to primarily be operating window-backed or layer-backed or whether you want to support both.

Now veteran Cocoa developers will often tell you that non-view objects can sometimes or often be more lightweight than NSViews in terms of both memory usage, NSView really is a very general purpose class rather than having a very deep class hierarchy.

We have NSView at the root for all of our views and it encapsulates a lot of functionality and a lot of possibilities including arbitrary coordinate transforms, ability to participate in responder chains, queue loops, and a lot of stuff that, you know if you were to design your own very specialized object for representing say nodes in your TreeView or so on, you might be able to design something that's because you know all the assumptions that go into it.

You can design something that's very small and lightweight to represent your individual parts.

CPU usage also, although to a lesser extent, you know again, because NSView is a very general purpose class when we traverse the view tree to do various operations yes, we do have to make a lot of decisions and branches based on, is this particular view instance using this feature so I take this code path if not take this other one.

So more in the sense of memory usage, that's usually the more significant thing to people.

And so people will sometimes go and build all their content out of non-view objects and that's fine.

It's a little more work but it's OK if that's what you find you need to do.

However, I would caution you that factoring things as views has a lot of benefits especially for layer-backed mode that people tend to overlook.

And now that we have this new concept of layer-backed mode in the system as of 10.5 it sort of changes the game a bit in some important ways.

And one of the most important benefits that you get when you're layer-backed by factoring your content as views is that you get caching your content in separate parts.

Each view gets its own backing layer, each layer gets its own backing store, and so each view for each view we have a snap shot of what the view last drew.

And this makes it very efficient to do animations.

We can just slide things around.

It's very cheap to recomposite bitmaps or top each rather than having to go back to the Core's drawing commands and redraw things from scratch.

And also you get animation versatility and convenience really from this, because you can use the view animator syntax to do moving of views frame origins, frame sizes, and so on.

If you're not using view objects you kind of end up having to implement more of the animation stuff yourself so it's very convenient to be able to just say view animators set frame origin fu and off you go.

Views also provide important culling, event handling, and accessibility benefits by culling I mean sort of a gross high level clipping that you tend to do when you're figuring out, OK, what parts of my content do I need to draw?

When you factor your content as views AppKit takes care of all of that.

It's built in.

If you're using non-view objects you have to build that in yourself have your drawRect do intersection testing against all your subobjects and so on.

Likewise for event handling, you know there are a lot of event handling benefits, things you just get for free, behaviors you get for free with NSViews and accessibility.

Again it's much easier if you factor your content as views and we'll talk about that again later.

So, by now I guess it's probably obvious which direction I leaned in designing TreeView.

TreeView is composed as a set of Nested View Subtrees.

So what I mean by that is at the root we have the TreeView itself a single instance and that TreeView has a SubtreeView that is its root SubtreeView that's sort of an invisible container that contains that entire subtree, in this case the subtree is the whole tree.

That SubtreeView has as a subview, the NodeView, that represents the root node of the tree and then we have descendant SubtreeViews that have their own NodeViews and those NodeViews have their own children and so on.

Now we also want to draw up connecting lines between the nodes and it turns out that for layer-backed mode it's very convenient to encapsulate those in views and we group them so that the connecting lines from a given parent node to all of it's children are drawn by a single view and that makes it very easy to just move those around when we're doing animations in layer-backed mode very efficiently.

So a few benefits that we get from this design, it groups subtrees logically, obviously sort of the view structure tends to mirror the recursive nature of the tree structure where you have this idea of subtrees that contain subtrees and so on.

And it also makes it very easy to sort of move subtrees around if I want to move a subtree to make room for something else.

I can just move the subtree view's frame origin and the whole subtree moves.

Whereas if we had chosen more of a flat design here where maybe we could have made all of the NodeViews direct subviews of the TreeView and all sibling views of each other.

Well then if you want to move a subtree you've got a whole lot of individual parts to move around.

So this grouping kind of helps us, helps make it easier to do certain things that we want to do.

It simplifies relayout, it simplifies relayout animation and it gives us the benefits of content caching when the view is layer-backed.

What are some of the other things you can do?

Some kind of more basic stuff, if your view has custom properties that you might find interesting to animate such as colors, or metrics, that you want to be able to interpolate from the previous value to a new value rather than just jumping to the new value.

You can make those custom view's properties first class animatable properties.

If you want, you can override defaultAnimationForKey on your view subclass and you can do something sort of like this.

Let's say we have a view with a border color and a border width property.

You look for your property name as the incoming key string so we say OK if the key is equal to border color or if it's equal to border width and either of those cases we want to return a BasicAnimation app , OK, that BasicAnimation is just saying interpolate from A to B with some default curve and for any case that you don't specifically handle always call up to super.

And as I said what this enables you to do is use the animator syntax with your custom property.

So, it becomes really sort of a first class animatable property in the system and you can do things like view animator or setBorderColor to blue color and we will smoothly interpolate the color from A to B.

Now an important thing that you need to do if you want to make this work is make sure that your setter methods or the properties you want to animate perform the necessary invalidation by which I mean setNeedsDisplay activity.

Because what's going to happen, we'll get this view animator setBorderColor and we will AppKit will incrementally call invoke setBorderColor on your object with various interpolated values until it gets to the final value for your border color.

So you're going to get a series of setBorderColor messages and each time you get one of those you need to make sure that the affected part of yourself gets redrawn.

So, you want to do setNeedsDisplay in your appearance related setter methods and that's all you need to make that work.

So those are sort of the basics for preparing for animation.

Now let's look at drawing and layout.

And one of the most basic decisions that you get to make upfront is whether your view is going to be flipped or unflipped.

Where this is a fundamental choice that you have with NSViews and it affects a lot of stuff in terms of the way you're going to write your code down the line for doing your layout computations.

So it's worth giving some thought to it upfront.

isFlipped is the NSView method that determines whether a view is flipped by default.

If return's NO you may want to override it to return YES.

And the most important thing this does is it determine the origin of y-axis direction of your views bounds or it's interior coordinate system, the one in which it actually draws and handles events.

So if you have an unflipped view it's origin or it's (0,0) point is going to be at it's lower left corner positive y-axis pointed upward.

If you have a flipped view origin is at the top left corner positive y-axis points downward.

Another effect of deciding whether you're flipped or not is it determines the meaning or the way the AppKit interprets your subviews frame origin values.

If you put a subview in an unflip view the subviews frame origin determines where the subviews lower left corner goes.

If you put a subview on a flip view it determines where the subviews upper left corner goes.

So, if you think about it this sort of makes sense if you position a subview at (0,0) you want it to be in the corner of its parent view but that's something that people sometimes find surprising.

So it really affects you know, you can see how this will affect if you're doing layout computations to figure out where your subviews go it matters whether you're flipped or not.

And if you just change your flipness setting to the opposite setting you may need to change the way that your code is written that does your layout.

If you've worked with the CALayer API directly you may have noticed the geometryFlipped property which sounds really related but it's actually distinct in meaning.

It has sort of a recursive meaning.

That's how you flip an entire layer subtree, so it's not really a direct mapping to the concept of flipness for views.

One of you declares it's flipped or not flipped that just affects that immediate view and not any of its descendants.

They each have their own flipness setting.

So how do you decide whether to be flipped or not?

Well in general you just want to think about the natural growth direction for your content layer, adding new content, you know, is it like a text view where you tend to add new stuff down at the bottom or you tend to add new stuff on top.

And then choose according to that.

It's mostly a matter of convenience and as I alluded to you, you just want to choose whichever convention enables you to write simpler code.

If you find yourself writing a lot view, handling view resize code where you're having to move things numerically just to get them to stay in the same place you might want to think about whether you should use the other flipness convention.

Another minor side effect of this is that if your view is going to be used as the document view of the scroll view as in the TreeView demo case this affects what we call the pinning of the document view when resize happens.

So, when the user resizes the window and resizes the scroll view content area which portion of the document view is going to appear stationary to the user and which part is going to expand to reveal new content?

Some quick points about drawing, you've seen a lot of this stuff hopefully before.

Your most basic responsibility as a custom view is to override drawRect.

You draw whatever content you want and NSView draws nothing by default so you need to tell us everything that you want us to draw.

When you're doing this it's good to be sure for performance reasons that you draw only what you need to.

AppKit passes in an NSRect to the the drawRect method that gives you bounding boxes as this is all we're really asking you to draw.

Everything inside here you don't have to draw anything outside here and in fact AppKit will clip it out.

If you're content is really complicated and expensive and this happens only in pretty rare cases, but this is useful to know.

If you have really complex content, a lot of objects to potentially draw you may want to use the needsToDrawRect method or the getRectsBeingDrawn:count method, because potentially AppKit is really asking your view to draw a series of rectangles.

We keep track of complex up to date regions to try to minimize the amount of redraw work that gets done.

The drawRect NSRect parameter is really a bounding box for that more complex set of rects.

And it's a little bit of an over simplification for something.

So if you want to be really precise about not asking things to draw that don't need drawing, look at the set of rects being asked to draw.

The flip side of that is invalidate only what you need to.

You know, when some state changes in your viewer you get some new content.

You know, you can always throw up your hands and say set needs display YES which basically means, "Oh, I don't know I need to redraw all of myself for all I know."

Sometimes you know that's good enough, that's the best you can do, but whenever you can determine that just some part of your views content needs redraw.

Send setNeedsDisplayInRect in preference to setNeedsDisplay, you know, even if you have to send several of those messages because there are a few different rectangles you need to redraw, try to tightly bound the area that you are asking to have redrawn especially if that drawing is going to be expensive for you to do.

And lastly, and this is sort of a subtle point that many people miss when trying to get views to work correctly in layer-backed mode is be very careful about which views you're messaging to be invalidated.

This was something that you could, you could be sort of sloppy about and get away with it in window-backed mode where everything is sort of sandwiched together and all that really matters at the end of the day is which part of the window was asked to be redrawn, whatever part of the window was marked as needing display we're going to go through and ask every view that makes a contribution to that part of the window from back to front to redraw a part of itself.

But in layer-backed mode again, every view gets its own content snapshotted separately.

So, you got to make sure that the view you invalidate is the view that is actually drawing the content that needs redraw.

Sounds simple but it's something that's easily overlooked and it's something you may need to debug if you are working in window-backed mode and suddenly you are going in layer-backed mode and hey things aren't redrawing when my state changes.

For layout, I just want to point out a convenience that people rarely take advantage of.

We have a viewWillDraw method now.

This message will get sent recursively down your view tree before we recurs to sent drawRect to all of the views that need drawing.

So every view that's going to get a drawRect message in a given recursive drawing pass will first get a viewWillDraw message.

And you could override this to do all kinds of stuff.

It's permitted to actually resize your viewer, resize your subviews at this point, to perform additional removal of subviews.

This is a great point to do what people have often wanted to do which is sort of do some work, just at the very last minute when you know you're going to be asked to draw.

OK, now I do my layout.

This is very handy for things if you're doing something whether you're fetching stuff over the network and you don't want to actually do your layout work until you have all of the data that you need or until you know that you need to draw whatever you have at that point.

If you do use viewWillDraw to perform layout in this way make sure that you mark the view as needing display whenever layout work is needed.

If the view isn't marked as needing display we're never going to send you a drawRect or a viewWillDraw.

And always call up to super viewWillDraw when you override this because that is in fact the mechanism for continuing the recursion down to your descendant views.

So you can invoke this before or after, or in the middle of doing your work possibly depending on whether your descendants will require you to have done your work first.

So that's very handy for doing layout.

The lowest hanging fruit in the world of view optimization is opaqueness.

This sense of whether a view is opaque or not.

This is the easiest and most worthwhile optimization you can do for a lot of views.

By default a view reports itself as not opaque.

A view draws nothing, right, so it doesn't cover anything.

But if you know that your view is going to cover its entire content area with 100 percent opaque fill what you can do is override isOpaque to return YES.

And this is very valuable to us especially in window-backed mode.

It tells AppKit basically don't bother drawing anything behind me.

Don't bother asking any views behind me to draw in my rectangle because I'm going to paint over all of that anyway.

So we can even get away with not drawing the window background fill behind you which can be a significant savings especially if your view covers a large window area.

Now, if your view reports itself as opaque but it has an overall alphaValue setting of less than 1.0, less than fully opaque AppKit still does the right thing.

So you don't need to worry about taking into account what your views alphaValue maybe that's the sort of overall opacity dial in for how your view is composited in, it matters mostly in layer-backed mode.

Just take into account your fill coverage and whether you're filling with an opaque color and you'll see that TreeView reports itself as opaque for example depending on whether its background color is an opaque color.

For geometry calculations just some high level points, always make sure you're using compatible units.

You can get into a lot of trouble if you're not.

Remember when you are using NSPoint values, NSSizes, NSRects, these are all implicitly defined in terms of some coordinate system in which coordinate system is, it usually it's the coordinate system of some particular view or some window that information isn't carried along with those geometry values so you've got to be careful to make sure that your code treats those values appropriately so if you're writing a mathematical expression where you're doing some layout computation you have to make sure that all of the units that you are using are compatible.

They are on the same views coordinate space, for example.

And in order to get compatible units, you want to do the necessary conversions between view spaces and these are the six canonical methods that we provide on NSView for enabling you to do that, you can convertPoint values, NSSize values, NSRect values and you can convert them from the receiver of the message, from a given view to the receiver of the message or to a given view.

So, there is a little bit of duplication here.

You can use whatever syntax is more convenient for you.

You can tell A convert point from view B or tell B convert point to view A.

So these are very useful for making sure you have compatible units.

There is a special meaning to passing nil as your view parameter here.

Nil denotes the windows coordinate space and for window-backed rendering as you may know, this is a very good space to do any pixel rounding calculations you need to do.

If you want to wind things on exact pixel boundaries which at times to produce crisper, sharper rendering, you often want to do your pixel alignment in window space but what about when you are going layer-backed?

When you're layer-backed, your backing layer isn't necessarily aligned in the same way as your windows pixel coordinate space.

So you want to perform, you're rounding in that case in the layers interior pixel coordinate space.

So rather than how do you have to worry about this, we provided a set of new methods for converting to and from what we call a base space and base space is implicitly sort of the appropriate space for you to do pixel rounding calculations whether your window-backed or layer-backed.

If you are window-backed, it is the same as converting to and fromView nil, so you might take a point convert it to base and then do some floor, or round, or ceil arithmetic on it and then convert it back from base to do whatever drawing or layout you need to do in your view.

So these are very handy.

I encourage you to use these when you are doing writing model pixel rounding code from scratch.

It saves you a lot of work and makes you sort of window-backed versus layer-backed agnostic.

Your code can work both ways.

Lastly, a quick item about handling printing or PDF output specially.

We sort of take for granted nowadays that we have this unified imaging model where you write your drawing code once and the same ports drawing code can be used to produce output that goes to your view and its window and also output to be saved to PDF file or to be sent to a printer.

For most cases, you want to use the exact same code to do that, but occasionally, you'll want to customize some of your drawing code for printing and the way to do that is to look at your current graphics context.

Your graphics context will tell you whether it is drawing to the screen.

If it says, no I'm not drawing to the screen, you can assume OK, I'm being drawn either for, to say PDF output or I'm being printed.

And you can do things like for example, you know, a web browser will give you the option, usually when you print a page, do you want me to print the web page background or not?

Usually, you want to leave that image or color fill out 'cause why bother?

So, here is an example of doing something like that.

Only if we're drawing to screen in list view fill its background, otherwise, the drawing code could be the same.

So, just a quick tip there.

So those are the essentials, sort of I think for drawing and layout, now let us look at handling of state changes.

You know, handling things that can happen to your view that you might want to react to but might not have initiated.

First, quickly entering and exiting layer-backed mode.

Again, in most cases, this is magic.

You do not need to worry about it.

If you are doing fancy staff like hanging your additional layers of our own off of the AppKit provided backing layer, setLayers is the override point for this because it's invoked recursively when a view subtree becomes layer-backed or ceases to be layer-backed every view in the tree will get either assigned a new layer or assigned layer nil if we're tearing down the backing layer tree.

Set 1's layer isn't a good place to do this because only the top level view on the subtree will get it and this was talked about in detail at the Leveraging Cocoa's Layer-Backed Views talk back in WWC 2008, if you can find this on ADC on iTunes, I encourage you to look at it because it deals with a lot of the important new ounces of writing your code for a layer-backed mode.

More mundane kind of everyday stuff and the course of its life cycle your view will be added to a superview sometimes and removed from the superview and as part of that, it's entering a new window or being removed from a window.

So let's say, we have here a text field that has actually a subview, that X button there at the right hand is meant to be seen as a subview of the text field.

And let's say, we add the text field to some view superview in the window, what will happen?

The text field itself will get a viewWillMoveToSuperview message notifying that you're about to be moved to a new superview and this is a great override point if you want to be able to do something special when that happens.

You'll get the new superview as a parameter.

You can look at self superview to find out which superview we were in previously because this is a will notification.

This hasn't happened yet, it's telling you that change is about to happen.

Now in addition, every view in that subtree is going to get a viewWillMoveToWindow message, because you're splicing a view into a new superview.

Now all of the views in that subtree are going to be in a new window that they weren't in before, maybe they were in window nil now, they're moving to a new window.

Again, you get the new window as a parameter or you can ask for self window to find out which window you are moving from.

So okay, you move the view in, the changes happen.

Then viewDidMoveToWindow, gets done to every view on the subtree but viewDidMoveToSuperview get sent only to the top level view, the text field in this case.

So these can be very useful in particular for subscribing for notifications related to the window, you want to know when you go into a new window, OK, now I want to subscribe to notifications for certain things happening in this window and then when you're pulled out of the window, you want to unsubscribe so you don't get those notifications anymore.

That's a typical usage pattern.

Being hidden or unhidden, usually not something you need to react too but maybe some times you're doing some processing that's expensive.

You are decoding some animation and if your view is hidden, and it's not displaying anything, why bother eating up CPU and resources so you might want to turn that stuff off temporarily or pause it when you're hidden.

So when a view becomes hidden, really hiding handles, hiding effects an entire view subtree, so if we say set hidden Yes to the text field it and its button are going to be hidden and both of those views are going to get viewDidHide messages and then for unhiding there is a corresponding viewDidUnhide message.

So again, you know, maybe not something you need to respond to but if you need to, those are the hooks.

Becoming/Resigning firstResponder.

FirstResponder, for those familiar with other user interface framework is sort of our concept of the focused view.

It is the view in a window that will get any key stroke events that go to the window, so in AppKit, we call that firstResponder and when your view becomes a candidate to become firstResponder, maybe the user clicks on it to put the input focus there or tabs to it if it is the key loop, you'll get an acceptsFirstResponder message.

AppKit is asking you, hey, can you become the firstResponder?

If you answer Yes, you will becomeFirstResponder message and a resignFirstResponder message.

You may also be interested in whether your window is Key because usually, the combination of being firstResponder and being in the Key window, being the window in the application that's designated to receive Key events is what you used to determine whether you want to draw, say a focused range so that you are focused and actually receive input.

And there are WindowDidBecomeKey and WindowDidResignKeyNotifications that you want to look for, for that.

Being resized, very basic thing, it happens to all views, setFrameSize is a perfectly fine override point for finding out about that.

If you do override it, always call up to super and then do whatever layout positioning of your content or subviews, you need to do.

You can also override resizeWithOldSuperviewSize and or resizeSubviewsWithOldSize.

However, if you do this, one of the things people get hung up on sometimes it'll override these methods and they find they don't get invoked.

What is very important is that you are view instances have the autoresizesSubviews flag set to Yes in order for this messages to be sent.

If autoresizesSubviews is off, since these are part of the autoresizing mechanism these messages will not get sent to you.

Again, it is always a good practice to call up to super if you override these methods.

So both of these are fine ways of doing it, a lot of people like to override setFrameSize because it's reliable, you don't have to worry about the autoresizesSubviews flag being set on instances.

Another good thing to do is make sure that your view can be archived and unarchived and one of the really exciting things that this enables you to do is have the potential to create an Interface Builder plug-in and inspector for your view and make it really a first class citizen among the world of standard views.

And the technique for this is pretty much the same thing you do for any ordinary object that you want to make archivable, you override encodeWithCoder to write out your state after calling up to super to have NSView and your ancestors write out their state and then you override initWithCoder to read the state back, anything that you persisted, you read back in.

And when implementing initWithCoder, you always want to make sure to access your iBars directly.

It's generally not good practice to invoke your setter methods because then you may be sending messages to an object that's not completely formed and that will confuse your code.

So those are sort of the essentials for being prepared, for handling stuff that can happen to your view, our last section for today is handling interaction.

We're going talk about input for a bit and of course, there are a variety of input sources that you can receive events from.

Of course keyboard and mouse, the basics, a trackpad acts for the most part like mouse that's what it is.

But then, if you have a multitouch trackpad on your machine, it can also sense gestures and even individual multitouch events.

So, you may want to take advantage of those capabilities.

Likewise, for a tablet, basically acts like mouse if you want it to, but will also adds new capabilities that you can take advantage of such as variable pressure sensitivity, the ability to sense whether the users are using the writing tip or the erase tip and so on.

And last but not least, accessibility which I again, I consider something very essential.

This is, as has been described in other sessions, another way for the system and system components like VoiceOver, anybody who's using the accessibility APIs to discover the structure view user interface and be able to drive it in various ways and because that is so important, I want to talk about it first real quickly.

As we all know, enabling accessibility support in your views and your applications enable the assistive device access of course for users with visual impairments and other such disabilities.

But another under appreciated side benefit is that it provides for the kind of automated user interface testing that a lot of us are wanting to do nowadays.

We've been doing unit testing on our low level code for long time and now people are wanting to automate their user interface testing to detect regressions and just sort of performance test things.

That's something that you can do.

Once your interface is accessible, if you make your views accessible, they can be driven not only by VoiceOver but by anything that you want to use the accessibility APIs with, any code that you want to write, AppleScript, your user interface becomes scriptable.

So the basics in making a costume view accessible, something like TreeView that's complex, you want to expose both your view and its internal substructure to accessibility and you'll see in the code that we specify appropriate accessibility roles for the views, you return appropriate accessibility attributes for the roles that we have declared and you also need to support setting attribute values and actions for the appropriate role.

And the real take home point with a TreeView example is again, because we've structured our content using views, we get a lot of stuff for free and there is, if you look at it, there's very little accessibility code in this code sample and there would've been a lot more work to do, a lot more of a headache if you had factored things using if go off on your own and use your own non-view objects to structure things then those are completely invisible to us and you really have to build up, do a lot to build up the accessibility capabilities.

So views are your friends once again.

Handling keyboard input, I won't go into this in great detail because there is a great session right after this one, in this room at 11:30 about handling keyboard input and also dealing with menus and menu customization.

But very briefly, if your view wants key events, lot of people do not realize you have to ask to accept them, you need to override the acceptsFirstResponder method that I mentioned earlier to return YES, by default it returns NO.

And then once you've done that, you override keyDown to receive key press notifications and keyUp if you want to know when keys are released.

And the interesting event properties for key events are the string of characters or the sting of characters that's composed ignoring the modifier key state, you can get the bit field that tells you to stay the modifier keys, that being Shift, Apple or Command, Control, and Option, and whether the event is an auto repeat event or whether this is a user initiated key stroke that you're getting an event for.

Any key events that you do not handle, you should always pass up to super, you might also want to respond to changes in modifier key state.

You are not people sometimes expect to get a keyDown event when somebody presses the Shift or Command key.

It does not work that way.

Those modifier keys are treated specially but there is a flagsChanged method that you can override to get notifications of changes to the modifier key state and again, you can look at the events modifier flags to figure out which of those modifier keys are currently pressed or released.

Lastly, I want encourage you to make the parts of your views content keyboard navigable because a lot of users just want to have hands on the keyboard all the time and that's really nice when you can use a view and access all of its functionality exclusively from the keyboard when you feel like doing that.

As part of that, you may want to indicate when your view is focused to receive keystrokes that help the user know when I type, where is the typing going to go.

I showed earlier how you can tell when you are the firstResponder view in a window and when your window is key or not key, once you've determined that, you can use the NSSetFocusRingStyle API and to clear the NSGraphics.h to setup a special drawing style that will surround any subsequent drawing you do with the FocusRing shape, so might do to say in your drawRect method.

Here, we are looking at, OK.

Am I, my windows firstResponder?

And is my window, the current key window of the application?

And if so, I can I'll save graphic state to isolate this state change and then set my FocusRingStyle and then do some drawing.

Now when you do this, you want to be careful about invalidation.

There is a special method that you should use when your view is showing a FocusRing, you should always use seKeyboardFocusRingNeedsDisplayInRect instead of setNeedsDisplayInRect.

Anywhere you would send the a setNeedsDisplayInRect.

The reason for this is that FocusRing drawing kind of breaks the conventional rules of drawing.

It is not clipped in the same way that conventional drawing is.

This is what enables you to draw a FocusRing around the perimeter of your view inside your drawRect where we would normally clip any drawing to your view's interior.

So what KeyboardFocusRingNeedsDisplayInRect does it basically adds a little extra padding to make sure that the FocusRing spill over, gets erased.

Now spot and events, when the user clicks the main mouse button, you will get a mouseDown event followed by maybe one or more mouseDragged events and then a mouseUp message at the end.

What a lot of people like to do is override mouseDown and then create what we call a modal event tracking loop where you'll go into a while loop and you'll pull events off the application cue looking for mouse events and you'll eat them all up and handle them until you see that mouseUp event come and then and only then does mouseDown return back up to the caller.

We tend to discourage this practice nowadays, I would strongly encourage you to use these individual methods as override points.

The problem with creating your own modal tracking loop is that you know, you are taking over the entire what you are doing is you are blocking event processing that the run loop might otherwise do and handling event loops sources and things.

So, by using these individual methods, decomposing your mouse event handling, you free the run loop to handle other event sources and things like that.

So that is a lot better, so you might have animations if you do modal tracking loop approach, you know, you may have animations that are running on timers, but oh your timers are not getting serviced because we are not getting back to the run loop to service them.

So that's a lot better approach nowadays.

And for handling right mouse button, there are corresponding rightMouseDown, rightMouseDragged, rightMouseUp messages and then for mice with more buttons than that, there is otherMouseDown and otherMouseDragged, otherMouseUp.

The main event properties of interest for mouse messages buttonNumber of course, which button was pressed.

This is especially important for otherMouseDown since that's in for any other button.

ClickCount, you know, maybe I'll be too if it was a double click.

The modifier flags, you also get the state of the modifier keys at that time of the mouse event, and then locationInWindow which is the location of the mouse cursor at the time of the event.

And this is it's important to point out, this is provided in window coordinates, as a lot of people do not realize, you need to convert this to your views interior coordinates usually to do something useful with it.

So here we have a mouseDown method.

And we want to get the point in the view, we get the events locationInWindow and use the convertPoint fromView method that I mentioned earlier to convert that point from window space to view space.

Now, it's in view space, now we know what to do with it because all of our layout calculations and drawing calculations are presumably done in our own interior coordinate space.

You can also ask to receive mouseMoved messages if you're interested in every little motion of the mouse.

But because these are kind of expensive to send out, we don't send them out by default.

So one of the pitfalls people sometimes run into, they'll override mouseMoved and say, hey, why aren't I getting mouseMoved messages.

I'm moving the mouse, because they're expensive to send out, we don't send them out by default, you need to send your window message, not the view but the window saying setAcceptsMouseMoved: YES and then you'll open the floodgates and then get the mouseMoved messages.

However, if you can, if you can do what you need to using one of these other facilities such as tool tips, if you just want to pop up a little text message, you know, pop up when the user mouse is over a certain rectangle in your viewer, you want to change the cursor or actually do any other kind of arbitrary thing, tracking rects tracking areas rather as a more modern form the most general form of this, use these facilities whenever possible.

They're made much more lightweight by the fact that a lot of the work is done in the windows service and the app is only called on to do work when something an interesting event actually happens.

So, if you have to, ask for mouseMoved events but if you don't really need them, if you can get by with this, these are much lighter weight facilities for reacting the mouse movements.

Gestures and touch events.

You know, something obviously only users who have multitouch trackpads are going to be able to use these.

You don't ever want to tie any of your views functionality exclusively to gestures or touch events but it provides a nice way to provide some extra polish and another more natural way to interact with certain aspects of your view for users who do have multi-touch track pads.

So gestures are the highest level form of this and the easiest to implement, and gestures are sort of abstractions that are implemented by the device layer, they're interpreted for you.

You don't need to opt in the received gestures.

You just override one or more of magnifyWithEvent, rotateWithEvent, or swipeWithEvent and this will notify you when the user is making the sort of familiar gestures that we've all learned now, magnify being sort of the pinch gesture, rotate, you know, gesture where you move your fingers about a common center and swiping.

When you're using either a three-finger swipe on a trackpad or actually even on a magic mouse nowadays, you can do a two-finger swipe and you will get swipeWithEvent.

So, in the TreeView case, if you have a multitouch trackpad and or a magic mouse and you swipe, you'll see that it collapses or expands the entire tree in response to that, just a sort of a demo item.

You might also be interested in beginGestureWithEvent which tells you when you're we're starting a series of gesture messages.

Basically the user has touched the trackpad with one or more fingers and we're starting to interpret gestures.

So we get a beginGestureWithEvent at the beginning and then you'll get one or more of these, magnifyWithEvent, rotateWithEvent, swipeWithEvent messages, and then at the end when the user lets their fingers off the trackpad, you will get an endGestureWithEvent.

So if there is any processing you need to do at the beginning or end of a series, those are the hooks to do it.

If you want to get really fancy, you can ask to receive individual touch events.

This is new in Snow Leopard, there's API for this.

You have to opt-in.

This is obviously more complex because then it is up to you to figure out how to interpret the touch events individually, but it's arbitrarily powerful and that you basically get to define your own gestures, however you want to.

You need to opt-in to receive these, they are not sent by default.

You need to send setAcceptsTouchEvents to your view, and if you want resting touches, setWantsRestingTouches.

And then once you've done one or both of those, you need to override all of the following methods, touchesBeganWithEvent, touchesMovedWithEvent, touchesEndedWithEvent, and touchesCancelledWithEvent and always, always in your implementations of these call up to super to make everything work great.

Lastly, quick note about tablet input.

Some of your users may have tablets.

This is a bit more specialized in that, you know, if you want to if your app or your view needs to use tablet input, you probably know it already, you know, you're implementing a view for sketching or something like that.

But there are maybe cases where you want to do clever stuff, we haven't thought of with this.

The basics for handling tablet input, the first high level item to be aware of is we have an inking system on Mac OS X.

It's a handwriting recognition system, we call that inking.

By default, when someone touches the pen down over your view, the system will assume that the user maybe wants to start inking.

They want to start doing some handwriting that is to be interpreted and converted to text.

So if you want to handle pen events yourself, you need to override, shouldBeTreatedAsInkEvent and return NO when you want to suppress inking and get the pen events yourself.

NSControl because usually when you got a tablet and you're interacting with controls, you want to interact with them, you do not want to start inking when you touch the pen over a pop-up button say, so NSControl by default returns NO but NSView if you're subclassing directly, returns YES.

So, you need to override it.

Tablets in addition to acting like mice provide some special events for you.

There's a tabletProximity event that gets sent when the pen or stylus comes within sensor range of the tablet or leaves sensor range and there's also a tablet point event that gets in as the user moves the pen around but it isn't necessarily clicking.

So we got locationInWindow.

You can get the absolute position of the pen on the tablet so that the position that's not mapped into your screen space by just raw position.

The pressure as I mentioned, which is number from 0 to 1.

Mice only have one pressure for clicking which is 0 or 1.

You know, your clicks always have a pressure of 1.

But with the pen, you can get variable pressure.

You can sense with some devices the rotation or tilt of the stylus, tangentialPressure on some devices, the buttoMask, which buttons are pressed or released and whether the event is a proximity event where the pen is coming into proximity.

And then there are a whole bunch of vendor.

Mostly the vendor defines things where it can find out what type of device is being used.

And that's pretty much it for input.

So we looked at four major topic areas today, how to design your views with animation in mind from the beginning so that you can have the most versatility in order to move stuff around the way you want to, how do you do drawing and layout, handling of state changes, things that can happen to your view that you might want to react to, and lastly handling of interaction or user input.

The take home thoughts I want to leave you with, with attention to detail and thought put into the design of your custom views, you can craft robust and polished customs views that ideally will fit naturally into the Aqua user interface.

And the user of your app won't necessary realize that, oh, they're using something custom that's not just something that Apple built right into the UI.

I encourage you to download and refer to the TreeView code sample.

There's a lot of good stuff in there especially the layer-backed mode optimization stuff, again, the first time that we've illustrated some of these things, and there's also the accompanying view implementor's checklist, which I hope you'll find useful to go down as you're trying to look for reminders when implementing your own custom views.

There's a lot of good related documentation out there for those who are implementing custom views.

The Cocoa View Programming Guide of course.

I encourage you to refer to the Cocoa Accessibility Guide when making your views accessible.

Cocoa Drawing Guide, Cocoa Event-Handling Guide.

And again, right after this talk we've got a great talk coming up on Key Event Handling and menus.

I encourage you to stick around for that.

Also, earlier this week, we had the Design Principles for Accessibility talk, and also Cocoa Tips and Tricks talked about both accessibility, and somewhat related to what TreeView does, it talked about how NSCollectionView deals with loading views from nibs.

It's very similar model that TreeView tries to use.

Lastly, I hope you had the opportunity to attend the API Design for Cocoa and Cocoa Touch talk yesterday.

That was a great talk that really distilled a lot of the fundamental design philosophy that we used when developing Cocoa APIs, especially if you're going to be developing custom views for other people to potentially use.

This talk provides a lot of great guidelines.

So if you didn't get to go to it, I hope you get a chance to review it on ADC for iTunes.

For more information, you can contact our evangelist.

We've also got developer forums where you can ask questions and get answers.

Thank you very much.

I hope you enjoyed today's talk.

[ Applause ]

Apple, Inc. AAPL
1 Infinite Loop Cupertino CA 95014 US