Examine the technologies that make a modern macOS app. Come learn from the experts how to harness the power of Cocoa to build powerful, immersive apps. Whether you're a seasoned Cocoa developer or new to the platform, this session is for you.
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.
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 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 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 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. 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 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? 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. 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.
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 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.
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 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 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 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 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 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, 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 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. 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 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.
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.
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.
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 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, 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 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 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. 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 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 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 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 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, 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.
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 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 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 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.
Looking for something specific? Enter a topic above and jump straight to the good stuff.
An error occurred when submitting your query. Please check your Internet connection and try again.