WWDC2016 Session 239

Transcript

[ Music ]
[ Applause ]
>> Hello, welcome to
Crafting Modern Cocoa Apps.
My name's Corbin Dunn, and
I'm going to give this talk
with my colleague Jeff.
We're both AppKit
software engineers.
So let's jump right into it.
What are we going
to talk about today?
We have a whole bunch
of subjects.
We're going to talk about
getting a modern look,
drag and drop and how
to do it correctly,
container view controls,
table views,
using some system appearances,
designing with storyboards
and some other cool
Mac features.
and some other cool
Mac features.
Tons of stuff which
we're going to highlight
with a quick demo
app that we have
that we see in this screen shot.
How it's alive in new features
or existing features
they can adopt.
And all these technologies, most
everything you can use already
on Mac OS 10.10 or
10.11, anything new
to 10.12 we will specifically
highlight and point out.
And this demo app we
encourage you to download it.
It's not quite available
yet but it will be soon,
and it will be associated
with the talk.
So let's jump right
into it and talk
about creating a modern
look with modern views
and what you can do there.
So modern look means a modern
window and a modern toolbar
such as shown in
this demo app here.
One thing you want to be
doing is using a full size
content view.
The full size content view
allows your content view
to extend underneath
the toolbar area
that I'm highlighting
here in white.
And that means that full size
content view will have its area
And that means that full size
content view will have its area
automatically blurred by the
titlebar and toolbar area
without you having
to do anything.
Using a full size content
view is really easy.
It's just a window style mask.
You can use the
.fullSizeContentView bit
on it to include it.
Or if you're using Interface
Builder you can just check a
check box, full size
content view, and you get it.
So if you're using the full
size content view you may need
to offset your content
underneath it.
For instance, let's say
that we have something
that we want underneath
that titlebar/toolbar area.
We might want to put like
a label or something there.
If we put that label
there how do we get it
under that titlebar/toolbar
area which might have, say,
a dynamic height that would
change, and we want that label
to not have a hard
coded Y offset
because hard coding
offsets would be really bad.
So what we have is we have the
content layout rec property
So what we have is we have the
content layout rec property
on NSWindow.
This is a key value observable,
a KVO observable property
which you can use to find
out whenever the
contentLayoutRect the area
in turquoise changes.
So you can go and place your
view directly underneath the
titlebar/toolbar based on that.
But we also encourage you
to be using auto layout.
And if you're using auto
layout you can use the
contentLayoutGuide which
is a property on NSWindow.
For instance, inside of your
NSViewController subclass you
might be overriding
updateViewConstraints,
and you want to create
a constraint.
You grab the window's
contentLayoutGuide,
tick the top anchor for it,
and you have your text field,
and you create a constraint
to that text field's top
anchor also and activate it.
And if you do that, that text
view will automatically be below
that titlebar/toolbar area
without you having
to do anything.
It's very simple to do.
Next for a modern app
you might be wanting
Next for a modern app
you might be wanting
to use a streamlined toolbar.
So here's a screen shot
of a normal application.
The things to notice here is
it's got an explicit titlebar
or, sorry, it's got
explicit title up at the top,
and you would have an icon there
if the app actually had
an icon for this document.
If I go to using a streamlined
toolbar that title disappears,
the toolbar is pushed
up a little bit,
and the window buttons,
the close button, etcetera,
are directly in line with
that titlebar/toolbar area.
To do this it's really simple.
And all you have to do is set
the title visibility property
to .hidden, and that
title will be hidden,
the toolbar will be pushed up,
and everything will be in line.
When do you want to do this?
You probably want to do this
for applications that are kind
of like shoebox applications,
for instance maps, calendar,
system preferences, things
of that nature that are kind
of like one window applications
are good for this type of look.
of like one window applications
are good for this type of look.
So what other things
could you want to do?
Well, you might want
to complement
that titlebar/toolbar area with
accessory view controllers.
For instance, you might want
to have an accessory view
controller that's right below
the titlebar, and by
doing this you want it
to automatically be blurred
without you having
to do anything.
The size to be automatically
changing
as the window is resized without
you having to do anything.
So how do you do this?
It's really simple.
We have a view controller
subclass,
called NSTitlebar
AccessoryViewController you can
set the view to that
view controller.
In addition, it exposes
a layout attribute.
So the layout attribute could be
set to .bottom which means, hey,
this accessory is going to be on
the bottom of the titlebar area.
And notice the text appeared
below it because, again,
we're using the proper
content layout wrapped
in content layout guide
without having to do anything.
In addition to the bottom, the
layout attribute could be set
In addition to the bottom, the
layout attribute could be set
to leading or trailing.
And this means you get a
accessory view controller
up in your titlebar area
such as a register me button
or something else that
you might want up there.
New to macOs 10.12 is the
ability to use leading
and trailing as opposed
to using left and right
which were published before.
We prefer you to use
leading and trailing
because it allows your
window to work better
when you're using a right
to left localization.
So, for instance, if I'm just
running this in pseudo right
to left mode you can see
that that accessory view
controller automatically flips
to the other side without
having to do anything.
Next we, the system,
may actually be adding our
own accessory view controllers
to your window.
For instance, we might be using
it for creation of tab windows
where we aggregate
multiple windows
into one sort of virtual window.
So how do we do tab windows, and
what do you do as a developer?
So how do we do tab windows, and
what do you do as a developer?
You don't really
have to do anything.
If you order a window front what
we do is we look for windows
that are similar to that window.
And by similar I mean we look
for the tabbing identifier
property.
If it has the same tabbing
identifier we're going to prefer
to aggregate those windows
into one single tabbed window.
The windows themselves are all
considered visible even though
it might be in a hidden tab.
But we actually hide them
with respect to core graphics.
If you're using NSDocument,
a lot of the things
such as the plus button inside
the tab bar work automatically
without you having
to support anything.
But if you aren't using
NSDocument you can implement a
responder chain method
new window for tab
to create a new document or a
new window on the plus button.
For more information check out
the, What's New in Cocoa talk
to see more details on this.
For more information
on the titlebars,
the accessory view controllers
and how they interact
with full screen, I highly
recommend seeing the talk
from 2015 on improving the
full screen window experience.
from 2015 on improving the
full screen window experience.
Next let's talk a little
bit about core animation.
So what is core animation?
Well, it's a graphics
rendering engine that does a lot
of the work on the
GPU as opposed to CPU.
So everything can be
very fast for scrolling.
We can do very fast
and smooth animations.
And the base component
of this is a CA layer.
How we actually create
your layout
and your views using
layers or views themselves.
So this is an example
animation where you might have
like a background
layer, a middle layer,
and then a front layer
actually doing an animation.
So you want to compose your
views of multiple subviews
to create the final look.
Let's take a look at some of
the properties in core animation
or CALayer that allow
you to set the contents
of what you see on screen.
Speaking of contents, CALayer
has a contents property.
Speaking of contents, CALayer
has a contents property.
The contents property can be
an NS image or a CG image ref
that actually represents
what you see
for that layer's contents.
But you could also set
other properties on CALayer
such as the background
color, the border color,
the border width,
the corner radius.
There are more properties you
can take a look at CALayer
to see the other things that are
available and how to control it.
So these are some of
the intrinsic basic ways
to actually set how
a CALayer will look.
And we're going to talk a little
bit more about this in a second.
But what you should be doing
is you should be using a layer
backed view.
So you're going to be using
NSViews set once layer to yes
which I'll show in a
second how to do that,
and that will implicitly
create a layer for you.
And that means that you can
provide the layer contents via
draw method.
But if you're using layers we'll
actually have two more important
methods, updateLayer
and wantsUpdateLayer.
And in just a second I'm going
to show how these come into play
And in just a second I'm going
to show how these come into play
and when you'll want
to use them.
So what do we actually recommend
for using core animation
or when should you use core
animation and layer backing?
Well, we recommend that
you layer back your windows
content view.
And when you layer back one
particular view all the children
will automatically get
layer backed for you, too,
without you have
to opt into a layer
for every individual child view.
We recommend that
you layer back views
and you not use CALayers
directly added as a sublayer
because that takes care of some
things automatically for you
such as when a layer is
shown on a retina display.
We'll do some more setup
that takes care of the work.
Turning on layer
backing is really simple.
On the content view of your
window you can set wants layer
to true and code.
Or, of course, you can just go
ahead inside interface folder
and check the checkbox
for Core Animation Layer
on a top most view.
So an important property
on layer backed views is the
layerContents RedrawPolicy.
You want to set this
.onSetNeedsDisplay
which is not the
default value for NSView.
What this means is you
as a developer whenever you
want the contents of your layer
or the view to change you
have to call SetNeedsDisplay.
This is slightly
different semantics
in the way normal
views would work
where they might actually
redisplay as they're moving
across changing a frame origin.
So it's something you need
to explicitly opt into,
and it's better for performance
particularly with animations.
So how do you actually get
contents into your layer?
So let's say your view is dirty.
You mark SetNeedsDisplay.
And what we do is we
ask your view, hey,
what do you do for
wantsUpdateLayer?
What's your answer
for wantsUpdateLayer?
And here we fork.
If you say yes
for wantsUpdateLayer then we're
going to call updateLayer.
for wantsUpdateLayer then we're
going to call updateLayer.
If you say no we're
going to call drawRect.
So this is a complete fork.
It's an either/or.
Then you may be wondering, well,
when should I use
updateLayer versus drawRect.
This gets back to what I was
showing about core animation
and NSLayer properties.
So if you can represent your
views representation directly
by setting CALayer properties
then you probably want
to use updateLayer.
It's going to be more efficient.
So if you can set
the layer contents,
layer background color,
prefer to do that.
If you can't, then that's
the point where you want
to override draw or drawRect
and actually do your manual
drawing inside of the subclass.
The thing about doing
this is that every view
which implements drawRect
will get its own unique little
backing score which you
can think of as an image.
And so if you have a lot of
those it might be expensive
in particular for memory.
Let's take a quick look at an
example of using update layer
Let's take a quick look at an
example of using update layer
and how you would use it.
So first of all you'll
override wantsUpdateLayer,
say that you want to get
an updateLayer callback
by responding with a true.
And then you're going to get
a callback to update layer
where you can actually
set the layer properties.
So we access the view's
property, self.layer contents
and the self.layer
backgroundColor
or whatever other
things you want to set,
and you set it at this time.
So that was talking about modern
views and creating a modern look
for the titlebar/toolbar.
Let's talk about drag and drop
and do some event tracking.
So modern drag and drop,
what you should be doing is
adopting drag flocking as shown
in this video right here.
Drag flocking is where each
individual item will move
independently and flock
together or unflock together.
And when you let go of the mouse
they'll actually all flock back
to where their original
location was.
to where their original
location was.
To use drag flocking you
just use NSViews method
beginDraggingSession,
and you pass an array
of the actual items that you
want to have be dragged around.
It's very simple to do.
For more complex
controls like a table view
or collection view we
provide delegate methods
for you to adopt these.
So for table view you should
really be preferring the
delegate method.
tableView pasteboardWriter
ForRow
where it can provide an
individual NSPasteboard writing
item for every item in a table
view that's being dragged.
As opposed to the older
method, tableView writeRowsWith
to a pasteboard where you
would have written everything
in one go to the pasteboard.
Similarly, collection view
has two delegate methods
that look very similar to that.
And for collection view
you should also prefer the
pasteboardWriter
ForItemAt version
as opposed to the other version.
So with drag flocking new to
macOS 10.12 is the support
So with drag flocking new to
macOS 10.12 is the support
for drag file promises.
This may be a reason why you
weren't using drag flocking
up until this point.
And you can use NSFile
PromiseReceiver
and NSFile PromiseProvider
to do reading
and writing of file promises.
Take a look at the talk,
What's New In Cocoa
for more information and
more details on this.
So let's take a look
at event tracking.
And let's say you
have your window here.
And let's say it has a
button up at the top.
So you have this button.
And what you want to happen
is that when you click
on that button down and up it
works like a normal button.
You will do some action
like showing a popover.
But you also want to be aware
you click on that button,
and if you actually drag the
mouse it starts a window drag.
So how to get both those
behaviors together in a proper,
modern way that works well
with modern system features?
Well, first of all let's
talk about event tracking
and how you'll track
events to do this.
and how you'll track
events to do this.
And this window has a method
track events matching mask
with a timeout mode handler.
And the handler is
a block callback.
We prefer that you use this
as opposed to the older method
on NSApplication which was
nextEvent matchingMask.
With the older method you
would create your own loop.
With the newer method you
just get a block callback
to do your work.
Taking a look at a
block callback let's say
in that button we subclass
and override mouseDown.
The first thing we're going
to do inside this mouseDown
is call window track events.
We want to track
all of the events
for the drag and
the leftMouseUp.
If the user did a click,
just a down and back up,
then we're going to just be
like, oh, we're going to stop
and we're going to say,
oh, we're going to call yes
for super because that way
everything will work normal
like an NS button.
But here's the extra part.
Let's say you click and
you drag, and if you click
Let's say you click and
you drag, and if you click
and drag inside this
button we're going to figure
out if you went far enough, if
you went far enough we're going
to pass off to the window
to do a performDrag.
And so what that means is when
you pass off to the performDrag
of the window the window and
the system is going to take
over dragging the
window at that point.
You should not be dragging
and moving a window
by calling set frame again
and again on a window.
If you pass off to the system
as soon as you pass it off,
if your application hangs,
then that window will continue
to be moveable even if your
application is spinning.
In addition, other
system features will work
such as space switching,
the spaces bar will drop
down when you move to
the top of the window,
window snapping,
window alignment.
Any other new system
features we add
with window movement
will automatically work
without you having
to do anything.
So we encourage you to
pass off to the system
by calling performDrag.
So that was talking
about drag and drop,
So that was talking
about drag and drop,
a little bit about
event handling.
Let's talk about container
view controls and how
to handle those properly.
So container view controls we
should be using the view based
table view at this point.
And you do that by using the
delegate method table view view
40, or inside of an interface
builder you can set things
up directly in interface
builder itself.
And why you want to do it is
so you can get what we saw
on that video new features
such as swipe to delete
which only exists in the
view base table view.
To do the swipe to
delete is very simple.
There's a table view
method, row actions for row
on a particular edge, the left
or the right edge or, sorry,
leading or trailing edge.
And you can return an
array of one or more
or a zero or more row actions.
In the NSTableView
row action allows you
to create a string value for
what the title is of the button
and a handler to actually
do the code that happens
and a handler to actually
do the code that happens
when the user clicks on the
button or swipes far enough
to actually invoke it.
Next let's talk about ScrollView
and that complex control.
So for using all these
technologies we talked
about let's say that we
take the ScrollView --
or let's say we take the window
and set the window property,
titlebar appears transparent
which makes the titlebar
appear transparent.
Applications like Messages
take advantage of this
to allow the contents
to show underneath.
So here we can see
what's happening.
That ScrollView on the side is
showing the content underneath
the titlebar/toolbar area
and would automatically
blur with it.
But this presents a dilemma.
How do you get that ScrollView
to automatically be inset
so that you don't have to add
in like an extra empty row
or anything really weird?
And it's very convenient
and easy to do this.
ScrollView has a method or a
property called contentInsets
that allows you to drop the
content down a little bit.
that allows you to drop the
content down a little bit.
In fact, we can do
it automatically
so we have a property
automaticallyAdjust
ContentInsets.
And if you set that to
true then what we're going
to do is ScrollView
is going to use KVO
to track the contentLayoutRect
that we talked about earlier
and automatically set
the content insets
to be the appropriate
value for you.
But you as a developer may
actually want more control
of the content insets.
You might want to drop
it down even further
and add some other
accessories there such as
like a search field
or something else.
And an example of where we do
this in the system Mail drops it
down a little bit and adds
another sort indicator.
That's how we accomplish
things like that
in our system applications.
Next let's talk about
auto layout.
You should be using auto layout,
and you should be using
base localization.
Which means that all your nymphs
should be in base.lproj instead
of multiple copies
of different folders
and different localizations.
You shouldn't use fixed
width constraints.
You shouldn't use fixed
width constraints.
You want to use controls to have
intrinsic content sizes instead
of hard coding sizes.
Prefer to use StackView.
Use leading and trailing
attributes.
All these things are right
for creating a properly
localized application.
But let's take a look at some of
the localization options in IB.
In particular let's look
at this Text Direction,
Layout and Mirror section.
So the text direction
has three values.
It has natural, left to
right and right to left.
Natural means that the actual
control is going to look
at the string value that
you set on that control
like a text field, look
at the string value.
And if that string is a right
to left string then
we will actually put
in the direction right to left.
If it's a left to right string
we'll put it in left to right.
Or you can manually control
it by setting these to left
to right or right to left.
The next property is the
userInterface LayoutDirection
which is key to layout.
And it has either left to
right or right to left.
And it has either left to
right or right to left.
System controls such as table
view will look at this property
and may do things
like automatically
flipping the table columns
when it's set to right to left.
The default here is
based on the app value.
But the interesting thing
is the mirroring property
because it's a little confusing.
So if mirroring is set
to automatically then
that userInterface
LayoutDirection,
the last property we just
looked at, will automatically go
from left to right to right
to left when it's in a right
to left localization
and vice versa.
And it will also flip other
properties automatically
like the cell image
position for a button.
So the button's image is on
the left and we'll flip it
and put it on the right
for you automatically.
But one property that we won't
flip is on text alignment.
If you have it set
to center, justify
or natural those don't
really make sense to flip
so we won't flip them.
So then you might
be wondering, well,
when do I actually not
want to have mirroring?
Well, you might want to
have mirroring set to no
when you want a control that has
a very physical representation
like a play button,
a fast forward button
or a rewind button.
And then these are all
interface builder things
so how do you actually
do this in code?
So in code you actually have
to look at your controls value
for the user interface
layout direction.
If it's set to left to right
then you're going to have
to say, hey button, I want
that image to be on the left
when it's in a left
to right localization.
And I want that image to
be on the right when it's
in a right to left localization.
You have to manually do this.
But to make it a
little bit easier
in 10.12 we added a
convenience method on button
and an init method which allows
you to pass a string, an image,
a target and an action and
have the flipping automatically
happen for you.
So that's it for
my first section.
I'm going to bring up Jeff
to talk about appearances,
storyboards and some
other Mac features.
Thank you.
[ Applause ]
>> All right, thanks, Corbin.
We got an absolute ton of
stuff to cover in this section
so I'm going to dive right
in with system appearances.
So here's our app.
We've been looking at
it for a while now.
And we're trying to
develop our UI a little bit.
And we've decided that we want
to adopt this really
slick dark look.
This is kind of characteristic
of Pro apps usually.
And we're nothing
if not ambitious
with our little demo app here.
But look at this, this
looks really complicated.
We have an entirely different
system, Window Chrome,
we need all our control
artwork to change,
segmented controls,
buttons, sliders.
And all of our text
labels need to invert
from dark text to light text.
Which seems like an awful lot of
work, but actually we can do it
in one line of code
using NSAppearance.
All we've got to do is create
one of our system appearances,
assign it to the window,
and it's automatically
applied to everything within.
and it's automatically
applied to everything within.
You can think of an appearance
as sort of a pallet of colors
and artwork that we use
to resolve how to draw all
of our standard system controls
and also all of our
named colors.
These are things like
label color, control color.
And for that reason
it's really important
that you use these
colors when applicable.
Not only do you fit in better
with the entire system theme,
but you'll continue to fit in if
that theme changes in the future
or if you change your
appearance in the future.
So let's take a closer look.
Here we've got a panel.
It's got tons of nice labels,
controls, etcetera, on it.
And all we have to do is
just apply dark appearance,
and we see a pretty
dramatic change.
Our control artwork has changed,
and all of our labels have,
of course, inverted
from dark to light.
We can actually take
it one step further
and apply this cool
vibrancy effect.
You may have noticed a slide
ago that the appearance
that we applied is
called vibrant dark.
That doesn't mean that you're
obligated to use vibrancy,
but it does mean that
the artwork looks great
in a vibrant context.
And to get this appearance
all you need to do is add all
And to get this appearance
all you need to do is add all
of your controls as a subview
of NSVisualEffect View.
And you'll automatically
get this great behind window
blurring and also
this cool blend effect
for everything on top.
Now, you might be thinking
that's great and all,
but my designer has this
really cool like specific color
that they want for
our text labels,
and so I'm not going
to use label color.
I think that I'm not planning
on changing from dark to light
or vice versa so I'm
fine, right, I'm safe.
Well, let's have some
food for thought.
Let's check in with
the accessibility pane,
and we're going to
turn on this setting
in the middle here
called Increase contrast.
And let's see what
that does to our UI.
On the left we again
have our standard panel.
But on the right
we have that panel
with Increase contrast
turned on.
And although we haven't
inverted all of our colors,
this transformation is
every bit as dramatic
as the transformation
from light to dark.
You can see that the window
background color has gotten
lighter, our text is
darker at every level,
and all of our controls have
gotten this really nice,
and all of our controls have
gotten this really nice,
bold outline.
It makes it really pop
against the background.
Now, when you provide a
hardcoded color value we can't
really second guess that.
We can't adjust for
settings like this.
And so if you don't supply
things like label color,
secondary label color, you
might be doing a real disservice
to people who need settings
like increased contrast
to get the most out
of their Mac.
Now I hinted a moment ago at
visual effect view and vibrancy.
We're not going to go into
depth on that API right now,
but I would refer
you back to our talk
in 2014 that's Adopting
the Advanced Features
of the new UI of OS X Yosemite.
And that's appearances.
It's a real simple way to theme
your application while remaining
harmonious with the overall
look of the operating system.
Next up storyboards.
Storyboards are a
technology that will allow you
to design not only the
individual components
and the views that comprise
them for your application,
but also visually
design the relationships
between those components.
In this case when I say
component storyboards operate
In this case when I say
component storyboards operate
in terms of controllers,
there's our window controllers
and view controllers.
And we connect them together
with these things called segues,
those are the arrows up
there on my screenshot.
And segues abstract away
all of the glue code
of putting these
components together.
Those are things like adding
subviews, adding constraints,
creating popovers,
really just housekeeping.
Now, one thing that we need to
think about with storyboards is
when we have separable
components like these,
you can see I've got
a split view here,
and you can see the
storyboard that creates it,
all these new little components
do their own little thing,
that every piece of UI
has some kind of data
that it wants to
look at or modify.
And we can't necessarily
just drag outlets or actions
from one scene to another.
Of course, if we did that
they would not be separable
components anymore, and then
we have defeated the purpose
of this whole thing.
But sometimes this
data that we're working
on doesn't really live conically
in the scene that we have here
on doesn't really live conically
in the scene that we have here
in down in our leaf node.
Sometimes it might
live all the way
up on the window
or in the document.
So how do we propagate
that data all the way
up from the top level
down into our leaf nodes?
Well, we have a couple of
rules of thumb, not hard
and fast rules but
just a couple of ideas
that we think are good
for handling this.
For one, dependencies should
generally cascade downward.
If you're wondering, if you
need some kind of rule of thumb
for this just follow the
arrows in your segues.
If nothing else this gives you a
nice unidirectional information
flow in your application,
and that makes it a lot
easier to reason about.
Next try to reduce the
amount of assumptions
about your UI structure that
you hardcode into your code.
Now, you just got done designing
your UI and your storyboard.
And if you hardcode
assumptions about how
that structure is put together
in your code, now when you want
to make a change to your
storyboard you're going back
and forth.
You've got to change your
code, change your storyboard,
and now you're fighting
yourself.
Let's not do that.
Let's have our code focus
on our data and focus
on designing our interface
and interface builder.
And one technique that we
can use to address that is
to use protocol conformances
to work really generically
across our UI.
So here's an example.
Let's say we have
this property here,
it's up on our window
controller,
and we want to automatically
provide that property
to anything in our view
hierarchy that understands it.
So in our didSet we're
going to go ahead
and call this propagate method.
And we've got this protocol
that we've defined off screen
called photoControllerConsumer.
And this just says I
know how to do something
with a photoController.
And so if we look at a
child ViewController and see
that it conforms to
that protocol we can set
the property.
And then we also automatically
propagate recursively
to all of its children.
But what about things like
popovers or sheet presentations,
things that are kind
of on demand?
These probably don't exist
when you're setting
a property like that.
And we need to be able
to provide their data
on demand before they
show up on screen.
And that's exactly what the
prepare for segue method is for.
This is called on the
presenting view controller
or window controller at the time
that the presentation occurs.
And in this case instead of
doing something like inspecting
or segue identifier forcibly
casting to the controller class
that we expect it to be
and then doing some kind
of specific setup, here we're
just doing the exact same thing.
We're checking out what
protocols it conforms to
and then setting the
property appropriately.
And what this does is
it changes our logic
from focusing really strictly
on identity into capability.
We're saying I have
this knowledge,
and anything that's presented
off of me could potentially gain
that knowledge automatically
through a protocol conformance.
It's really handy.
More about actions.
It's really frequent
that the best object
to handle an action that's
triggered in UI is not actually
in the same scene that
the control is defined in.
in the same scene that
the control is defined in.
And this is exemplified
by menu items which are
in their own little scene
completely disconnected
from your UI.
And luckily we have a
really great mechanism
for handling this case, and
that's the responder chain.
If you're coming from
a platform like iOS
where maybe you don't use
the responder chain quite
so heavily, that is that little
orange cube that is in the top
of every storyboard scene.
That's a proxy for
the first responder.
And so if you hook up
an action from a control
to that proxy it will
automatically be sent
up the responder chain when
the control is invoked.
But what if there's no
object in the responder chain
that handles your method?
Or what if in the case of zoom
in it's not always
appropriate to send that action?
For example, we might be
at our maximum zoom level,
and so we don't want our zoom
in button to just do nothing.
We should be able to look
before we leap with our action.
And that's what UI
validation is for.
So let's take a look
at a block of code
that would inspect UI
validation to determine
that would inspect UI
validation to determine
if a control is actionable.
The first thing that we're
going to do is ask NSApp
for the target for an
action from a control.
And what that does is it
automatically walks the
responder chain and
finds some object
that implements that action.
Or it might not find any action,
or it might not find
any object at all.
The first case we want to look
at for the results
there is any object
that implements the
NSUserInterface
validations protocol.
And this just means you
can ask me proactively
if a control is valid.
And so if you're on the
other side of this equation
and you have a control that
is conditionally valid you can
implement this method.
And controls like NSMenuItem
and toolbar item will
automatically validate
against that method.
So we can ask that method.
And then, of course, we also
have the cases of some object
that just handles the method
unconditionally or no object
at all which clearly means
that we're not prepared
to do that action.
And there's just some techniques
for deal with storyboards.
So now we've been talking
a lot about the kind
So now we've been talking
a lot about the kind
of design time facets
of building your app.
Let's look at some more
user facing features
that really help you make
the most out of our platform.
And the first of those
is user activities.
NSUserActivity is this
object that describes kind
of what your app is doing
right now, what is it viewing,
what is it editing, etcetera.
And this is the object
that's used by Handoff
to move those activities
between devices.
It kind of takes your whole
context and moves it between.
I won't have time to go into the
entire API in depth right now,
but I do want to
highlight how simple
of an object this really is.
You construct it
with an activityType,
and that is a unique identifier
that describes the
activity that you're doing.
And this is also a key that you
would put into your info plist
to declare I'm a
good app to pick
up this activity
on another device.
And then you just fill in
some basic configuration info
to describe your activity,
a nice user facing title
to describe your activity,
a nice user facing title
and also some user info.
Basically just the most basic
amount of data necessary
to jump back into that
task at a later time.
We want to keep this
dictionary small
because it's often
transmitted wirelessly,
and we definitely want our
handoff interaction to be fast.
We also get an opportunity
in the delegate
to add the absolute
latest information
about our context right before
or at some point before
we perform a handoff.
And this is called at some
point after you mark an activity
as needing to be saved.
And we'll call this
method for you
so you can fill in
that latest data.
Now, how do we decide
what activity is the
current activity?
Because, of course, we need
to certainly determine what
is being looked at right now.
Well, you can manually manage
that with the become current
and resign current
methods on NSUserActivity.
That means that you're
probably going to have
to follow your user around
your application figuring
out what they clicked
on and trying to figure
out what they're doing.
AppKit can make this a
lot easier by allowing you
AppKit can make this a
lot easier by allowing you
to attach activities
to the responder chain
for automatic management.
For an example of this let's say
we're building a calendar app
which looks a lot
like our calendar app.
And we have two activities
that we want to put
into our responder chain.
We've got a higher level
activity for the view
that we're looking at,
this whole, this day view
that we have here, and then
we have a much more specific
activity for the specific event
that we want to view
right there.
And by attaching these to the
responder chain when we click
that event and make it
their first responder
that activity becomes
the closest thing
in the responder chain
through the first responder.
And it becomes current
automatically.
Now, you may not have
considered Handoff
or the NSUserActivity
API before,
maybe you don't have
a companion iOS app
or you just haven't
really thought
that Handoff is especially
compelling for your application
and your specific needs.
But activities are
not just for Handoff.
New in Sierra we
have Siri on the Mac,
New in Sierra we
have Siri on the Mac,
and Siri uses the
current activity
to provide context to commands.
So, for example, if you
say remind me about this
at some date, Siri is
going to infer this
to mean your current activity.
And it will actually
even take that activity
and embed it inside
of your reminders
so that you can pick
it up in the future.
So we see activities as
a generalized mechanism
for describing this
kind of information.
It's not just for Handoff.
Now, for full information
about the Handoff API I
recommend checking out this talk
from 2014, Adopting
Handoff in OS X and iOS.
That covers a lot more
of the advanced topics
like continuation streams
which allow you to move a lot
of data between devices.
The next feature, resume,
which we sometimes
call state restoration.
It's one of the best
features on the Mac is
that when you quit
an application
and then relaunch it, it
comes back exactly the way you
left it.
In fact, that happens if
you crash an application
or even reboot your machine.
Everything just comes back
exactly the way that it was.
Now, clearly to accomplish
this effect we need to save all
of the UI state that builds up
over time and then restore it
when the app relaunches.
But clearly we don't want to
save that state in our model.
It doesn't really belong there.
So a state restoration API gives
you a distinct place to save
that UI state separately,
and it gives you a good place
to restore that state
when you're launching
but before your UI
has gone up on screen.
You enable it on a
per window basis.
It's pretty simple.
You just say isRestorable
is true.
And then you may provide
a restoration class
that just handles the act
of creating your windows
from the encoded data.
And good news if
you're using NSDocument,
NSDocument handles
this all for you.
Now, what kind of state
might we want to restore?
Well, we might choose
to save the current tool
that we have active in our app.
We might also want to save
the state of the sidebar,
what's selected, what
our scroll offset is.
what's selected, what
our scroll offset is.
How do we do that?
Well, if you've used
NSCoding before it's really,
really simple.
The first method
encodeRestorableState
with coder.
You implement this on
any NSResponder method,
and then it's just
like using NSCoding
but for your controllers
effectively.
Another important call is
invalidateRestorableState.
And this just says
whatever my backing data is
for encodeFestorableState
has changed in some way.
And we'll schedule to
make sure that we save
that state again
sometime in the future.
And then finally
restoring is just as easy.
It's exactly like you'd expect.
It's a lot like a
init with coder.
All you have to do is call
super, decode all the saved data
that you've encoded before,
and then set up your UI based
on that information
that you've encoded.
Now, that's pretty easy,
but we can actually
make it even easier.
All you have to do is
implement a class method
All you have to do is
implement a class method
restorableState KeyPaths.
Of course, we're
going to ask super
because that's the
polite thing to do,
but then we also append
our own key paths.
And these are the
properties that you want
to have automatically
restored or saved
and restored by the system.
These properties need to be KVC,
that's key value
coding accessible
because we access them by
key path, and they also need
to be observable so that we
can observe them and invalidate
or state when they change.
And that's state restoration.
The third and final technology
we want to talk about,
documents in the cloud.
So once upon a time
to opt into documents
in the cloud you actually
had to proactively opt in
and create a container.
But these days with iCloud Drive
and now especially
now iCloud Desktop
and Documents it's
more likely than ever
that your app is working with
documents that live in iCloud.
Now, this is important because
new in 10.12 local copies
of documents might be
evicted to free up space.
And this means that you might
be working with documents
that aren't actually
on the local hard disk.
So how do we handle this?
That seems pretty scary.
Luckily, first off if you're
using NSDocument it handles
everything for you so
you're in great shape.
But if not you need to make sure
that you're using
file coordination.
If you register yourself
as a file presenter
with the file coordination
API we will make sure
that your document is not
evicted out from under you
which is a good thing.
And then next if you
use file coordination
to coordination your IO on
those files we'll make sure
that we schedule
your IO conveniently
after the entire file
has been downloaded.
And that's documents
in the cloud.
Now, there are a
couple of technologies
that we didn't have time
to talk about but I do want
to give an honorable mention to.
The first is asset
catalogs, faster and smaller
than having loose
assets in your bundle.
And they can also
help you with things
like wide gamut and
right to left.
Accessibility, extremely
important.
Cocoa puts really powerful
accessibility technologies
within an arm's reach
of your application.
And it's extremely
important that you make sure
And it's extremely
important that you make sure
that you learn to use voice
over, learn to use all
of these accessible
technologies and make sure
that your app is
doing the right thing.
You would never ship an app with
a visibly broken user interface,
so don't ship an app that
has a UI that's broken
to accessibility.
Sandboxing and also XPC
services, two different
but somewhat related
technologies
in that they help you isolate
code from the rest of the system
and from other processes.
Sandboxing is, of course,
mandatory for the Mac App Store,
but it's appropriate
for every app really.
And XPC services can help
you separate out code
into separate processes.
This is really great
for things like, say,
code that's handling
untrusted data off the network
or doing some kind
of parsing work.
We all like to think that our
code is perfect but, you know,
we also wear seatbelts in our
cars, and we're very happy
that it's there if
something goes wrong.
So we have covered an
absolute ton of content
So we have covered an
absolute ton of content
in a very short amount of time.
So I want to rewind
and recap and make sure
that we all remember what
we just talked about.
And we started off by looking
at creating a modern look
with our modern view and window
pipeline making sure you get
great animation performance.
Then we talked about drag
and drop and event tracking,
making sure that you get the
really cool drag flocking
effect, modern drag
file promises.
Then we covered container views
likes scroll view and table view
and making sure that we
localize them correctly.
Then we walked into system
appearances, storyboards
and also a couple of
modern Mac features
that really make
the platform shine
and really take advantage of it.
Here's the permalink
for our talk.
You should find related
resources there including the
download for our demo
application as soon
as it becomes available.
All of our related
sessions are in the past.
I hope you were able
to attend them.
If not, then you
should definitely check
out the videos online.
And that's a wrap.
Thank you for attending.
[ Applause ]