Transcript
>> 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 ]