App Frameworks • OS X • 1:00:26
Learn the ins and outs of making your app Lion-ready. Use Lion's new Full Screen mode, and adapt to changes in the look and behavior of standard controls. This session will include tips and sample code that you can learn from and put immediately to work.
Speakers: Patrick Heynen, Dan Schimpf, Troy Stephens
Unlisted on Apple Developer site
Downloads from Apple
Transcript
This transcript was generated using Whisper, it has known transcription errors. We are working on an improved version.
Hello, and welcome to session 113, Full Screen and Aqua Changes. My name is Troy Stephens. I'm a software engineer on the AppKit team, and we have a variety of interesting topics for you today. They are all about interface, all about interface changes in Mac OS X 10.7 Lion. First up, if you're eager to learn about Lion's new Full Screen mode and how you can enable your own applications to participate in this powerful new system feature, you are in the right room. We're gonna walk you through it, show you just how easy that is to do. Next up, popovers, a brand new standard user interface element on Lion.
Very handy, couldn't be easier to use. From there, we'll continue our tour of the new Aqua, talk about some of the changes to the way standard controls are rendered, different rendering effects that are applied to controls in Lion, things that differ from previous system versions that you might want to adapt to in your own UI design work and custom control work. And last but not least, we've made some subtle but important improvements in our resolution independence architecture for Lyon.
The new model extends farther down the stack all the way to the window and screen level. It is much more comprehensive. We believe it is better suited to carry us all into the future together. So that's a lot of ground to cover. Let's start by diving right in and immersing ourselves in Full Screen mode.
As you saw in the keynote, Full Screen mode in Lion is not a per app state, but it is a per window state. You can take windows that are capable of being taken full screen into Full Screen mode. The system moves that window into its own new dedicated space.
Clutter gets out of the way. You're focused in on your content, but when you need your toolbar, your menu bars, they're available. You just flick the mouse to the top of the screen. I have all of my menu commands available to me. I can choose the magnifier tool. The menu bar gets out of the way. I'm back to focusing on my content. Now if I use a gesture to swipe to the right, I'm back to my desktop.
I leave that window in Full Screen mode. I can take another window Full Screen, and that window gets its own Full Screen space. If I swipe left, I see my Lion is still in Full Screen space. If I swipe to the right twice, I'm back to my desktop. I can always run home to Mission Control to get a top-level overview of all my spaces. Full Screen and not, I can navigate among them at a click or a tap of the trackpad.
We believe this degree of system-level integration for Full Screen functionality is going to be a big boon to users. It will enable this kind of Full Screen mode of operation to be accessible to more people than ever before and provide interoperability among apps. So we're very excited about it. typically look for in full screen capabilities in an application.
Well, there are different things that different users look for in different apps, but it all boils down to a combination of two things, typically. And the first of those is in the distraction-prone world that we live in, the opportunity to sneak off to a little secluded private office where there are no distractions, clutter is out of the way, and you can focus in on a single task.
I want to write my great screenplay. I want to do my digital painting. Whatever it is I want to do, I don't want to be distracted by shiny things popping up in my Twitter feed. I don't want to have my email on Red Count weighing on my conscience the whole time. I don't want to see that stuff. I want to get off to a full-screen space and focus on just what I want to do right now. Full Screen mode enables you to do that, of course.
And in hand with that goes the desire to make the most of available screen real estate, to maximize the use of what you've got. This is useful even on a 30-inch cinema display, but users who have an ultra-compact portable, for example, the 11-inch MacBook Air, will especially love you for giving them a mode where they can operate, where they can see as much of their content as possible, interact with it with as little intermediary UI as possible.
and it gives them a way to really use that screen space to its fullest. These user benefits taken together are so compelling that of course a lot of apps have gone ahead and implemented their own notion of full screen mode. So what are the benefits to our making this an integrated system feature and your adopting our notion of full screen mode? Well, there are a couple of things that it lets us do.
First and foremost, it lets us present a per window model for full screen mode, where this is the state of an individual window. So you can have some of your windows in full screen mode, some not, and different windows across different applications in full screen mode or not, and something that users can manage on a per window basis. This would be something that would be a lot of work for individual developers to do in their own applications. And even if you did all of that work, you wouldn't have the kind of cross app coexistence and integration that we can provide at the system level.
In addition, as with so many other things on the Macintosh, there's the opportunity for us to provide a consistent and uniform user experience. This enables users to learn a feature once in one application and leverage that knowledge, apply it to every application that supports that feature. So with Full Screen mode, the relevant things are the ability to recognize which windows can be taken full screen, standard user interface elements so that the user knows how to take a window full screen, how to get out of full screen, how to navigate, as I said, using spaces, using mission control, using gestures in and out of those full screen spaces so they feel like they're always in control and they know where things are.
So here we have a simple window. This is an Xcode window. And all you have to do is look in the upper right corner of the window and see that Enter Full Screen widget. That tells you that that window is capable of being taken full screen. I can click on that widget. My window gets taken into a new space that's created just for it to be in full screen mode.
And if I mouse up to get my menu bar to come down, I can see in the upper right corner of the menu bar, that's my escape hatch. I always have that exit full screen widget available to me, so I know I can get out that way. And again, I know I can navigate using gestures, spaces, and mission control.
So how do you enable this capability in your applications? Well, there really is. With so many things with Cocoa, we try to make the simple things simple. The basics really are very simple. There is one fundamental thing you need to do, and that's simply to tell AppKit which of your windows should be capable of being taken full screen.
Typically, in a document-based application, these are your document windows. Or if you have a single window application, it's your one main window. Just tell us which of your windows should have this capability. In addition, it's nice for you to provide an Enter Full Screen menu item for users who like to discover or find functionality through menus.
And that item is best placed in the View menu if your application has one. If you don't, the Window menu is a good fallback for that. And that really is all that you fundamentally need to do. Of course, there's always the opportunity to apply more polish, and there are some things you can do to make your apps really stand out.
One of the things you can do is have your Windows toolbar, if your window has a toolbar, automatically hidden and shown with the menu bar in the full screen space. That's real easy to do. I'll show you how. Another thing you might want to consider doing is providing a custom size for your Windows Full Screen frame. By default, the system will assume that you want to use the entire main screen, which may be appropriate for some apps if I have my month view in iCal.
I probably just want to stretch that up so that the boxes are as big as possible and I can see as much of the detail in each of them as possible. But sometimes you want a different frame size. We'll see that's not all as appropriate as in our demo app, and you can customize that easily.
While you're doing that, another thing you might want to consider doing is modifying or streamlining your Windows content for Full Screen mode. If you think one of the things that your users are looking for is a more streamlined UI that gets clutter out of the way, that gets distractions out of the way, and provides them only with what they need to operate in that mode, this might be a good time to consider altering your Windows layout, maybe temporarily removing some views or having things that auto-hide or just take less space.
And if you find yourself making extensive customizations of that nature, you may find it useful for your bookkeeping purposes to just create an entirely separate window for use in Full Screen mode. That window will reference the same content to the user. The illusion is that their original window was just morphed into this Full Screen window, but for your purposes, you can keep a separate window for that, and we give you the hooks to substitute a different window of your choosing when under Full Screen.
Lastly, the system provides a standard crossfade transition for taking the window into or out of Full Screen mode, but you have the ability to fully customize that, and we'll look at doing that in our demo, too. But, you know, there are five bullet points of customization potential there, but really it all boils down to the basics.
telling us which windows can be made full screen. You can do this with zero lines of code in Xcode's Interface Builder mode. This is part of a window's collection behaviors, as usually pertains to spaces and so on. There's a new full screen item. Just change that from Unsupported to Primary Window for your window class, for your window template in IB, and you're good to go. If you really like writing code, of course, there's a corresponding bit for this in your window's collection behaviors. You can set it using Set Collection Behavior.
and Dan Schimpf. And again, adding that menu item is a nice thing to do. Couldn't be easier because there's a template for this in Interface Builder. You just drag out the Full Screen menu item, drop it in your View menu or your Window menu. It's already wired up to send the toggle Full Screen message to your application's first responder, so any window in the responder chain will pick that up, validate against it if it can go Full Screen, and respond to that message to toggle the window into or out of Full Screen.
The menu item updates to say exit Full Screen when you're in Full Screen, and usually actually it has the recommended key equivalent of Command Control F wired up for it already. And to show you just how easy this is to do, we're going to take Sketch, which is one of our standard document-based application examples, and convert it to be a Full Screen capable app on Lion.
So I'm going to open up the Sketch project here. And this is Sketch just built as is. So by default, the system doesn't assume that it can be taken full screen. We can maximize the window using the Zoom button here, but that doesn't give us the same effect. We still have the menu bar in the way. We still have the dock, and we're really just covering up other windows on the desktop. So this isn't really full screen mode by any stretch. So let's open up the Sketch project.
And again, the basics of what you need to do, just tell us which windows should be capable of this. That's the first thing you need to do. So we're going to go into the DrawWindowNib file that holds Sketch's document window. And if I go in the Properties Inspector here, here you see the windows collection behaviors in these pop-ups. I'll just change Full Screen to Primary Screen and Save. And we could be good to go now, but let's add that menu item while we're at it.
I'll go to the Windows menu, since we don't have View menu. I'm going to type in Full Screen in the search here, and we want to be in the Object Library. There we go. Boom, drop it right in. And if I inspect this item, I can see it's wired up to send toggle Full Screen to the first responder. So great, we're good to go. Let's just try and build and run just like this.
All right, now we've got our Enter Full Screen widget. And I can toggle Sketch into and out of Full Screen mode. And I've got my menu bar available when I want it. I can bring up my inspectors and so on. I can do my editing. Everything works. My dock is down here if I mouse to the bottom of the screen, but it's out of the way most of the time. So we've got basic full screen support in here. We're still not using-- we're not really using the available screen space very well. We don't need that much space to see our document. We've got all this gray space on the side.
So let's look at doing something about that next. What we want to do here-- is customize the Windows Full Screen size, as I said. All of these kinds of customizations that you might want to do next are done in the Windows delegate. So typically, that's an NSWindowController. In Sketch's case, it's an SKTWindowController. So we're going to go into SKTWindowController and scroll down here to some code that I've written ahead of time for us. And disabled.
We'll enable that now. And the method to override is windowWillUseFullScreenContentSize. AppKit passes you the window that it's planning to take full screen, the proposed size for that window. Typically, this is the entire screen. You have the opportunity to return whatever size you want, taking that proposed size into account.
So what we're going to do here is we're going to assume that we want to keep the same zoom factor for the document and pretend that-- imagine that we have an infinitely large screen. What would be the ideal size that we want this window to be so that the user doesn't have to scroll, but they can see their entire content? And so what we'll do is we'll take the graphic view, which is the document view in the window, get its bounds, convert that up to the scroll view space.
We'll consider that our ideal content size. That's how much space we need to show our content at its current zoom factor. We'll need to account for the possibility of the scroll view having a border and maybe having legacy scrollers, which take up space. So we might need to grow that rectangle a little bit. That gives us our ideal window size here.
And then we just need to constrain that to reality, the proposed size, the actual screen size that we have to work with. So we'll take the smaller on each axis and return that value. And let's build and run. And now when we take Sketch Full Screen, it just slides on over into space. And we've got this nice linen background that the system provides for us. So we've got room to arrange our inspectors and other things around it, which is nice. And if I exit Full Screen mode, and Mike Schuch there.
Okay, now we're in Full Screen mode. Exit. and we're back. But the transition isn't exactly what we would want. And especially we can see this if we kind of try to exercise this a little in a more challenging way. Let's zoom in and suppose that we're only seeing part of the document.
We want to enter Full Screen mode. That's not really a great transition, I think. We would sort of expect that the existing content that we can see would just sort of slide on over into space, because we're not changing the zoom factor, and we would just reveal the other content. So maybe here the crossfade isn't really a good idea, and we want to implement our own custom transition.
I will show you the hooks that you need to do that. So there are six methods for this, three for entering Full Screen and a parallel set of three methods for exiting Full Screen mode. Four of those methods are really trivial to implement, so really we've just got two major methods to worry about.
These are also methods on the Windows delegate. The first thing you need to do to tell AppKit that you want to provide a custom enter Full Screen transition is override custom windows to enter Full Screen for window. You're given the window that AppKit means to take Full Screen, and you're expected to return an array of windows to be used in that Full Screen mode.
Now why an array of windows? Well, you might want to specify additional windows for any temporary animation effects that you want to create in Full Screen mode. What you're returning is the set of windows that are tagged as belonging to the Full Screen space that's being created. So any windows that you want to have appear in that Full Screen space and during the transition should be included in this list. In this case, we're just including our original window.
If we wanted to substitute an alternative window for Full Screen mode that's just dedicated to Full Screen mode, we could specify that instead. Right now, we're just going to use the window that we were given, but overriding this method tells AppKit that, well, we want subsequent methods to be invoked. We're doing a custom transition.
The real method to think about is Windows start custom animation to enter Full Screen with duration. You're passed in the duration in seconds of the transition that the system is going to use for everything else, including the menu bar, sliding spaces over. So you want to use the same duration in seconds, but it's up to you to set the duration of your animation to match that. And the first thing I'm going to do is stash off to the side what our Windows frame was before we went into Full Screen mode, because when we exit, it'd be nice if we just know which frame to restore back to.
We're going to persist this along with the Windows other persistent state, because the fact that a window is in Full Screen is a part of the Windows persistent state. If the user takes a Sketch window Full Screen and then quits Sketch and relaunches Sketch, not only will that document be reopened, but it will be reopened in its own Full Screen space. So that state persists.
We should persist the previous frame along with it. Next thing I'm going to do, I'm going to bump the window up above the menu bar level so that the window during--while it's in flight, it can fly over the menu bar instead of appearing on the screen. So that's the first step.
The second step is to set the window up above the menu bar level so that the window during--while it's in flight, it can fly over the menu bar instead of appearing on the screen. So that's the first step. The second step is to set the window up above the menu bar level so that the window during--while it's in flight, it can fly over the menu bar instead of appearing on the screen. That just makes things look a little nicer.
And the next thing I'm going to do here is set the Full Screen window mask bit in the Windows style mask. And this fundamentally is the thing that marks the window as being in Full Screen. It also has the side effect of hiding the Windows title bar. Now, AppKit would do this automatically for us, but I want to do it at the start of the animation so that I can start with just the remaining content right to the window and smoothly interpolate that to my new target frame.
Next thing we want to do is figure out how much space we have to work with. On a multi-display system, it's always the main screen, the screen with the menu bar. That's where windows are going to be put when they're taken full screen. All the other displays are available for inspectors and other auxiliary windows.
So we're going to get the size of that screen, and now we want to figure out what window size we want. Well, we've already written a delegate method for that, so we'll just invoke it right here. Use the code we've already written. We'll center that rectangle on the screen, and now we just want to animate to that new proposed frame.
And I'm going to use a new, minor new bit of API enhancement in Lion. Run animation group completion handler. This is a convenient way to wrap a set of animated actions in a begin and end grouping. And at the same time, specify a completion handler that will be executed after your animation completes.
And it's kind of nice syntax that that completion handler is specified later. This is the order that things happen in. So you specify the duration. We animate to the new frame using the windows animator. And then when we're done, we're going to clean up by restoring the windows previous window level.
Last but not least, there's a method that gets invoked if the system tries to take your window full screen but can't for some reason. This can happen for reasons beyond your control. Something else happens in the system state that you have no control over. For example, if somebody tries to take your window full screen and then immediately switches to dashboard, that would cause this kind of thing to happen. So you have any cleanup you need to do, such as temporary animated windows or other resources. This would be where you do that cleanup if you fail to enter full screen. We have nothing to do here.
So I've left that empty. Now, for exiting full screen, it's pretty much the parallel thing. Three methods. You specify which windows you want us to use. Then there's the method that's invoked to tell you to start your animation. And this is much simpler than the Enter case, because I've saved the frame I want to restore to. So I'm just going to go through.
Set my windows level to be above the main menu bar so that we can fly right over it. I'm going to set the style mask at the beginning again. In this case, I'm clearing the full screen window mask bit so that we can put the title bar back right at the beginning and slide the window continuously from that rectangle to its new rectangle. And we've saved the frame we want to restore to, so we just animate to that. And then when we're done, we'll restore the window's previous window level. There's one other trick in here worth pointing out.
Normally, a titled window, when you try to set its frame, it constrains itself to the screen. And in particular, the system prevents you from moving that top edge of the window above the bottom edge of the menu bar or the top edge of the screen. We want to be able to defy those limits during the course of an animation because when we're exiting full screen, first thing we're going to do is pop that title bar back to visible, but it's off screen, and the system's going to try and push it back down.
So we want to temporarily suspend that, and I've done that with a simple window subclass here. I've added SKT window. That has a Boolean property we can use to simply suspend that behavior and stop constraining to screen. And to make that work, I'll need to make sure I go into my nib and set the window's subclass to SKT window.
Save, and Build and Run. And now we have our sketch again. Take it full screen. OK, that worked, but that was the easy case before. Let's exit. Now let's zoom in and scroll around. Take it full screen. And there we go. We have a much smoother, more continuous transition. So it's a little more pleasing, puts a little more polish in the app. You can do really any kind of arbitrary effect you can implement here. The hooks are very general. We give you the way to do whatever kind of animation you want to do.
So one of the things that we saw there is that there's this Full Screen Window Mask that gets-- it's a bit that gets set on the window. When it's taken full screen, that removes the title bar. This is the thing to check if you have an NSWindow pointer and you want to say, hey, is this window in full screen mode? That's what you check for.
You might also want to ask, is the system currently showing a full screen space for any of my windows? And the way to do that is look in the application's presentation options for this full screen flag. That'll answer that question. Lastly, auto hiding the toolbar-- really easy to do.
There's an Auto Hide Toolbar presentation option, and your chance to provide this bit is in the Window Will Use Full Screen Presentation Options delegate method. Just override this or that bit in with whatever options AppKit is proposing to use for taking your window full screen. And there you go. Your toolbar will auto hide.
Lastly, we provide the full complement of Full Screen Will Enter, Did Enter, Will Exit, Did Exit notifications that you might expect, so that any part of your application can subscribe to these notifications and find out when your application is transitioning into or out of full screen mode and windows are changing state.
And there are convenient window delegate method equivalents for that. And really, that's it about full screen, but there's a lot more to know about the new Aqua in Lion, including popovers. And to tell you all about that, I'm going to hand you over to my capable colleague, Dan Schimpf. Thank you.
Hi, my name is Dan Schimpf. I'm a framework engineer on the application framework. And first thing I'm going to talk to you today is about popovers. Now, popovers are something we've seen in the system for a couple of releases now. There's a couple of examples here. Now, the popovers you've seen on the Mac so far have been custom implementations. Each application you've seen has had to do this themselves. Now, there's a standard control on the iPad called UI Popover Controller.
In Lion now, we have popovers. So what is a popover? A popover, well, loosely defined, it's a small chunk of user interface that's on the screen. And it's actually strongly relevant to a specific spot on screen. You click a button, it shows up, and there's actually an arrow pointing to the point where it came from.
So it's very contextual. It's a good place to have very contextual information that the user can see right then, because it animates. So the NS Popover is a new class in line to allow you to do this in a standard way in your class. And again, loosely defined, it hosts any content view relative to another view that's already on the screen.
So creating popovers is easy. A popover has an NSViewController inside. And if you haven't used NSViewController before, it's a new class that was in a few releases ago. And an NSViewController, well, it controls a view. And it's a generic class. A view is usually meant to subclass. And it just encapsulates the logic for controlling that view and whatever that view needs to do. And to help you out, it can actually load a nib very easily, or you can programmatically create the view. And it's just a great encapsulation for dealing with views and modular views and all their lib-noding semantics.
And it's a great class. So we've used it here to take advantage of all those great things. And it's actually great because you're going to want to use a new view controller for each popover that you use. So it's a great way to use this encapsulation. And you don't have to reuse any state.
So please just use a new view controller for every popover. pop over, and that'll allow you to-- and Dan Schimpf. Keep things fresh as you go along. So the popover appearances, actually two popover appearances that are built into the kit. There's the minimal one that you see on the screen here. That's sort of the default one you'll see most often probably. And then there's also a HUD variant that matches the HUD variant for Windows.
Showing popovers, there's one method to do this. Show relative to rect of view preferred edge. Again, remember, popovers are relative to a view on screen. So that's the view you pass in. That's the view that you want the arrow to point to. And then you pass a rectangle inside that view, and that's in the coordinate space of the view.
And that's just an area of that view that we'll use to point to. So if you just have a regular button and you just want it to come from that button, you can pass the button's bounds in, and it'll just use the button itself. But if you have a larger view, let's say like the iCal, and the last argument is a hint to us to say where should the arrow come from? The top, bottom, left or right? Now, the popovers can also move.
If you have an area inside of a view and that area moves, the popover can move to match it. Now, if it moves in a way that we can detect, say, for scrolling or the window moving itself, we will automatically figure that out and move the popover for you.
As you can see here, so the view is scrolling up, and the popover is moving automatically. You don't have to do anything for this. This happens automatically. But if it happens in a way that we can't predict, let's say back in the iCal Week view, if the user moves that event to another place, well, we don't know that. So you have to use Set Positioning Rect to update the original rectangle that you gave us, and the app will move the popover to the new spot.
Popovers also come with animations on by default. It animates on screen and animates the movement, as you saw in the last slide. So this, again, this is on by default. And that's what it looks like. So you only want to turn it off if you have something to do that you don't want the user to see.
So if you want to show the popover before the window even comes on screen, you can turn off animations, put the popover up, then you should turn them back on. So if you have to need to dismiss them, the user can get the right animation. Dismissing popovers, by default, you get the application defined behaviors. What that means is it acts like a window that you're probably used to.
You are responsible for closing it in the default case. There's a close method, or you can hook up a button to it. But there are also two other kinds of behaviors that we think will solve the problem of most popovers. And you should check them out and see if you can use them.
Transient means that any click outside of the popover will dismiss the popover. That's probably what you're used to. Any click outside of there, the popover will go away, and it'll animate. Semi-transient is a little more sticky. There are some events that it will let through, and it won't dismiss the popover. That's great for if you want to be able to switch to another application or another window and be able to refer to the contents of that popover.
[Transcript missing]
The one last nice thing that Popover can do for you is detaching. So if your application allows it, the user can drag the Popover, and it will create a standalone window with the Popover's contents in it, which is great for inspectors. They want to have an inspector here, and I want to drag it out and keep that inspector around. And so there's one call for that.
The Popover delegate has to implement detachable window for Popover. And what that is, you will hand us a window with the Popover with a window for that Popover. And so you should create a new instance of your ViewController to populate the window, because you may need to configure it a little bit differently to stick around.
So you hand us the window. Don't put it on screen. We'll put it on screen at the time that the user has done dragging it, or at the right time. So now I'm going to do a quick demo of how to put a popover in an application. OK. So we're actually going to start from new project. Call it popover.
So in my main menu, I just have a standard window. Let's get up. And so what I'm going to do is I'm going to drag out a button. And we'll call this the Info button. And this is just going to show my fake little inspector here. The next thing I'm going to do is search for popovers, and you'll see if I can spell popover.
When I drag this out into my top level objects, they actually create two objects. So we create the popover and the popover view controller. So this already comes configured out of the box, set up. So the popover-- and you can see that the popover's connections-- I can just select one of them-- it's already hooked up to the right view controller.
And so now, I'm just going to do everything in this nib. You'll probably find it easier with View Controller to do it in a separate nib. But just for the purpose of this demo, I'm going to drag out a view for the popover itself. View up. And so now this is going to be the view that the popover is going to show. So we can see it, let's just toss some radio buttons in there.
OK, now I need to write a little bit of code so I can show the popover. For that, up comes-- So this is my application delegate. This is the class that's gonna be controlling this. I'm gonna add an outlet for the popover, and then we're gonna add one action to show the popover. And this is really, really simple. As you can see, it just shows show relative to rect. And we're going to use the bounds of the button again.
Just make sure to pop that. We're going to use the bottom of the button, and we're going to put the arrow on top. Okay, now I need to make sure to remember to hook these up. We're gonna, now I've hooked up the action. and I will hook up the outlet.
Now, if I remembered all my steps correctly, all of this is necessary to show a popover. Now, by default, this is in the application defined behavior, so I can't -- if I'm clicking around here, the popover stays up. So let's look at some of the other behaviors. I'll go back to my inspector here and change it to transient. Now, build and run. Now, if I just simply click out, the popover closes automatically, and I can bring back more of them as I needed. So you should experiment with the other behaviors, see if they work for you.
Certainly the users will expect this standard behavior. and Dan Schimpf. So when do you use popovers? Popovers are great for inline inspectors like that, although that's not a great example. They're great for little spots on screen where you can show the properties of an object. It's great for a single metadata of UI, let's say name or something like that. You know, especially when something doesn't have enough space to fit in the main window, you can just show a little bit of extra content in a popover.
[Transcript missing]
Inside the popover, you'll see that there's a border all the way around it. There's a white content, so you're not going to want to put a border around the content inside of the popover. If you've got a scroll view or something, don't put a border around the content. Let it go all the way to the edge. Again, keep it simple. One or two things inside are great, especially for smaller popovers like that. You could have a larger popover, like say instruments, and that would make more sense too.
And don't put a close button in there unless you absolutely need it. If you have a transient popover, you don't really need a close button. But if you have an application-defined popover, you can put a close button in there and wire it up so the user will know how to get out.
One last tip to give you, popovers and toolbars. Popovers, if you remember, require a rectangle and a view to show. NS Toolbar and the action method for that, you have neither of those. The solution here is to use a custom toolbar item with an NS button inside that looks like a toolbar item, and then you get a button with a view and you can get a rectangle, and so you can show a popover from that. If you want to show a popover from a toolbar item, that's the solution to do. So that's popovers. Now I want to talk a little bit about the changes you've no doubt noticed in Lion.
So Aqua, what is Aqua? Aqua is the look and feel of Mac OS X. It covers both the appearance and the behavior of the user interface controls. You've probably heard this in a couple of different talks, like the term Aqua. And if you've been around to the platform, and a while, you'll remember what Aqua used to look like.
It used to evoke more Aquaness. There used to be more blue, there's more pills. There was a lot more Aqua-y things. and Dan Schimpf. But here we are now in 10.7, and things are a little bit different. They're more gray, they're more subtle, they're a lot flatter, there's more soft features.
And Aqua is no different. There are more changes in line. What we've done is we've refreshed many of the controls. So some things that have been around for several releases now without any changes are different. If you're a developer, you'll notice that some controls have more transparency than before.
Now, the user won't necessarily notice this, but if you're trying to use custom controls, you probably will if you haven't already. And there's more context-specific appearances these controls can get into. And what I mean by that is that, on the big matrix of states that a button can get into, more of those spots have a specific, unique appearance.
So inactive, pressed, all these things have more specific, unique appearances that you may need to be aware of when you're implementing a custom control, just so it matches the built-in ones. Again, these changes were meant to focus the user attention on the active window content. So background windows fade out.
They get a lot lighter and more subtle. And even the surrounding content of your active window, like the toolbar and your sidebar things, a lot of the color was taken out of that to focus on the active window, the content of your active window, so where you're actually doing your work. And so everything else is not as distracting.
Textured controls. I mentioned that some controls have more transparency, and this is where you'll probably see it. So some controls have so-called textured appearances, buttons and such. And these were intended for use on a textured background. Textured used to mean metal, if you remember back in the day, when textured windows were first introduced.
You might say, well, that's a funny way to say metal, because they look metal. But what we really meant is that these controls, the textured controls are meant to be used on a textured background, and they just happen to be metal at the time. So now they happen to be transparent, because that's what shows the background of the new textured windows the best.
So if you're using, let's say, a round textured button in sort of a background where you're going to see a big bright color or something other than, again, the window background, you may get something like this now. And that's probably not what you want to be seeing. Because again, these buttons are best for use in the toolbar or against any sort of other window Chrome. And that's where they look better. They don't actually look transparent in that case. So you might want to use a different kind of button instead. Maybe the round back button works great in table views and those places like that where you may be using them.
Inactive controls, another area where we see a lot of change. So again, many controls now have unique appearances for their inactive states that you may not have seen before. And so if you have a custom control, you probably want to match this. And a good way to know if your window is inactive is just to check if it's key, because that also works for if the application itself is inactive.
But it's important to know that although the buttons look different, they're still usable in the background. There's not an actual usability change here. This is just an appearance change. So buttons are still-- if the buttons could be used on first click, that is still the case now. That hasn't changed. So it's just an appearance change.
So how do we indicate that a button can be used in the background? Well, we're using rollovers a lot in toolbars for this. You'll see this is a movie of the mail toolbar. And you can see when you're rolling over the toolbar items that the button glyph itself highlights.
and the best thing about this session is that it's a little bit more intuitive. The bezel itself is only indicating the window state. And the glyph itself is indicating the button state. And again, this is just so You have to be careful with rollovers because you don't want users to be rolling their mouse across the screen and seeing just a ton of stuff changing in their mouse, just like a carnival, like a sparkly mouse.
Unless you want a sparkly mouse, but not all users want sparkly mice. So rollovers, again, just indicate when the mouse is overhead, and it gives a soft, subtle effect to say, hey, if you click here, something's going to happen. And so if you've got a custom control, the best way to do this is with NSTrackingArea. It was introduced a couple releases ago, and it's a great way to find out in simple mouse entering or exiting messages.
So on that last screen, you saw the glyph highlight. Well, did we have two different images for that? Well, no. Actually, we just had one image. It's called an image template. And AppKit is processing the image into different states. for-- that's appropriate for the context. So this is the image template for, let's say, a lock icon. And so you can see it's just a black image. And App can see-- App replaces the black areas or other areas like that just with a softer color, or you'll see there's more effects that we can do with that.
And if you're already using this because this is not something that's new, if you're already using these, you get updated to the new effects automatically. So how do you make your image template? Well, grayscale images are best. And PDS, we recommend PDS for scaling purposes. So you get high resolution-- you get high DPI support for free with PDS because they'll scale infinitely.
So when you're designing your image, if you say you have a color icon and you want to make an image template out of it, think about the shadow it would cast and use that. You don't want to just invert it. You want to just drain all the color out of it, but think about how it would look.
Especially, look how-- because sometimes, we'll need to invert that icon, make it all white on certain backgrounds. Let's say, after something's been selected, it'll be all white. So think about the different states it's going to be in, and try it out. And also consider, new in line, the user can select different source list styles or icon sizes. So if you've got an icon, make sure you know how it scales.
So to set these templates, the best way to set the templates, if you're loading it from a file, the file has template at the end of it. So let's say like lock template dot tiff or dot PDF. We'll notice that, and it'll automatically mark it as a template. The other way you can do it if you already have something loaded is call image set template, and that will mark it as a template.
So drawing templates, again, we need more context about your situation versus just regular image drawing. So button cell and image cell give us this context, because button cell knows if it's being pressed, that sort of thing. And you give it a view that we can tell whether or not the window is active or not. So use button cell for interactive elements, buttons that can be pressed, obviously, and image cell for static elements like icons and such. And remember to set the background style on the cell before you draw it. And we'll talk about background styles more in a second.
So here's just a quick example of the same image template, that lock icon, in a number of different states, active, inactive. As you can see, just one image gets you all of these images, other images for free. And you get updated to new appearances as the system changes.
So background styles. What are the background styles? Well, background style is a hint to us and to you How your custom content should be drawn. So it sort of describes the background that you're drawing your content onto. And then you can take appropriate steps to change what you need to do. And this includes drawing images and text. And again, if you're using them already, existing clients are updated automatically.
So here's a couple of-- there's four different background styles-- raised, lowered, light, and dark. And here's some example context in which they can be used, but this is not exhaustive. It, again, is meant to be more general. So it just happens to be that raised happens to be appropriate here. So raised means my glyph is-- the background is raised relative to the glyph that it's drawing. So you get sort of an engraved effect.
[Transcript missing]
So in GraveText, new in Lion, you can do the same thing for text now. And if you're using a text field or text field cell to draw your control text, you get the new behavior, the new look for text in an inactive window, for example. And again, if you're using text field cell to do this, you get updated automatically.
But you may need to be aware of-- you may want to be able to redraw your view when the window goes inactive so the view can update and draw the correct appearance when the window goes inactive. So again, here's just an example of some engraved text. That's an example on Snow Leopard and Lion. It may be a little bit hard to pick out, but there's actually a gradient in the background of that text.
So I'm going to do a quick demo here. I'm going to open this application, which it's a bad example of a Snow Leopard error application. We've got a sidebar here. We've got a toolbar item. We've got some status text down here. Now, your applications look a lot better than this, trust me. But this will just be an example of how we can make it better. So let's look at this status text down here, and I'll see if we can make that better.
We're going to update to use the new engraved text in Lion. So we're going to replace this indicator string and image with cell drawing. So I'm going to take out the points. I'm going to initialize it and replace that with cells. And notice that I'm setting the background style here to raised to match the context in which it's being drawn.
and I'm going to change the drawing code. This will actually get shorter because the cells will do a lot of the work that I'm doing here. So now you can see that we've got the template drawing for the image and the text. Let's see if we can-- we cannot. We cannot zoom up on that. But there's the correct template drawing on the text as well.
Now I notice if I click over here, well, we've got these round textured buttons, and this could be better. So let's look at the cell. I'm going to switch this, the icon, the image drawing on the left over to be an image cell. I'll jump down here. It's being drawn. This is a regular NS image. Let me grab an image cell.
So now I've got an image cell. I'm setting the background style to whatever my style is, because this is an NSCell subclass. So the table view is going to set the appropriate background style on me, and then I can pass that along to the cells that I'm drawing. So now we build. We see that when I click on-- and Awesome. Oh, you know what I forgot? Cell copy. Always remember to update your copy with zone for NSLs.
OK, now when I click on it, you'll see that the icon inverts to indicate that the row has been selected, and it's a better appearance against the darker background. But I've still got this round textured button. So let's go back and just change it to a round rect button.
Textured Rounded to Round Direct. And now I've got a button that doesn't become unreadable when the user selects it. So one last thing, I'm going to look at this. This is a toolbar item with just a regular image in it. But what I can do-- let's go here.
Let's look at this. I actually have a template version of this icon. In IB, it goes all black, but when I run it, now we get the nice engraved appearance for the toolbar that also works when I go inactive. So just a few simple things that you can do to your application to make it fit in better with the standard controls on Lion. Okay, that's all from me. Now I'm going to turn it over to my colleague Patrick to talk about resolution independence on Lion.
Good morning. My name is Patrick Heynen, and I'm here to talk about the pixel. So, over the last decade, LCD display technology has become quite pervasive and is the predominant way in which computer graphic content is presented to users. Along the way, because of the way it's manufactured, Moore's Law has enabled us to make it just as easy to pack a whole bunch more pixels onto the same piece of glass as less pixels.
So, there's general agreement that in the course of time, the future of the pixel is small. Now, this, of course, presents tremendous challenges for software compatibility, because if we were to just proceed as usual, we would end up with really tiny user interface elements that our eyes, which are not conforming to Moore's Law, would not be able to really perceive very well. So, what do we do? Well, our colleagues over in iOS have had too much fun, and we'd like to take some of that stuff back to the Mac. So, I'm here to talk about some exciting updates to our architecture for resolution independence for Mac OS X line.
First and foremost, what we're basically going to do is take the device-independent drawing model that we have already, the Core Graphics API that you may have seen, and extend it down to the levels of the screens themselves. What this means is that no matter what the size of the screen and how many pixels it has, the sizing of that screen is going to be now expressed in points, not pixels. And the content will be scaled accordingly.
Now, one important consequence of this is that units that used to be measured in pixels, such as event, window global coordinates, or window frame coordinates, event coordinates, mouse coordinates, are now all going to be expressed in points, not pixels, independent of the characteristics of the screen that they're on.
So how are we going to expose this in the system? Well, we're introducing the concept of these new high resolution display modes, which a display might have. And you will notice it's described in a way that the sizing of that display is now not expressed in pixels, but it's merely expressed in a sort of standard point size.
But it may have a higher resolution backing, which we indicate currently with this sort of high DPI tag. Another, and perhaps the most significant update we have, is that we're focusing on integral scaling per screen. And these high DPI display modes are actually configured with a two to one pixel per point ratio.
which makes the software compatibility a lot better. Okay, so what does this mean for user interface scaling? Well, the best part about it is that for most Cocoa applications, the user interface scaling is absolutely automatic, and you don't have to do a thing. What does this mean? How does this work? Well, first and foremost, Windows automatically gain high-resolution backing stores, which results in twice as much pixel density and twice as much-- well, twice is kind of four times, I guess, technically, mathematically-- higher detail for both regular quartz drawing and text rendering, of course, which, of course, is the killer feature for high-resolution, is that super sharp, crisp, absolutely beautiful text.
And the new Aqua, which my colleague Dan was describing to you, has actually been engineered to take advantage of all available pixels, should they be present, and provide a much higher quality rendering if possible. So here we go, and we'll see an example here going from low resolution 1x to absolutely beautiful high resolution 2x. I can look at this all day. And you'll notice that the template drawing that my colleague Dan was describing automatically provides a higher quality rendering, much nicer interglows and shadows and all that stuff, perfectly automatically for you. You don't have to do a thing.
Okay, so what does this really mean for Cocoa? What does the world look like for Cocoa applications running in this mode? Well, for things that are not court-strong, like, say, bitmap image resources, they're automatically interpolated to the appropriate user presentation size, which means that they may be fuzzy, but they will be correct, which is important.
And view frame coordinates and layout remain absolutely consistent with the 1x operation, so absolutely nothing really changes from the view of your software when thinking about the coordinate system, the user local coordinate system, and the view coordinate system. OpenGL frame buffer content is also kept consistent with 1x operation. Low resolution content is maintained. Your OpenGL code sees the same kind of backing context, and the resulting content is magnified automatically to the display at the appropriate user presentation size. An important change, very good and important for compatibility of existing applications.
Now, we have kept one of our most popular features of our previous editions of resolution independence, which is the magnified mode. What is magnified mode? Well, in magnified mode, window buffers can stay, get created at low resolution sizes and don't change from what they would be on normal low resolution displays, and the window contents themselves, after they're drawn, are scaled to the right size on the display. This is an important backstop for applications that, due to bugs or bad assumptions about the display, are not able to be created at the right size.
So, the users that are out there drawing environment are unable to provide a good user experience with the default scaling mode, and so we kick them into this magnified mode. The important thing is that this provides perfect compatibility for a large class of applications. Now, how do you actually optimize and provide the best possible user experience for this high-resolution environment? Well, the most important thing you can do, and we recommend you do, is provide high-resolution image resources for any static bitmap content you may have, and your user interface or user-facing content.
To facilitate this process, we're taking a page off the ILS book and adopting the sort of naming convention for image resources of at2x and foo.ping and foo.at2x.ping. We're going one minor step further and added a special feature to Xcode to actually automatically combine these image resources into a single multi-page TIFF for optimal runtime deployment usage, so we don't have all these files laying around on disk and so things are a little more manageable.
In addition, PDF templates, as my colleague Dan mentioned, are a great way to automatically achieve, you know, rasterization independence, resolution independence for button icons. And lastly, one area that you'll need to focus on is adapting any OpenGL and Core Animation code for this high-resolution environment. Now, let's talk about OpenGL and Core Animation for a little bit, because they're a little bit special.
OpenGL and, to a lesser extent, Core Animation are inherently pixel-based technologies and provide abstractions upon a very thin level of abstraction upon a raw frame buffer. So they're really kind of hard to virtualize in the same way that, say, the Quartz model is. Because of this, just like on iOS, applications are going to need to request high-resolution backing surfaces, because typical OpenGL code isn't going to be surprised if they suddenly find four times as many pixels as they're drawing into, given the nature of the OpenGL APIs. To do this, we've introduced a new API.
NSU set wants best resolution OpenGL surface. Yes. If you say yes, on a particular-- you can do this on a view-by-view basis, so you can, you know, you can make the choice on a per-view basis. It'll get-- when the view gets constructed, when the surface gets constructed, it'll get constructed with twice as many pixels, four times as many pixels.
And your OpenGL viewport and Model-V transform code can get-- can need to be modified accordingly to provide the appropriate rendering. Custom Core Animation layers, just like on iOS, need to have an explicit scale factor. So, if you're using a layer set for any custom content that you might be storing on them, that these-- that's the content scale property.
But of course, if you're using AppKit layer-backed NS views, we do all this housekeeping and management for you, so you don't have to worry about it. So, we encourage you to use those when necessary, and only go to the custom CA layers and layer scale management if you need to.
OK, so what are some of the problems that you sometimes do need to-- that exist that where you might really need to be aware of the pixels going on in your-- that you're drawing into and punch through this virtual world? Well, there are a couple of key cases. One is sort of you want to draw in a nice device-aligned way, so you don't spill over the edge of a pixel, have a nice crisp, sharp line, or align things perfectly.
And other cases are like you're trying to allocate an off-screen bitmap context to draw the contents of a particular view-- of a user view size into. In that case, you need to know how many pixels are back in this. Well, we provided a set of tools for you in the form of some APIs, some old, some new.
The most important which is the-- for aligning drawing to the device pixel boundaries is good old Center Scan, right, which has been around for decades it seems, but has been updated to actually work in this high-resolution backing environment and device align any rectangle that you-- from given in a local view-- So you can see the coordinates into device alignment.
If you need-- if you're doing a more complex layout operation and you need more control over the rounding operations of-- of the rounding behavior, we've provided new advanced API in line for-- for controlling the individual rounding behavior of each of the edges of a given rectangle. This API is, of course, available in its view and its window and in a screen because each of them may have independently varying back in coordinate spaces.
Now, for coordinate system conversion like the bitmap context case I was alluding to before, we have these coordinate system-- new coordinate system conversion APIs for converting RECs from and to backing. Now, I just say REC here on the slide, but we have all the geometric types available. And these are available again on NSView, NSWindow, and NSScreen to handle all those three different coordinate spaces. So in the bitmap context case, if I want-- had a particular view I wanted to create an off-screen for, I take that-- the view's bounds, I'd say convert REC to backing, get this-- and that would convert it into pixels.
So in the bitmap context case, if I want-- had a particular view I wanted to create an off-screen for, I take that-- the view's bounds, I'd say convert REC to backing, get this-- and that would convert it into pixels. I'd say convert REC to backing, get this-- and that would convert it into pixel geometry, get the size from that, allocate my memory that's appropriate to that, create the CG bitmap context, apply the scale to it, I'm good to go.
So, a few tips and tricks, realities of living in this virtual world, things that are a little bit different. First and foremost, be prepared for fractional mouse locations and view coordinates. Something that actually aligns perfectly nicely on a nice pixel boundary on the screen might actually, in your code, end up on a 0.5 boundary now due to this virtualization. It's not a bad thing, not a horrible thing, just be careful to rid your code of any assumptions about things being integral into point space having anything to do with pixels.
And keep in mind that NSView, NSWindow, and NSScreen, especially with this funniness with the OpenGL surfaces, can all have independently varying backing resolutions, so don't make any assumptions, and make sure you use the right coordinate transformation API to do your work in the right space and make your decisions in the right space.
Secondly, or, well, fourthly, I guess, NSImageSize, because it's an AppKit object, its size is expressed in points, but CG images are always expressed in pixels, so be careful that you don't make any mistakes. And keep in mind that NSView, NSWindow, and NSScreen, especially with this funniness with these layout assumptions based on CG image geometry in your local view space.
This is a common pitfall. And lastly, most of the time, calling center scanner is really all you really have to do to make sure things sort of snap into place in a high-resolution screen. And with that, I'd like to hand it over to my esteemed colleague and... Thank you, Patrick. ...do a little bit of wrapping. So this may be the end of our session today. Thank you very much.
and David I can see more pixels already. This may be the end of our programming here at this talk, but this is just the beginning of your adventures on Lion. You can always email Bill Dudney, our Application Frameworks Evangelist, [email protected]. He will be happy to steer you in the right direction.
We have fantastic documentation, chock full of information. I'd like to point out in particular, we have brand new resolution independence guidelines that relate directly to what Patrick just talked about. Look for those on developer.apple.com. Also, the application kit release notes are written by the app kit engineers who work on the framework code.
This is knowledge imparted directly to you, and it often contains lots of late-breaking stuff that hasn't made it into the documentation yet. I highly recommend you look at those and scour them for information about features you're working with. We have Apple Developer Forms where you can ask questions. Some great sessions coming up. Don't miss these.
Today at 3:15 in this room, scrolling, swiping and dragging. Now with more animation. We have an entire talk on view-based table views tomorrow morning. You'll see the fantastic things that you can do now that you can put views in table views and do automatic animations. Thank you very much for coming. We hope you enjoy the rest of the conference.