Chris Parker: Welcome to Session 111, the iPad Development Overview.
My name is Chris Parker.
I work on UIKit and this is, basically, we are going to talk about how to take an iPhone application all the way through to become a fully universal iPhone and iPad app.
So you have probably spent a bunch of time working on your application, getting it really well tuned to work on an iPhone 3GS. The screen size lends itself to a particular style of application.
It tends more toward the navbar style information navigation system.
You know, you push on something.
You push another view controller, you sort of drill down into your data and you drill back out of your data, and then we went and did something kind of crazy and came out with this iPad thing which has a much larger screen, and it lends itself to a different set of behaviors for navigating user's information.
It has a much larger screen.
Those are actually proportionally correct, right.
So the iPad is, let us call it, you know, sort of roughly 4 iPhones maybe.
But it does change both the way the user will interact with the information in your application, and some of the design considerations in trying to share code for a universal application.
We'll talk today about some of the new UI considerations of going from the iPhone to the iPad.
It comes down to things like the initial appearance.
What does your application look like when it first launches, covering some things about rotation, transitions, and managing sort of the information density and presentation, and two of the things that we have introduced for that are PopoverController and SplitViewController, and we'll go into some detail on those.
The main thing that we're working on here is going to be universal applications and the things that you'll do in order to write for multiple SDKs.
So you'll make some changes in Xcode.
There are some API things to take into consideration and you may have to think about symbol availability.
So when we talk about what symbols are available across the SDKs, we'll get to that in a bit.
What does need to change in your project?
You'll need to make some Xcode project settings.
So the Base SDK, the deployment target, the targeted device family will all have to change and we'll see that in a minute.
You'll probably have to refactor some code, right.
It's very simple in some ways to wind up with a design where you're taking advantage of the fact you know everything that's on a navigation stack and sort of crawl up and down that stack to be able to find the view controller you want.
We'll talk about refactoring your code to try and make it easier to share things when you start using things like split view controllers, and the emphasis there is going to be on controllers which send actions, and then you're also probably going to need to change your resources just like with the iPhone OS and iOS 4, the iPhone 4.
You know, your designer is probably going to have to help you out with getting some new resources either things for a larger screen or new artwork that fits the larger form factor.
So let's start out.
I just want to talk a little bit about Popovers and Split View Controllers.
A UIPopoverController is not a UIViewController.
It's its own managing controller.
It's going to host a content view controller.
That content view controller has a view that it manages and that content view controller, that is where you are going to get the viewWillAppear, viewDidAppear, messages, and things like that.
That popover controller is usually owned by some object.
It might be your application delegate.
It might be another view controller.
You know, it is something that is going to manage the popover lifetime itself and you create these just by calling initWithContentViewController.
For those of you who are in the Cocoa Touch section, this slide probably looks familiar.
ContentViewController allows you to change which content view controller is present.
popoverContentSize allows you to change the popover content size while it's up so you can animate size changes for that and the passthroughViews is a way you can use a list of views that you'll pass this where the user can interact with those views while the popover is up.
You present a popover controller using presentPopoverFromRect:inView: permittedArrowDirections animation.
That rect that you pass in is in the coordinate system of the containing view.
So if you find that the popover is suddenly showing up in some funny location, you probably want to make sure that you've passed the correct thing and in a lot of times, you'll be passing view bounds.
If you pass view frame, you'll get some really funny, funny results.
PopoverArrowDirections allows you to control where the thing gets laid out.
There is an algorithm in UIKit where we try to maximize the area of the popover controller you're displaying, and we'll figure out whether it belongs on the left or the right, or the top or the bottom depending on what the content size is.
If you need to display this sort of bar button item, we know that you can have the toolbars up on top of the UIViewControllers at the top of the view, so you may want to be able to display a popover from bar button item.
You do that just with presentPopoverFromBarButtonItem.
When you dismiss these things, you will call the dismissPopoverAnimated.
We don't send the delegate methods because those are for the user controls.
The delegate methods are popoverShouldDismissPopover and DidDismiss, and the should is really both I'm about to dismiss this notification and your opportunity to decide whether or not you want to actually dismiss it.
I have just a little bright red square here.
That bright red square is actually something that I can drag and it will return to its center point, and let me take a look at that.
This is all of the code for that.
It's called an anchored view and all I'm doing here is using a pan gesture recognizer to find out where I've tapped in the view so I can drag it.
And as the gesture recognizer moves through its states, I'm when I state when I get UIGestureRecognizerStateBegan for the pan gesture recognizer, I am going ahead and just setting its translation and view to its own anchor point.
Its center, basically and this is just set, it's sort of initializing the value, and then in UIGestureRecognizerStateChanged, I just set this view center to the the center here is the gesture recognizer, translation view, self superview just basically says, set my center to a translation that corresponds to the self superviews that I move along with the finger.
You can do this in TouchHasBegun, TouchHasEnded, TouchHasMoved, but you have to sort of compensate for the location of the point difference between the center and where the touch went down.
This is just sort of interesting use of a pan gesture recognizer that Josh Shaffer suggested that is working out pretty well.
So that is one of these anchored views here and these 3 buttons here, they all say pop on them are all set to just popup a straight popover controller.
So if I could just take a quick look at that.
This is the people picker from address book so I have just taken a stack pop navigation controller and put it in to this popover, and as I click in different places, you will see that the arrow winds up pointing in the right place.
We make room for it depending on what's going on and right now, I have all four of these on: up, down, left, and right.
Those correspond to the directions in which the arrow was pointing.
If you look here for pop, right.
I could have possibly put the popover up over the button, but the algorithm is showing us that we can lay it out with just a few more pixels all the way, top to bottom there, and we also take care of maintaining an appropriate margin around the outside.
So if you do set something that is really large or if your view controller is very tall, we are going to shrink it.
The content size is more a recommendation than anything else, but for the most part, we're able to honor those sizes.
If I turn these off so, for instance, let's turn off left and right.
Remember what happened when I tapped in the sky at the bottom last time.
The arrow was pointing left, but if I tap on it now, it is going to point down and we actually move in to the target rect.
The target rect in this case is-the rect that I'm presenting from is this popover view.
So when I do click around though, I can't grab this view while the popover is up and that can be a possible problem.
I'm just setting up here in the viewDidLoad.
I just set up all of my little buttons and everything, but down here in display popover, I have a line here that I'm going to erase the comment from where I'm setting the passthrough views to this anchored view that I've set up in my view controller, and now if I run this guy, what I find is that I can pop up any of these popovers and, you know, while I am popped up if I tap some place, it dismisses except if I tap in this guy here, and now I can drag that view out from under the popover, and see what is there.
So you can imagine this might be a few that has content in it like, for instance, the contents of a message, and you may be selecting something to try and have the user take an action using the popover, but they can't really see what's going on underneath it.
So they might want to draw it out.
You can grab that and draw that view out, and then when you let the mouse, when you release the touch, it will go back to the center.
So those are sort of the basics of popovers.
So for UISplitViewController, SplitViewController is a full screen view element.
It is itself a UIViewController subclass, right.
So this means that all of its containing views, view controllers actually help change the rotation behavior and things like that.
So a SplitViewController has a delegate.
It is going to inform that delegate about events that happened as the device is rotating mainly.
The object at index 0 is generally the master view controller and the object at index 1, this is the view controller's property for split view is generally going to be the detail view controller and in this case, our application delegate will be the owner of the SplitViewController.
This will be very common.
SplitViewControllers are typically the top level item.
You usually create these things from nibs, right.
You'll have a nib set up for your iPad.
You will drag out a SplitViewController into it and you will get a full screen window that has everything in it.
If you want to create one programmatically, you can call alloc init and you can also get at the view controllers property and the delegate.
And again, the delegate tells you about what's happening when the SplitViewController rotates.
So, I have downloaded this so we'll talk about at the end of the talk but again, these 3 delegate methods are splitVewController, willHideViewController, withBarButtonItem:forPopoverController.
This is the landscape-to-portrait rotation, right.
So when you go from landscape to portrait, we're going to hide one of the view controllers, that guy on the left at object at index 0.
And we're also going to give you a UIBarButtonItem that you can put in your interface somewhere, typically in the toolbar that's at the top, and that will be what the user taps on in order to bring down the popover controller that will have that master view controller in it, pre-populate it.
And the popover controller, you'll get a reference of that so if you need to dismiss it you can.
splitViewController:willShow ViewController:invalidatingBarButtonItem, that is going the other way.
That is going portrait to landscape and that means that the view controller in the left side is about to become visible and that BarButtonItem we gave you before, yeah, get rid of that, it's no good to you now.
And splitViewController:popoverController: willPresentViewController is where you basically get told the users tapped on the button, we're about to present the popover controller.
If you need to take other action when you're going to present that then that's your opportunity to do so.
So we're going to use the CoreData Books example.
We have modified it slightly for this talk.
This is if you type pretty quickly you will be able to get to the URL here, but you can search for it on the sample code site as well and we will have an updated version soon.
But the CoreData Books sample looks like this.
This is just a very basic application that demos CoreData to manage a list of books.
If we run this, here is our iPhone Simulator here and we have a list of books.
If I tap on a particular book I get a view controller that gets pushed in.
It is a full screen transition.
You know, there is not much space on the iPhone so we tend to write these kinds of interfaces.
And then when we tap on an author card we get another view controller that gets pushed in, all right, and the navigation controller is keeping track of where I have been.
So if we run this app actually in the 3.2 Simulator we get a small version of the CoreData Books app.
Actually, this is totally expected.
We're running this in compatibility mode so when you run an iPad, when you run an iPhone app on an iPad, we are going to go ahead and run that in a small window and you know, you can interact with it and you still get the same full screen transitions.
And then you can also zoom this up to 2x and you get the bigger version but it's still essentially an iPhone app.
We are not really taking advantage of any of the things that even scaled up here at 2x of any of the things that really make it, the iPad a really fantastic device to use.
One of the things that we are going to have to do is start converting this project, right.
So, there as some Xcode project modifications we will need to make, and there are some code refactoring we will do and then we are also going to have to come up with some new resources.
And the first thing we will do actually here is work on converting the project.
So if we take a look at the resources and we look at our info plist, there are a number of things here.
So we have the various keys that make up the info plist.
This is how you tell the application as we launch, this is how you tell UIKit the various things about your app.
So this will include the main nib file base name which in this case is called main window, and that corresponds this MainWindow.xib file here.
The application requires whether or not it requires the iPhone environment, that kind of thing.
So, one of the things that we will do is take a look at the raw keys and values.
These are the actual info property list keys, and if you have been editing these things for a while, it should be very familiar.
The NSMainNibFile though is interesting because if we pop up this menu here in Xcode, we see there is also NSMainNibFile~iPad and NSMainNibFile~iPhone.
And this is your opportunity to be able to specify which nib you want to load depending on what device you are going to support.
So to be able to pick a nib for your iPad and nib for the iPhone and when the application fires up, we will pick the right one.
We could go changing this but there are a lot of things we'd also have to change.
So this is the CoreData Books project build info here and actually we could look at it.
Actually, let me look at the target because that will be better.
What we see is the Base SDK here is iPhone device 4.0.
And the Base SDK when you are working with Xcode for these things is basically the latest version of the operating system that you would like to run on.
And if we go look for the deployment target, we'll see the deployment target here is set for OS 3.2 which is good for our purposes.
On a fresh project this might actually have been set for iPhone OS 4.0.
And then if we look at the targeted device family, right now this is set for iPhone.
And setting this for iPhone means it is only ever going to run in the iPhone and if it runs on an iPad it will run in compatibility mode.
And what we want to wind up doing is setting that to be iPhone/iPad.
Let us make sure that this is all set up here.
And then rather than do all of this editing by hand because the first time I did all of this by hand I managed to keep missing things.
Xcode 3.2 has this nice feature where if we control click on the target we can upgrade the current target for iPad.
And look at the big sheet that drops down and it asks that we can either upgrade the current target for universal app, runs on both iPhone or iPad, or we can create a separate target for the iPad and the device specific iPhone target gets left as is.
So you can either have 2 targets, 1 for iPhone, 1 for iPad or 1 universal application.
We are going to go ahead and choose 1 universal application.
So, now a bunch of things happen sort of behind the scenes.
The first is if we go look at our info plist we will see that we now have a main nib file base name with main window in it and one that has MainWindow-iPad.
And in fact, Xcode went ahead and created a resources iPad group and in this group it is now also thrown in a MainWindow-iPad.xib.
And this MainWindow-iPad is basically just a big version of our original MainWindow nib.
So it has done a bunch of conversion for us.
If I look at the Base SDK I should see yup, sure enough iPhone OS, iPhone device 4.0.
If I go ahead and look at targeted device family, we will see iPhone and iPad and just underneath that the iPhone OS deployment target is iPhone OS 3.2.
What happens if I run this?
Here is the iPhone Simulator 4.
That is not so bad, that is unsurprising.
It is sort of the same application.
Let us actually go ahead and run that in the 3.2 Simulator and we get a big iPhone app.
But it is really big and some of it doesn't work.
So you know, you click out here and you tap out here and you can't do anything and you happen to get into the right spot and these big sloshing full screen transitions happen.
Do not ship this.
[ Laughter ]
Don't do this.
What we'll do instead is start the work of sort of moving everything into a 4.0 and 3.2 combined application.
So, one of the things that we need to do is to refactor the application delegate to really effectively take advantage of the iPad.
So when we created the new nibs, right, there is an outlet inside the nib for your app delegate and you get to choose what the class of the application delegate is.
But this system is going to choose a nib for us based on what we have in our info plists.
So depending on what we tell the nib to use for the application delegate class, it will automatically pick either an iPad-specific or an iPhone-specific app delegate.
Oh, kind of cagey.
The application delegate here for CoreData Books has 5 things in it that are common pretty much to every single application we write on an iPad or an iPhone.
It has got the managedObjectModel, it has got the ObjectContext, it has got the persistentStoreCoordinator, the thing that does all the real saving.
It has the applicationDocumentsDirectory which is where in this case we are putting the CoreData database and it has a window.
And also it's an outlet for navigation controller which is great for the iPhone but it is not so great for the iPad because that means we'd only be able to connect to one of those 2 things and we actually need both of them in order to write an effective application.
So what we are going to do is actually hoist all of that stuff up into a shared super class, an app delegate shared class.
And then we will go ahead and have a separate app delegate iPhone and app delegate iPad class and one of them will have an outlet for the navigation controller.
And in the other, we'll have an outlet for the split view controller.
And so now we have a class that gets automatically chosen and instantiated for us at application launch and this is an opportunity to start dividing some of the UILogic in your application, right.
So we are going to move some of that controller logic around.
It is really hard to avoid some of these tightly coupled designs, right.
So on iPhone application, you have got the stack view controllers on a navigation controller.
Sometimes everybody winds up kind of knowing about everybody else.
And pretty soon you want to be able to reuse one of these view controllers and you realize that reusing one of these view controllers means I also have to figure out how to reuse all of those 6 other view controllers that it knows about.
And now I can't quite share code as effectively.
One of the things that we would like you to do is think about intent, think about what is happening as the user maneuvers through your application.
They are taking actions.
You are going to admit those actions and you are going to respond to those actions.
So that target action paradigm that we used in UIKit and on the desktop in the app kit is actually something interesting to consider here whenever you are looking at refactoring your code or even just writing new code.
And you really want to talk about trying to choose your action receivers with some level of care, right.
If I have to have peers that know about each other, sometimes that gets to be one of those really tightly coupled designs.
So you want to try and choose the closest object in your hierarchy which knows about all the players.
So, which in this case, we are going to use our app delegate to manage some of this logic.
But the responder chain will be another appropriate hierarchy for that, right.
So sort of 2 different parallel hierarchies, one has to do with views and view controllers and one has to do with just who is the first responder.
So in this instance, we are going to think about using a view controller where when you tap on a particular book we send a show book message to the iPhone app delegate and the delegate is the one that is in charge of creating a new detail view controller and then filling that view controller in with the appropriate book, and then we go ahead and push that on to the navigation controller stack.
Okay, that works pretty well for a phone application.
What does it look like for an iPad app?
Well, we have got a split view controller that is owned by the iPad app delegate.
We go ahead and tap on a particular book.
We send the same show book message to an app delegate.
It just happens to be different app delegate in this case, right.
And then that app delegate turns around and sends set book to the detail view controller that it knows is in place.
So rather than creating a new one we just go ahead and set that on the receiver in the view controller.
So we are going to reuse this view controller and rather than creating a new one and setting at each time.
And let me show you a little bit about what some of that refactoring looks like.
So here we have our CoreData Books that is running in the simulator.
We have gone through, we have our resources, iPad and if I take a look at the main window here for the iPad resources, we have the split view controller.
The split view controller has a navigation controller in it.
It also has a detailed view controller.
That is the guy on the right, let us open that up.
And you know, the navigation controller has the normal nav bar and all that other business.
And we have got the root view controller is our root view controller class here, which is this guy.
Sample code, sample code, sample code, but it is in charge of basically handling the viewDidLoad business and things like that.
And this actually is part way to getting through the refactoring.
If I run this though I have not really made any code changes but we see that if I try to rotate this it works but I have got the view controllers so you know, the guy on the left is not quite right because the Edit button is not going to function properly the way we want given an iPad interface.
This you know, well, I am still stuck in this.
So there are still some work to do here but if you get to this point you know, you are on your way, you are on your way.
I am actually going to jump to our fully recooked universal CoreData Books example.
Nice high resolution large version here.
It still does the full screen transitions, and if we run the 3.2 version here.
What we have is our rotation code here is all working.
We have got the various larger resources as we tap on things.
We are only 1 tap away from the various bits of information that we want to see.
And we also have a little popover here for the author information which is kind of appropriate for this kind of use, right.
It is a little transient popover controller for just displaying some information, getting rid of it but we don't have any full screen transitions kicking around here, and I will show you how that works in a minute.
Let's take a look at the info plist here.
UI supported interface orientations here, one of them, the fallback here is for UI interface orientation portrait.
For the iPad version, we've got all 4 of them here and that's what allows the iPad version to launch in all 4 orientations.
Actually, what happens if I go ahead and delete that?
If I actually delete that and then say rotate the portrait, let's go home here.
So that ends the process.
If I build and run this again, suddenly the thing works to portrait and shows up in this other orientation.
You actually want your application to show up in all possible orientations.
So you want to make sure that your supported interface orientations are all in your info plist.
The other thing you will want to make sure you do is your view controllers all have to implement the rotation code, shouldAutorotateToInterfaceOrientation.
So remember when you are working with the UISplitViewController, the split view controller is going to go ahead and ask its children whether or not a particular orientation is supported.
If it is supported, return yes.
So for most of your iPad work you are going to want to return yes all the time.
SplitViewController willHideViewController withBarButtonItem.
This is the delegate method I was talking about before.
What you will do here is just grab the title, grab the bar button item that we hand you, fill it in with the title you would like and go ahead and make a copy of the toolbar items probably of the toolbar that is in the view controller, the detail view controller.
Insert that object at index 0 and that will be the first item.
In our case, that view controller has a spacer in there in order to separate things out.
The set items for toolbar items animated yes, this just goes ahead and puts that in a nice cross fade and then we release the toolbar items here.
And we're also grabbing a reference to the popover controller so that we can dismiss it if we need to ourselves.
And we are going the other way, we are going to go ahead and this is a splitViewControllerwillShowView ControllerinvalidatingBarButtonItem.
We will grab the toolbar items here, that same mutable copy with the object at index 0, there we go.
And set the items, go ahead and release it and that's what enables all of the button management in split view controllers.
It is something you do have to handle yourself because we just don't know where you want to put it based on your interface.
And then if we look at the detail view controller here, again we also have our popover controller and this guy just creates a popover controller and presents it right from here it is authored text field bounds, right.
If I pass author text field frame that would make it again wind up in a funny spot.
But all of this uses sort of the refactored model of being able to send messages to the app delegate and have the app delegate do the work of positioning all of the various bits of interface.
So what changed?
Well, in the Xcode project we changed the Base SDK and the deployment target and the targeted device family.
Actually, we didn't change it, we let Xcode go ahead and change that.
For the refactored code, we're hoisting a bunch of stuff in the application delegates.
We're using some of these actions sending controllers so that we're not actually having these tight relationships between UIViewControllers, you know.
Too many things knowing too much about each other, there are some software design principles there.
And we actually had a number of different resources get changed to nibs, images, stuff like that.
What didn't change?
Well, I didn't touch a single line of code that had to do with the model.
And that is actually pretty significant.
If you're writing a universal application, you probably don't want to be messing around with the model especially if you're sharing the model between the desktop and your devices.
The model, we make a big deal in all of our documentation and all of our design and we try to make our design principles follow this with the model view controller paradigm.
By not, you know, by having all of this logic up in the controllers in the view hierarchy.
We're really minimizing the amount of code, we're really going to have to change in order to get our stuff to work.
So if you have too much tight control, tight knowledge between the model and your views or the model in the view controllers, that is going to be a problem too.
Ken Kocienda is giving a great talk, Model-View-Controller for iPhone OS, that's in Russian Hill tomorrow at 10:15.
He is going to go over a lot of the things that you can do using model view controller and some advanced techniques for managing how the different things work on iPhone.
So it will be a great talk if you need to brush up on that or if you want to know some things that are particular to designing your code and your software for the iPhone.
When we're working with SDKs and symbols, we sometimes wind up in situations where you want to use a symbol and it is not just available, or you want to try and subclass something that does not exist.
So here are actually 3 examples of some symbols that are available on the iPad, on iPhone OS 3.2 and the iPhone on iOS 4.
So UIPopoverController, these symbols are present everywhere, right.
But on iPhone OS 3.2 you can actually use and create a popover controller.
But if we find that you are running on iOS 4 you can instantiate one but will actually throw an exception when that happens because the iPhone, the iOS 4 model on the iPhone, it is a smaller screen, the presentation doesn't lend itself particularly well to popovers or split view controllers.
And that is really not the way that the iPad really can present your information to its best advantage.
So UISplitViewControllers also, when you create one of those we go ahead and throw an exception if you are on the phone.
And gesture recognizer is actually usable in both places.
So you know, if you have been carrying a bunch of code to do all kinds of gesture recognition yourself, you certainly can continue that.
You can fold that into a UIGestureRecognizer subclass which is actually a great way to do it because then you can work in conjunction with our UIGestureRecognizers.
You also may want to know in line what the device idiom is.
We have introduced some UI, API rather on UIDevice.
UIDevice.h has this user interface idiom, enum.
And the idiom is basically a way that we describe what kind of user experience we're offering.
And in this case it's 2 enums.
The phone one includes iPod touches, right, they're basically the same form factor.
They have the same kind of screen, weight, et cetera.
And the iPhone UIUserInterfaceIdiomPad.
The way you typically use it is you could just check to see, hey we're on an iPad, we have got the iPad user interface idiom, we'll create a UIPopoverController and go ahead and use it.
Or if we're not on an iPad, an idiom pad type device, we'll go ahead and just create an author view controller and initialize it with an author and push that on to the UI navigation stack.
This is a different approach.
Typically, you're, you know, probably going to use the subclassing approach.
It's a little bit more flexible.
And if you find yourself sprinkling a bunch of if user, UIUserInterfaceIdioms all the way around, that is probably a hint that you want to think about hoisting some of that logic up into a shared controller with separate subclasses, right.
This is a macro by the way.
If you look in the UIDevice.h header the UIUserInterfaceIdiom macro does a UIDevice response to selector for the access or for this idiom and then calls that and it returns that answer, and if it doesn't respond to the selector, it goes ahead and just returns idiom phone.
And that's actually a really good technique also in terms of dealing with symbol availability.
You're going to want to use NSClass from string to find out if a particular class exists on a device.
You're going to want to use response to selector to figure out whether or not a particular instance, you can actually send that message to that instance.
So there is a sense of being able to test for capability and not for version, right.
The interesting bit isn't the fact that you're running on iPhone OS 3.2.
The interesting bit is that the particular facility you are looking for is available.
So what do you guys need to do?
Well, you'll need to do some refactoring probably, and that refactoring is going to involve some of that hoisting of functionality up into superclasses.
It's going to involve some maneuvering of code so that you have got sort of don't repeat yourself thing going, right.
Do not have multiple copies of the same code in one place.
And really try and take advantage of some of the things that the application loading system is doing for you and being able to change, choose nibs and things like that.
You're going to want to change up your resources, right.
A lot of the interface that we saw on the initial passes at the universal version of the CoreData Books app, you know, there are full screen and it is a big table view that spreads across the detail view controller.
And what you really want is that nicer presentation and you want to try and bring a lot of the information on the iPad a couple of taps closer to the user, right.
If they had to keep drilling down even on the iPad, there is probably something there about how you've got your data organized that isn't quite optimal.
And we do really encourage you to write universal applications.
If you offer a universal application to your users as an upgrade from an existing application, a lot of that data is going to come along for free.
The user's data will probably get just pulled up when they upgrade to the new version.
We also highlight universal applications differently in the store, right.
So when you search for an application in the store, the universal applications are listed first.
They also get a little plus indicator on their price that says yes, this is universal application.
So, we have got Mr. Bill Dudney is our Application Frameworks Evangelist, so thank you.
[ Applause ]