WWDC2017 Session 408

Transcript

[ Background Conversations ]
[ Applause ]
>> Hello, everyone, and welcome
to What's New in Swift
Playgrounds.
My name is Connor.
I'm an engineer on the Swift
Playgrounds Team.
And today, along with my
colleagues Grace and Najla, I'll
be sharing with you some of
what's new in Swift Playgrounds
since last year's conference.
Today's session will be broken
up into three parts.
First, we'll take a quick look
at the Playground book format to
ensure that everyone is on the
same page.
Then, we'll talk about many of
the enhancements which have been
made to support building better
Playground books.
And finally, we'll introduce a
brand-new API for interacting
with and connecting to Bluetooth
accessories in Playgrounds,
allowing you to provide a
consistent user experience.
So let's get started by talking
about Playground books.
Swift Playgrounds supports two
file formats, the classic
Playground file format used by
Xcode as well as the Playground
book file format, which allows
you to control the experience
which your users see.
Playground books are split up
into chapters, which are then
further subdivided into pages.
Playground books may contain
resources which are used either
at runtime or in the prose which
you show to your users.
All of this is done with a
package-based file format.
If you're not familiar with what
a package-based file format is,
essentially instead of being a
flat file, it's a folder which
itself contains other files and
folders which make up the
document.
If we take a peek inside the
Playground book, we can see
inside there some PLIST files,
some Swift files, and a bunch of
directories which make up the
structure.
Let's first take a look at the
manifest files.
You can think of these as
something that's very similar to
an app's info.plist.
The manifest PLIST provides
book-, chapter-, and page-level
metadata, such as the name of
the page or the icon which is
used in the document browser.
Manifest files also specify the
options for the book, chapter,
or page, such as whether or not
the live view is visible by
default or if you want
Playground logging to be turned
on.
The other major component of a
Playground book are Swift files,
and there are three different
kinds of Swift files inside of
Playground books.
The first of these is the
Contents.swift file.
This is the source file with
which your users interact, and
it's where you can store the
prose which is shown to your
user inside of special markup
comments in the Swift file.
There's also the LiveView.swift
file.
This allows you to provide an
always on live view, which
provides a rich and interactive
experience for your users for
the entire time they're on the
page instead of just when
they're running their code.
And finally, you also have the
auxiliary source files.
These are stored in the book,
chapter, and page and allow you
to provide additional API
through separate Swift modules
which are automatically imported
at each level.
For more information on the
Playground book file format, I'd
really recommend that you take a
look at last year's session,
Introducing Swift Playgrounds,
where some of my colleagues go
into this in a lot more depth.
There's also really great
documentation available on
developer.apple.com.
Let's now take a look at some of
the new features in Swift
Playgrounds since last year.
First up is support for copying
code between pages.
This allows you to build a
Playground book which continues
a narrative from page to page
while allowing users to evolve
the code that they're writing.
So when I open up a page that
uses this feature, as a user, I
see a prompt to either bring
over my code from the previous
page or to start coding
immediately.
If I then tap on the button to
bring my code forward, then my
implementation from the previous
page is automatically copied for
me and the source editor is
shown, allowing me to begin
coding.
The next new feature is support
for speed control in Swift
Playgrounds.
This allows you to control the
speed of execution, and there's
two forms for this.
First up is support for stepping
through code as it executes.
So if you tap the speed control
in the live view at the bottom
left, you can select Step
Through My Code, and then as the
code executes in the live view,
the line of code, which
corresponds to the current
action, is highlighted in the
source editor.
So you can see, as Bite moves
around the puzzle world, the
line of code directing the
action is highlighted.
Swift Playgrounds now also
supports running faster than the
default speed if your Playground
book supports it.
So again, you can tap on the
speed control in the live view
and then select Run Faster or
Run Fastest.
Learn to Code 1 and 2 take
advantage of this feature to
make Bite move more quickly
throughout the puzzle world.
Swift Playgrounds will now show
runtime errors which occur in
your Playgrounds.
So in this case, I have created
an array with five items, and
then I try and access the item
at index 5, which results in an
index out of range error.
Swift Playgrounds is able to
determine which line of code
caused execution to crash and
then highlights the line with
the text that's provided.
Users are now able to add,
delete, rename, reorder, and
duplicate pages inside of
Playground books, which they
create inside of Swift
Playgrounds, including all of
the Apple-provided starting
points.
Swift Playgrounds is now
localized into a few more
languages to increase its reach.
So Swift Playgrounds and all of
the Apple-provided content is
now available in simplified
Chinese, Japanese, French,
German, and Latin American
Spanish.
And you as an author can
localize into any of these
languages or any of the others
which iOS supports.
[ Applause ]
Coming in Swift Playgrounds 2 is
support for the iOS 11 SDK, and
this means that a handful of new
frameworks are now available for
use in Playgrounds in Swift
Playgrounds 2.
This includes ARKit, CoreML,
IOSurface, PDFKit, and Vision.
Additionally, Playgrounds may
now access the camera if they're
running on iOS 11.
You can use the AVFoundation
APIs to get access to the
camera.
It's important to note, though,
that this won't work on earlier
versions of iOS, so if your
Playground depends on the
camera, we recommend that you
set its deployment target to iOS
11.
And now, let's hop into a demo
to take a look at a couple of
these features.
[ Applause ]
OK, so here I have Swift
Playgrounds, and I'm going to go
ahead and launch it.
And Najla, Grace, and I are each
working on Playground books
representing kind of our kitchen
where we're going to use Swift
code to make some things.
And I'm going to start out by
making a cake.
So I'll go ahead and open up my
Playground book.
And we're going to start from
the very beginning by making
our, making some butter on our
own.
So we've got this makeButter
function and we've got the cream
that we need for the butter.
So I'll go in and just add some
code to churn that cream, which
will then produce the butter
that we want.
I can then run my code, and then
we'll see in the live view that,
hey, we got the cream, we've
churned it, and then we have
butter.
So it's then, Swift Playgrounds
is then telling us that we've
successfully completed the task.
I'll go on to the next page, and
we're going to use that butter
to make some frosting.
And instead of having to take a
author-provided version of
butter, I'm going to be able to
pull my butter function from the
previous page.
So I'll tap on Bring Over My
Code, and then I see the
makeButter function that I
implemented is copied over to
this page.
Additionally, the authors
provided a makeFrosting
function, which I'll then fill
out.
So we've got all the ingredients
we need for the frosting: the
butter, powdered sugar, vanilla,
and milk.
So I'll go ahead and make this
return frosting, which I can do
by mixing together these
ingredients.
Let's look at the powdered
sugar, the butter, the vanilla,
and the milk.
And because I want to see
exactly what happens to make
this frosting, I'm going to
enable Step Through My Code.
We'll see that we start the
makeFrosting function, then we
start to make butter, and we see
the live view update as we make
the butter.
Look at the powdered sugar, the
vanilla, and the milk.
Then, we mix it all together to
get frosting.
And so there we've completed the
task on this page that we want.
So I'll move forward.
And now, we're going to go on
and start making the cake.
But if I look at this, there's a
lot of ingredients here that it
wants me to collect, and I think
there's going to be a little bit
more work that I need to make
this cake, and I'm kind of
running short on time.
So I'm going to pretend that I'm
on a cooking show and move
straight to the end.
So I'll tap on this Let's Eat
Cake page.
And Swift Playgrounds is telling
me that I should go back and
finish making this cake, but
because I can go to any page at
all, I'm able to tap in this
Start Cooking on This Page
button, which will then use the
default implementation of the
cake.
So we'll see the authors
provided a makeButter and a
makeFrosting function.
Then, the author has all of the
ingredients needed for the cake.
The ingredients are then mixed
together into a batter, which is
then baked in an oven, and then
we let that hot cake cool before
frosting it.
So I'm going to come in here at
the bottom and just call a
function to eat the cake.
Because I want to go as quickly
as possible to get through this,
I'm going to actually tap on Run
Fastest, which will then go
through each step very, very
quickly.
We can see in just a matter of
moments I have the cake that I
was looking for.
So great. I've now showed you a
handful of features that are new
in Swift Playgrounds.
Let's now take a look at how you
can adopt them in your
Playground books.
[ Applause ]
The first feature I showed you
was support for copying code
between pages.
This allows you to craft a
Playground book where the user
is constantly refining their
implementation of a particular
function or of other code.
For a user, when I look at a
page which is being used as the
source of copyable code, I
really don't actually see much
difference.
I just see an editable area
where I can insert some code.
But if we take a peek at the
code underneath, there's a
little bit of stuff that you as
the author provide to let Swift
Playgrounds know what should be
copied.
That is these copy source tags,
which allow you to tag a region
of the page with an identifier,
which then you can reference
later.
On a page onto which code is
being copied, as a user, when I
first open it, instead of being
presented with a source editor,
I'm presented with the option to
either copy my code from the
previous page or use the default
implementation.
If I haven't yet successfully
completed that page, instead of
being asked if I want to copy
code, I'm asked if I want to go
back to that page to finish it
first.
But I'm still presented with the
option to start working on this
page immediately.
Once the user makes a selection
to either copy code or use the
default implementation, then the
regular source editor's
presented with a code they
expect.
Let's take a look at how this
happens in the source code
itself.
And it's very similar to the
other case.
Instead of providing copy source
tags, though, you provide a copy
destination tag, which tells
Swift Playgrounds the page from
which you want to copy as well
as the identifier on that page
for the range of code which
should be copied because you're
able to copy code from multiple
sources onto a single page.
Then, you place this inside of
the editable code region into
which you want to place the
copied code.
There's also a little bit of
stuff that needs to be placed in
the manifest file to support
copying code between pages.
So there are a couple of keys,
the ready to copy instructions
and the not ready to copy
instructions.
These refer to markup text,
which is shown with Swift
Playgrounds is either ready to
copy because the user has
successfully completed the
previous page or when it's not
ready to copy because the user
hasn't yet finished the page.
And you're able to provide the
text that makes sense for your
playground book to tell the user
what they should be doing here.
There are also a handful of
optional keys -- the
CopyCommandButtonTitle key, the
NavigateCommandButtonTitle key,
and the
DefaultCommandButtonTitle.
These all refer to the titles of
the buttons so that you can
customize them to your
Playground book.
It's worth noting, though, that
if you don't provide any of
these keys, Swift Playgrounds
will use a default title
instead.
It's not possible to hide any of
these buttons in the UI.
The other feature, which I
showed you, was support for
controlling the speed of
execution in Playgrounds.
Swift Playground now supports
both stepping through code as
well as running faster than the
default.
The Step Through My Code mode as
well as its sibling, Step
Slowly, is enabled for all
Playgrounds.
This is because users should
always be able to enable this
mode to see what's happening in
their code.
Run Faster and Run Fastest,
however, we can't enable those
by default because not all
Playground books can actually
run faster.
Some operations, you just can't
make it go faster.
So Playground pages in books may
opt into run faster or in
running fastest.
Let's take another look at how
Step Through My Code works.
So when the user taps on the
run, the speed control, rather,
and selects Step Through My
Code, then their, the code runs
normally, but the line of code
which is running is highlighted
on the left.
Let's see how this works under
the hood.
So if you have in your
Playground book some
straight-line code that's
running synchronously like this
where we create a string,
iterate over each character of
the string, and then print it
out, there's actually nothing
that you need to do.
Swift Playgrounds will
automatically insert delays so
that every visible line of code
takes an appreciable amount of
time so this code will highlight
correctly.
There is one case which needs
some special care, and that's if
you as a Playground book author
are providing asynchronous API
to your users, which under the
hood is actually implemented
asynchronously.
So in this case, we have a
moveForward function, which is
going to just schedule some work
on a background queue.
While this would be a good
practice in an app, it's not so
great for a Playground because
when you -- when the user calls
the moveForward function, it
will only be highlighted for the
amount of time it takes to
schedule this background work,
not for the amount of time it
takes to actually move forward.
So instead, you need to make
your functions which appear to
be synchronous actually be
synchronous, and you can do this
with a little bit of code.
So first, at the top of your
function, you can get the
current run loop and then have a
variable, which indicates
whether or not the asynchronous
operation has completed.
Then, at the end, you need to
make sure that the function
doesn't exit until the
asynchronous operation has
finished.
So you can do that by having a
while loop which keeps the
current run loop running until
the didFinish flag is set to
true.
In the callback, after your,
whatever work you would be
normally doing in the callback,
you need to do a little bit
extra.
First, you need to set the
didFinish flag to true so that
that while loop will exit.
You then need to run a lock on
that run loop to tell it to stop
so that the CFRunLoop run call
will terminate.
And then, because of how the CF
API works, you need to tell that
run loop to wake up so that it
will actually perform with the
block that you scheduled.
The other half of speed control
is support for running faster
than the default speed.
Again, this is accessed through
the speed control in the bottom
left of the live view.
The user can tap on Run Fastest
if it's supported, and then the
Playground book does whatever it
can to run faster or run
fastest.
Since not all Playground pages
can actually run faster, this
requires an opt in from the
author to enable these modes,
and that's by setting the
maximum supported execution
speed to the maximum speed at
which you can run.
If you leave this out, Swift
Playgrounds will infer the
default, the speed to the normal
speed, which would mean that
only the Run My Code Speed would
be shown.
But you can also set it to
faster or fastest depending on
the fastest possible speed you
support.
To adopt this feature, you just
need to listen for a
notification, which is posted
when the user changes the
execution speed.
That's this
playgroundPageExecution
ModeDidChange notification.
Instead of this callback, you
can then ask the current
Playground page for its
execution mode and then update
animation speeds or anything
else for the new mode that the
user has selected.
So with that, I'd like to bring
my colleague, Grace, up on stage
to talk about a handful of the
other enhancements we've made to
Playground books.
Grace?
[ Applause ]
>> All right.
Thanks, Connor.
I'm so excited to talk to you
about some of the enhancements
that have been made to the
Playground book format to better
serve you all as authors and
developers.
The first thing that I'd like to
talk about is the introduction
of the minimum Swift Playgrounds
version key.
This is an optional string that
will allow you to specify the
minimum app version that can be
used to open your documents.
Next, let's talk about the
deployment target and the Swift
version keys.
These keys have been present
since the original release
Playground book format, but
earlier versions of the app had
hard-coded iOS 10.0 and Swift
3.0 to these keys.
However, now your selections are
honored.
Additionally, Swift version is
now a required key for you to
fill in.
This is all stored in the book
level Manifest.plist.
And for Swift version, you
should use the major and the
minor version of Swift that you
used to write your book.
Swift Playgrounds now allows you
to specify subtitles for your
document.
This is another optional string
in the book level
Manifest.plist, and if you don't
provide it, Swift Playgrounds
will just fill in the Swift
version of your book.
So here in Grace's Kitchen, I've
used Cooking with Playgrounds
for my subtitle, and it shows
below my document in the
document picker.
Next, Swift Playgrounds has
replaced the single Resources
directory with two resources,
two Resource directories,
PublicResources and
PrivateResources.
Any files that you want your
users to be able to access while
they're using your book, either
in the file picker or the image
picker, should go in the
PublicResources directory.
And any files that you don't
want your user to be able to
access should go in the
PrivateResource directory.
Those files could be
localization files or hints
files that you don't want your
user to peek at while they're
using your book.
Next, Swift Playgrounds now
allows you to create specific
runtime issues that will show up
in line.
So to do this, you can call
fatal error, and the string
that's passed in will show up in
line with other results while
your user is using the book, if
they ever hit it.
So next, let's talk about how
you can support user-editing in
your Playground books through
the use of template pages.
A new feature in Swift
Playgrounds allows your users to
add, remove, rename, duplicate,
and reorder pages within your
Playgrounds books.
You can see this functionality
already in our starting points
templates.
But to show it off a little bit
here, I'd like to hop into a
demo.
[ Applause ]
So I'm going to go ahead and
open up Grace's Kitchen, and
this is a book that I created
for my users to have a recipe
book on their iPad with Swift
code.
So in my kitchen, I have three
recipes: butter, frosting, and
cake.
And my mom would tell me that
this is not the most healthy
recipe book, so I want my users
to be able to add some of their
own.
So if we go into the Edit
button, I can add a page through
the + button in the top left.
And then, I'm going to add a
recipe for pancakes.
Not the most healthy, but we're
getting there.
So if I tap Done and then I
navigate to my Pancakes page, we
can see that I as the author
have provided some prose, and
then for sake of time, I've also
written out our pancakes recipe
in code.
And now, when I tap Run My Code,
this Playground book will
intelligently generate a recipe
for pancakes.
And to show you how you can
adopt this in your books, let's
hop back to the slides.
[ Applause ]
The first thing that you'll need
to do is specify a template
page.
Template pages are supported in
the last chapter of your book,
and this key should go in the
chapter's Manifest.plist.
And what I mean by template page
is the page that's instantiated
when your user adds a new page
to your book.
So to specify this, you should
use the TemplatePageFilename
key.
This is a string, and the string
should be the value of the file
that should be instantiated.
So here I've used
Template.playgroundpage.
Next, I'd like to talk about the
difference between
InitialUserPages and pages.
Both of these are keys that
store an array of page
filenames.
InitialUserPages are pages that
the author has created that they
want the user to be able to
interact with in that way of
duplicating, renaming,
reordering, et cetera.
If you don't want your user to
be able to interact with them in
that way, then you should store
them in the pages key instead.
So here's the file structure for
my Playground book.
We can see that the
Manifest.plist in -- is in the
chapter level, and that should
hold the InitialUserPages key
and the TemplatePageFilename
key.
And then, in my Pages directory,
I have my Butter, Frosting, and
Cake pages, and I also have my
Template page, which is what
should be instantiated when a
user adds a page.
And next, I'd like to pass it
off to Najla to talk about
localization.
[ Applause ]
>> Hi, everyone.
So as Connor mentioned earlier
today, the Swift Playgrounds
app, along with Apple-provided
content, is now localized into
six languages.
But if you wanted to localize
your own Playground books, then
you're not limited to these six
languages.
Localizing a Playground book is
a lot like localizing an app in
that you have the same resource
[inaudible] directories, and
strings files.
So to localize your Playground
book, you're going to need to
set the development region key.
The development region key
specifies what base language
this book was written in, and
it's required even if you
haven't localized your book yet.
If you've, if you are familiar
with localizing apps, the
development region key is the
Playground book equivalent of
CFBundleDevelopmentRegion.
So just like apps, Swift
Playgrounds supports localizing
content with strings files, and
there are four kinds of strings
files that we want to mention
today: ManifestPlist, Prose,
EditableFields, and QuickHelp.
Now, earlier in this session,
Grace talked about public versus
private resource directories in
your Playground books.
And since the strings files are
used to communicate with the app
itself, it's best that they live
in the PrivateResources
directory of your book.
So let's hop into some more
detail about these strings
files.
So Swift Playgrounds supports
localizing content from
ManifestPlists using a
ManifestPlist.strings file.
And an example of some
localizing ManifestPlist content
in Learn to Code 1 is the table
of contents that you can see
with all of the exercises.
Let's take a closer look at the
first exercise in Learn to Code
1, which is called Issuing
Commands, and here it is in
simplified Chinese.
So Playground book has a
ManifestPlist at every level --
at the book level, chapter
level, and page level.
Information about an exercise's
title is pulled from the page
level ManifestPlist, and here's
the page level ManifestPlist for
the Issuing Commands exercise.
You can see that there's a key
called Name, and its value is
the title.
The simplified Chinese title for
Issuing Commands is pulled from
the simplified Chinese
ManifestPlist.strings file.
And here you can see that the
name is defined as a simplified
Chinese of Issuing Commands.
So Playground markup blocks are
also supported.
You can also localize Playground
markup blocks in Swift
Playgrounds using a
Prose.strings file at the page
level.
And here's some prose from
Issuing Commands that's been
localized into German.
To do this, in the
Contents.swift file, it
specifies a block of localized
prose using a localized tag,
which takes a key.
This key is then defined in the
German Prose.strings file as the
German translation of our prose.
You can also localize editable
fields in Swift Playgrounds by
using an EditableFields.strings
file.
An example of an editable field
in Learn to Code 1 is that Tap
to enter code string that you
see in some exercises.
And here it is in French.
So in the Contents.swift file,
it specifies an editable field
with an editable code tag that
takes the key that's the
placeholder that we want to
localize.
In our case, the placeholder is
called Tap to enter code.
That placeholder is then defined
in the EditableField.strings
file for French as the French
translation of Tap to enter
code.
So in Swift Playgrounds 2.0,
which you got a beta of on
Monday, you can localize API
documentation.
And here's an example of some
API documentation from Learn to
Code 1.
It's a move forward command that
you can use to move Bite in its
little world.
To localize your API
documentation, you can use a
QuickHelp.strings file.
In you AuxiliarySources file for
your exercise, it specifies a
block of localized documentation
by using a localization key.
That key is then defined in the
QuickHelp.strings file.
In our case, our documentation
is in English, so this is the
QuickHelp.strings file for
English.
So along with using strings
files to localize your
Playground books, you can use a
Hints.plist to localize default
hints.
Here's an example of a hint from
Issuing Commands that's been
localized into Japanese.
So Issuing Commands only has one
hint.
So in our Hints.plist for
Japanese, we have one entry that
represents our one hint, and its
value is the Japanese
translation of our hint.
So the localization support in
Swift Playgrounds allows you to
create really detailed and
immersive Playground books for a
larger audience, and we're super
excited to see all of the
Playground books that you
localize.
If you want more information
about how to localize your
Playground books, we have a
session later today on
Localizing Content for Swift
Playgrounds at 3:10.
So let's switch gears and talk
about the PlaygroundBluetooth
API, which is in Swift
Playgrounds 1.5, which shipped
on Monday.
So Swift Playgrounds provides
full access to CoreBluetooth,
which allows you to integrate
accessories into your Playground
books.
And if you've taken a look at
some of the accessory Playground
books available, you'll notice
that they all have a really
great and consistent Playground
book experience.
And the Playground book API
allows you to integrate that
experience into your own
Playground books.
So there are two main components
of the PlaygroundBluetooth
framework.
The first is the
PlaygroundBluetooth Central
Manager, which is responsible
for all the logic to interact
and manage accessories.
The second is the
PlaygroundBluetooth Connection
View, which allows you to
customize a user interface that
users use to interact with
accessories.
They both have their own
delegates, and the
PlaygroundBluetooth Connection
View has a PlaygroundBluetooth
Connection View data source that
provides specific information
about an accessory.
So the PlaygroundBluetooth
Central Manager provides an
interface for connecting to and
interacting with accessories.
It's really similar to
CoreBluetooth's
CBCentralManager, so if you've
implemented that before in a
Playground, then you could reuse
code here.
The Central Manager can take an
array of accessory UUIDs to
search for, or you can specify
to search for all accessories in
the vicinity.
The PlaygroundBluetoothCentral
ManagerDelegate has methods for
interacting with accessories,
such as connecting to and
disconnecting from accessories.
So the PlaygroundBluetooth
ConnectionView provides an
interface for displaying the
connection status of accessories
and allows users to discover,
connect to, and disconnect from
accessories.
Here's an example of a
Connection View that I've
customized because my Playground
book searches for a Bluetooth
printer.
The PlaygroundBluetooth
ConnectionViewDelegate has
methods for customizing the UI
and handling user interactions.
For example, you can use the
titleFor delegate method to
customize the Connection button.
In my case, I've customized it
to say Select Printer.
Lastly, the PlaygroundBluetooth
ConnectionViewDataSource is a
protocol that's adopted by the
Connection View that provides
specific information about an
accessory.
In my case, the data source
provides information about my
printer, its title, which is
Najla's Printer, and that cute
printer icon that's to its left.
You can provide this information
using its one method.
So let's hop into a quick demo
of the PlaygroundBluetooth
framework in action.
[ Applause ]
So earlier today you saw Grace
create a Playground page for
making pancakes, and I love
pancakes a lot, but I cannot
cook.
So what I would really like to
do is I would like this
Playground book, in addition to
generating the recipe, to be
able to connect to a Bluetooth
printer so that I can print it
out and then give it to someone
when I want them to make
pancakes for me.
So let's go into my Playground
book.
And what I've done is I've taken
Grace's Pancakes page and I've
integrated this cute, little
Bluetooth printer.
So when I tap on Connect
Printer, it's going to search
for my printer.
And in addition to being able to
generate this recipe, when I
select Najla's Printer, it's
going to print out the recipe.
So keep an eye on the printer
and cross your fingers.
Here we go.
Yeah!
[ Applause ]
OK.
All right.
Nailed it.
So, [laughs] so now, I have the
recipe for pancakes and I can
give it to Connor after this
session.
[ Laughter ]
[ Applause ]
So we talked about a lot today.
You got an overview of all of
the format changes that have
happened in the past year.
You learned how to integrate
copy code forward into your
Playground books so that you can
create a really nice narrative,
how to create interesting user
editable books so that users can
then modify your template pages,
how to localize your Playground
books, and finally, how to
integrate accessories into your
Playground books.
For more information about this
session, please visit
developer.apple.com.
There are also a few related
sessions that you can visit or
watch online.
Thank you for watching our
session, and we hope that you
enjoy the rest of the
conference.
[ Applause ]