Streaming is available in most browsers,
and in the WWDC app.
Authoring Rich Playgrounds
Learn about the new features in Xcode playgrounds such as inline results, auxiliary sources and resources, multiple pages, and markup for formatted text. Find out how to explore programming problems in a playground, and create engaging experiences for teaching and learning.
SAM: Good morning. So thank you all for coming to today's session on authoring rich Playground. My name is Sam, and I'm an engineer on the Xcode team.
So Playgrounds have been out for about a year now, and we've been absolutely blown away by what you guys have done with them. We've seen everything from people writing their first "Hello, world" all the way up to people writing entire books in Playgrounds. The response has just been amazing.
So today we're going to be covering a couple of things. We're going to be talking about some of the new features that were added to Playground since last year, and we're also going to cover some of the existing features that have been updated.
And we'll also cover some tips and tricks that can help you author really, really rich Playgrounds, Playgrounds that you want to share with the rest of the world. And as a bit of a bonus, we're also going to be solving a problem that's common among WWDC attendees, and that is how to explore San Francisco. So what is the problem? Well, San Francisco is a big place.
There's a whole lot to see, but really you're not going to find a lot of time to do it between the sessions and the labs and writing your next great apps.
And you probably have a flight to catch at the very end of the week, so there's an endpoint here.
And as engineers, we want to be as efficient as we can possibly be when we solve problems, which in the context of this problem means we just don't want to walk any further than we have to. So how are we going to solve this problem? Well, once we know the places that we want to visit, we're going to start out at Moscone Center, traverse all the places that we're going to see in the quickest way possible before we finally have to jet out for San Francisco International Airport. Now to do this we're going to be building a Playground.
So without further ado, let's get into that.
Okay, so I've got a Playground already kind of set up for us here, and we'll just take a quick moment to go through what we've got. So first up we've got a struct here for place. So places are going to be what we want to see when we're in San Francisco, and they're going to have a name as a string and a location as a CLLocationCoordinate2D.
Next up we've got a struct for city. So in the context of our problem this is going to represent San Francisco.
And we can think of this as a collection of places. It's our own type to hold these things. So internally we've got an array of places, and then we've got a function here that we can add places to our city.
So let's go ahead and just create our city.
We're going to call it San Francisco.
And like I said, we need to start our journey somewhere. So we're going to go ahead and create a place for Moscone Center, which is where we're going to start.
So let's create Moscone there. And then finally we're just going to add the place to our city.
So those of you who are familiar with Playgrounds are going to remember that for each line of code that executes, we get a result in the results sidebar over here.
So we can see that our add place function is called, and it looks like we are passing in Moscone Center to that.
But if you look at it further down where we've created our city and our place, all we're getting is the type. So this is handy, but we kind of want to be a bit more descriptive, especially if we're going to be creating a few more places. If we see place after place after place, we're not going to be able to differentiate these things.
So for our places, the most descriptive thing is the name. So how do we get the name to be returned in our results sidebar? Well for that we've got a new protocol, and that is called Custom String Convertible.
So what I can do is I'll extend my struct here for place to say that I'm going to be implementing Custom String Convertible.
And you can see now I've got an area here because they're not actually conforming to the protocol yet. So to actually conform to the protocol we just need to implement a re property called description.
And that's going to return a string. And for us, we want to return the name of our place because that's the most descriptive thing. So you can see now, if we look back down to where we created our place, we've got Moscone Center here instead of just the type name.
So what does this mean for our city? Well probably the most descriptive thing there is to return the list of places that are currently in the city so we can see how our journey is progressing. So we're going to go ahead and do the exact same thing for city, just implement Custom String Convertible there.
And go ahead and return the description. And for that we're going to return to places.description, so the description of our array of places.
So now you can see when we first created San Francisco, we don't actually have anything in it yet so we get an empty array here.
But then when we add our place to it, we get Moscone Center. So when we create more and more places we're going to see more and more in our results sidebar.
So one of the cool things about Playgrounds is you're always able to kind of get a deeper look and get more information about a result by clicking this Quick Look here right next to the result. So I click that for Moscone Center. We see a bit more information about our place. So we've got the name and we've got the latitude and longitude.
This is superhandy to debug but, I don't know, latitude and longitude doesn't really help me. I'm not that great at cartography. I don't know how to position these things in the world. So I kind of want to see something a bit more visual, something that just kind of screams out, yeah, that's a place.
And to me nothing screams "place" more than just a big blue dot with a name underneath, something I would put on a map. So how are we going to do that? Well normally when we kind of want to do something a bit custom and visual, we create a view. So we're going to go ahead and just create a UI View subclass here.
And we've got some of that already set up. And let's just take a quick moment to look through what we've got there.
So we've got a class here called View. It's going to be a UI View subclass. And just internally we've got another view that's going to be our big blue dot view. And then we've got a name label that's going to show the name of our place.
And then when we create this thing, we're just going to let it decide how big it wants to be. And finally in layout subviews we're just going to position these things so that blue dot's on top and the text is below.
So we'll create one of these just to see how it looks first, and we're going to pop open the Quick Look.
Cool, that's starting to be a bit more like a place, like I can see that appearing on a map. But how do we actually join these things up so when we Quick Look our place, we actually see our view returned? Well for that we've got another new protocol, and that protocol is called Custom Playground Quick Lookable.
So what Custom Playground Quick Lookable does is if you implement the required method there, it will allow you to return another type to appear as the Quick Look for your type or any other type. So that could be something like a Bezier path or a color. But for our case we want to return our view.
So what I'm going to go ahead and do is just create an extension for our place and say that that's going to be implementing Custom Playground Quick Lookable.
And so again, we've got an error here because we're not actually conforming to that yet. So we'll just add the conformance there by implementing the Custom Playground Quick Look method.
And so inside that method we're just going to return a Playground Quick Look that's reflecting the view. Now because we're an extension on place, we have access to the name of our place, so we can just go ahead and throw that into the initializer there.
So now, when we Quick Look our place again, we should see, cool, we've got Moscone Center there and everything is starting to look a bit more like a place.
But we've kind of ended up in a bit of code here that's just hanging around in the rest of our Playground. And we kind of want to move this just out of the way because no one else really cares about this. It's not really helping us to show what we're trying to do. So for this we're just going to go ahead and, well first we're going to delete this place view here because we don't need that.
And we're going to grab the view here that we declared and we're going to just go ahead and put that directly in our extension on place.
Clean up some white space here.
And so when the Playground reexecutes you should just see, cool, exactly what we saw before but now that view code is all just embedded within our extension so it's nice and clean.
What does this mean for our city? So if we're following the kind of pattern that we want to see what's in our city at the moment, then we kind of want to see what places are in our city. And so this is going to happen by default. So if we click on the Quick Look for Moscone Center, we can see we've got a place in there.
And we can see the Moscone Center, little blue dot in there, but as we add more places, it's not going to help us all that much. It's just going to be listed up. We kind of want to visualize how these things are positioned to each other based on their location so we can start working out, well if we're at Moscone Center, you know, we're probably not going to go directly to Golden Gate Bridge, we'll probably go to something like Coit Tower first.
So for that we're going to create another extension. This time on City. It's going to be again, Custom Playground Quick Lookable, and we'll do that here. I've got a bit of code ready for that. And we'll just walk through this real quickly. So it's an extension on City, same way I implemented Custom Playground Quick Lookable. We have conformed to that by implementing the Custom Playground Quick Look method here. And then inside we're just going to create a view, work out where these things would be positioned within that view based on their latitude and longitude pair.
So now when we Quick Look our city we should see, cool, Moscone Center on the eastern side there by a little blank map, but we're starting to build up a picture about what we're actually going to be seeing while we're here.
So now that we've got this kind of in place, we're going to add a few more places. But before we do that, I kind of want to keep this map view here just open all the time, or at least visual. I'm opening and closing it a lot when I want to check my code. Well for that we've got this cool little thing called an inline result. So right next to this Quick Look button if I click Show Result, that's just going to go ahead and throw that Quick Look directly under the line of code that generated it so I can keep working on my Playground and see it constantly running and see what's actually happening.
And the cool thing is it's kind of small right now so we can actually just grab it and drag it out like we would any other window and make it as big or as small as we want.
So cool, we can start working on the rest of our Playground, adding our places to that, and we should see them pop up in our map.
So what do we want to see? I've heard Golden Gate Bridge is pretty cool and famous, so I'm going to go ahead and check that out. We'll add that to the places that we want to see. And when the Playground reexecutes we'll see that based on the location of our Golden Gate Bridge, we're going to be putting that in the northwest, which if my knowledge of San Francisco geography is correct, I think that's true.
What else do we want to see? Sutro Tower, how about that? That's kind of like something out of War of the Worlds, going to chuck that in and put that in the places as well, going to chuck in Sutro Tower. And the Playground reexecute and then we'll see Sutro Tower down south.
So we're starting to get a really good picture of how our places are built up, and as we add more and more of these things we can see, based on where their location is, how we're going to be getting between them. I'm just going to go ahead and switch back to slides now.
So you may have noticed that our Playground got a little bit long towards the end of that. We had some view code and some extensions, and there's a lot of stuff that wasn't really helping us to kind of describe the problem.
It was not that great. So what we kind of want to do is pull a lot of that code directly out of the main body of the Playground but still keep it around so we can use it. So to show you how we're going to do that, I'd like to invite my colleague Connor up to the stage.
CONNOR: Thank you, Sam. Hello everyone. My name is Connor and I'm an engineer on the Xcode team. Today I'll be talking to you about how you can make your Playgrounds more powerful and making them even more focused on the problem at hand. In order to do this, let's dive straight into a demo where I'll take a look at the Playground that Sam just started and add support for finding the best path through San Francisco while making it more focused. Let's head over to the demo machine now.
Okay, so here we are in Xcode. And if you noticed, we've got basically the same Playground that Sam was just showing you. We start off by creating a city for San Francisco. We create a few places, and then we add those places to the city, ending up with this great inline result showing you what we've built up so far.
If you notice though, there is one major difference, and that's that the entire Playground now fits on one screen. I'm still using my custom city in place structs, though. And even though Swift compiler's pretty great, it isn't able to actually create structs out of thin air yet. So what we're using instead is a feature which we added in Xcode 6.3 called Auxiliary Sources.
The auxiliary sources are additional Swift source files, which are embedded inside of your Playground, and they're precompiled into a separate swift module.
Because they don't need to be interactive, they can perform better than the Playground itself.
Additionally, since they're in a separate module, you can use access control to ensure that only the bits of your Auxiliary Sources which you want to be made visible to the Playground are exposed, so you can hide a bunch of internal implementation details in the auxiliary sources.
To find them, we'll go ahead and open up the navigator. In your PC, we've got the Playground at the top. And then inside of the Playground we've got thr sources folder. In the sources folder we've just got a collection of Swift source files. I can open one up, and here we've got city.swift, and it's basically the same struct that Sam was just working on with a few minor modifications.
First, I'd mark it as public because we want the city to be able to be used by the main Playground itself.
I've also added support for travel times to the city so that we can actually keep track of the time needed to get between each place in the city.
If we look at place.swift, we see the same basic stuff here. So same struct that we had before, just modified to become public and with a few modifications to work with the updated city struct. I have a couple of other auxiliary source files, which I'm not going to dive into. One has to do with property list serialization, and the other one is kind of a grab bag of utilities that I've just pulled together from other projects that I've worked on, and it will help me out when I'm implementing our algorithm.
Let's switch back to the main Playground now. And so here we see that we have, you know, the start of a city. We're starting to build something up, but I want to visit more than just these two places, and we also want to have all the travel times between places. I could add all of that in code, but that wouldn't be great because we'd have a bunch of code at the top of the Playground, which isn't really instructive to what we're trying to show. So instead I'm going to take advantage of another feature that we have in Playgrounds called Embedded Resources. The embedded resources are files which are copied into the Playground itself and are made available to the Playground when it runs as the resources of the main bundle. And they live in the navigator as well, in this little Resources folder. What I'm going to do is I'm going to drag in a plist from the Finder that defines the city of San Francisco. So I'll go ahead and do that. I've got the SanFrancisco.plist. I can drag it in here, and it will get copied inside the Playground file. This means that we don't have to worry about any external file references breaking when you decide that you want to share your Playground with someone else. It's all just in there so you can send it as a discreet unit.
Let's take a look at what this plist looks like.
And so first we've got an array of places.
Each place is just mapping a latitude and a longitude to a name. And then let's scroll down a bit in this file. We'll see here that we've got an array of travel times. And a travel time is just mapping two places to the time and minutes needed to get between those places, these times gathered using the trans directions in iOS 9.
Now even though I'm calling these destination and origin, I am making this a little bit simpler and assuming that the time it takes to get from point A to point B is the same as the time to get from point B to point A, so these are actually bidirectional travel times.
Switch back to the main Playground now.
And instead of having all this code here, I'm just going to select a bunch of it and delete it and replace it with a call to load my city of San Francisco.
So start out by calling this initializer on city, call contents of property list at URL. And what it will do is that it will just simply look at the main bundle to get the URL for a particular resource, this San Francisco plist.
We know that we've just added this into the Playground, so I'll force unwrap that optional so I can pass it to my initializer.
Additionally, this initializer has been updated to take advantage of Swift 2's error handling feature. So I'll indicate to the compiler that I know it can throw an error with the Try keyword and add an exclamation point at the end because I don't want to add a bunch of error handling code that's not instructive for what I'm trying to show in this Playground. Additionally, I know that this plist is correct, so it's okay for me to load it this way.
We can show this inline now.
And let's make it a little bit bigger. So here we've got our city now. We've got the Moscone Center, Coit Tower, Fisherman's Warf, the Painted Ladies, Sutro Tower, Golden Gate Park, Golden Gate Bridge, and not pictured in our map but still there is the San Francisco International Airport. And you can see all the connections leading to the airport.
So great, we've got a city that we can work with now. But now that I look at it, I think I'd like to make this Quick Look a little bit nicer by adding in some icons for all the places that I'll visit.
And it just so happens that on my desktop I've got this Images folder. And if I open that up you can see, here we've got a bunch of icons for each of these places. So I'll just go ahead and take those and drag them into the Resources folder as well. Just like the plist, they'll be copied in there and be made available to my Playground. I do need to update my Quick Look to take advantage of these images, though.
So I'm going to go ahead and open up the place.swift file in the Assistant editor by option clicking on it. I will then find this location view, and instead of initializing it to just the blue circle, we're going to update this to take advantage of the images. So I can take all that code and we will update our place view.
We'll start off by getting the image associated with that, create an image view, and then set a couple of properties before returning it.
Now when I save this file, Xcode will notice that the auxiliary sources have changed and will automatically recompile their Swift module. Once the compile finishes, we'll rerun the Playground itself and so the Quick Look will update automatically. I didn't have to do anything more than just make an edit and save to disc.
So great. That Quick Look looks a lot better. Let's switch back to the standard editor and start working on our algorithm. I'm also going to hide the navigator because I don't need it and I'd like the extra space. So we're implementing a brute force algorithm to find our way through San Francisco. And in order to get that started, we need to just get all the paths through San Francisco.
So we'll start with this and what we do is we just call this all paths through city method on our city struct, telling it that we want to start at the Moscone Center and that we want to end at the airport. And if you look in the sidebar at the right, we see that we have 720 paths. I'll then implement the algorithm itself, and here we're just starting with a couple of variables which will hold our results, one being for the best travel time and one for the best path. We'll then iterate over all the paths through the city, getting the travel time for each path.
We'll then compare that to our best travel time, updating our result variables if necessary.
Finally, at the end we can see here the best travel time through the city so we'll show that inline and we'll see that it will take 235 minutes to get between all these places if we never stopped and somehow managed to make all of our transit connections. We can also add the path itself inline, and so we can see that we'll start off at the Moscone Center, which is what we expected. We'll then head over to Painted Ladies and Sutro Tower. Then up to Golden Gate Park and Golden Gate Bridge. Then we'll head ove to Fisherman's Warf and Coit Tower before heading down to the San Francisco International Airport to catch our flight.
So great, I've got all the information that I need to go see the sights so let's switch back to slides now to take care of that.
So I just showed you how you can use the auxiliary sources and the embedded resources features in Playgrounds to make your Playgrounds more powerful while keeping them incredibly focused on the task that you're trying to actually achieve.
I think I have something pretty interesting here, and I'd like to share it with people when I get back home from the conference. But it's not quite polished enough that I'd be ready to show it to other people.
We've added a bunch of great features in Xcode 7, though, which makes sharing your Playground with the world even better than before. So now I'd like to invite my colleague Matt up on stage to show you about that. Thank you.
MATT: Thank you so much, Connor. Hi everybody. My name is Matt and I'm also an engineer on the Xcode team. And I think Connor and Sam have done a wonderful job of putting together a really awesome Playground that shows people how to get around San Francisco in the best way possible. But it really isn't ready to be shared with the world, so I'm going to show you a few tips and tricks you can do to make your Playground richer and more engaging so you can put it out there on the Internet and people will love it.
So you can see here we have the Playground as Connor just finished working with it. And we have a very simple algorithm that Connor wrote, which walks through all the possible paths in the city of San Francisco and compares them to one another. And it was great for you guys because we had Connor here explaining every step of the way as he was implementing it.
However, when we share this with the world, unfortunately not everyone was here at WWDC. So we'd like to add a little bit of information, a little bit of context, so that people who view this Playground later will understand exactly how we got here.
So we introduced a new feature in Xcode 6.3 called Playground Markup Comments. And that allows you to annotate your Playground with rich prose formatted in beautiful displays so that people can better understand exactly how you got to the Playground that you arrived at. So just to give us a little bit more space I'm going to start by putting away our Quick Look here, and I'm going to replace the comment at the top of the Playground with a little bit of information about exploring San Francisco. Now you can see this looks a lot like a comment that you would put anywhere else in your source file, but the biggest difference that differentiates a rich markup comment from another comment is the colon. We've got a colon after the first asterisk in the comment, and we can actually do that as well with single-line comments. So add a colon over here as well. And when I go up to the Editor menu and choose Show Rendered Markup, you'll see that this rerenders and now we have these beautiful rich blocks of text that we can use when we want to explain what's happening in our Playground.
We can actually format them a little bit more as well. So if I switch back to the raw markup, I can use a number sign to indicate that we'd like a heading to indicate "Exploring San Francisco." And I could use, for example, asterisks around "most efficient," to indicate that we want this to be in italics.
So if I render this again, you'll see that now we've got a nice, big, bold heading that explains exactly what we're going to be talking about. So that's a great start. I'd also like to annotate a little bit more about the other things that are happening in this Playground.
For instance, all paths. All paths through the city is a little bit vague and it's a little bit mysterious, so I'd like to add a little bit of information about how those paths were computed.
And the algorithm itself could probably use a little bit of explanation as well.
So we'll document the algorithm.
And if I switch back once more to the rendered markup, you'll see now we're starting to get something that looks much more like almost a book, something that a user could sit down with, walk through, and understand. We've even got a nice bolded list, which we use by inserting an asterisk at the start of each line.
So that's pretty awesome.
But it isn't quite rich enough. We want to take it just a little bit further. So to do that, we're going to do a feature that we introduced a long time ago on Playgrounds that is now even better on Xcode 7, and that's Live Views. So I've taking the liberty of writing a live view. I've put it into the auxiliary sources already, and we're going to start by showing it alongside this Playground to visualize exactly how this algorithm works.
So the first step to using Live Views is that you need to import XCPlayground, which is a module that has a bunch of really awesome functions that allow you to do some pretty cool stuff with Playgrounds. Now you're also welcome to do this in your auxiliary sources and create, for instance, a helper method that shows your live view. You don't need to do that in the main Playground. But I'm going to do it right here just so it's must clearer how it all works.
Once we've got our city of San Francisco we're ready to show it, so I'm going to instantiate a view controller with our new city view controller, and I'm going to pass in the city of San Francisco. I'm then going to use the XCPShowView function, give it the name of a city, and pass in our view controller's view.
So if now I switch over and show our Assistant editor after the Playground reexecutes, we'll see a live view that looks just like the city of San Francisco. And you can see that we've taken the idea of the rich Quick Looks that we've already added and we've laid them out onto something that looks a little bit more realistic so people won't be quite so disoriented.
So that's the first step. But we also want to show how the algorithm itself works, and to do that we're going to add a little bit of code that updates the live view as we're walking through our algorithm. So we have a loop here that iterates through all the paths and all paths, and I'm going to replace that with the method on my view controller called Visualize Path Iteration.
And I've written that to use a trailing closure so I can actually update it so that it looks almost exactly like the loop code that we originally had. And I'm also going to add a line in here so we can tell the view code controller once we've found a better path, we want to show that better path, especially on our map.
So it will execute through once more, and when it does you can see now we're visualizing all the paths as we traverse them through the city of San Francisco. And this is great because now people who are reading this program can really see exactly how we got to our answer of how to find the best route through the city. You might notice something, though, and that's that it's checking a lot of paths. It's actually checking 720 paths, as Connor pointed out earlier, and that's quite a few. In fact by default, live views run for 30 seconds, which you can change with this control down in the bottom right of the Playground. And that isn't even enough time to make it through all of the paths through the city of San Francisco. Now we've artificially slowed it down a little bit so you can see the paths as we iterate through them on the live view.
But I think when people see that, they're going to say, well there must be a better way. And in fact there is. And we'd like to start out by showing that to our users from the very beginning.
So we're going to use another algorithm. It's a nearest neighbor approximation algorithm, and what that's going to do is it's going to be as though we were starting at Moscone. We looked at all the places that we wanted to visit and then we just picked the closest one and we went to that. And then from that place we'll pick the next closest and we'll go on from there. And if I wanted, I could implement that right at the bottom of this Playground document. But that would start to get a little bit confusing, and it's not as focused. One of the wonderful things about Playgrounds is that they're very, very focused pieces of content, so we want to be able to preserve that.
And to do that we have a brand new feature in Xcode 7 called Pages, which allows you to have multiple pages of content in a single Playground document.
So to do that we're going to open the navigator. And in the bottom-left corner we'll choose the Plus button and click New Page.
And that's going to create a brand-new page in the same Playground. I'm going to call this one Nearest Neighbor, after the name of the algorithm that we're going to be using. And then we're going to rename our initial Playground to Getting Around. Now Pages are also pretty cool because, like the auxiliary sources and resources that you can include in a Playground at the top level, you can do that on a per page basis. You can see I can twist open any one of these Playground pages and we can embed their own sources. Any resources of the same name, for instance, will also override those in the larger Playground. So if you want to have some different branding on each page or a particular image that is specific to the concept that you're teaching in each different page, you can do that pretty easily.
So right now I'm going to switch the nearest neighbor page, switch back to the raw markup. And I've already written up this algorithm, so I'm just going to go ahead and paste that in.
So what we have here is a Playground with nearest neighbor approximation, and you can see when this one runs it examines way fewer paths through the city.
Now this is not an optimal algorithm in that it won't necessarily find our best path through the city. But you can see it's already done, which is pretty cool. So this is a great way of showing, alright, there might be more than one answer to this problem. And if we render this, we can also show where we got this path from. We actually edited this on, we found the information for this algorithm on Wikipedia. Unfortunately, I invented a little too far. And we can embed a link right into the Playground, which would take us to the page explaining exactly how this algorithm works. We could also link between pages in a single Playground and bring it back to our optimal path that we discovered earlier. And in fact, when we look at the number of paths and the different paths that we took through the city and how long this algorithm takes to execute, we examined 720 paths on this one and find a path that is only 21 minutes faster than the other page. So that gives people a frame of reference for saying, maybe there are other ways to do it. And they can use a Playground to experiment with that all by themselves, which is pretty awesome.
So that's adding sections of markup, Pages, and Live Views in Playgrounds.
So we have a lot of really awesome new features in Playgrounds in Xcode 7. We showed you Custom Quick Looks, which allow you to take your custom data types and make them come alive in the same way that a lot of the built-in types do. We've also got inline results, which stay with the Playground, so you can show results right next to the line of codes that produced them, and then those go with the Playground when you share it with other people. We also have Auxiliary Sources so you can take the code that goes with your Playground that isn't as instructive as the code in the main Playground and put those aside so that your users see only the API and the functionality that you want them to see. We also have Embedded Resources so you can bring in images and make things feel even more alive and more engaging. We've got brand-new markup comments that are a really great way to guide your user through your Playground when you can't be there with them, enhanced Live Views which are better than ever, and Pages which allow you to cover multiple topics in a single Playground without distracting from the focus of each of them, which is really, really cool.
So that is what we have in Playgrounds in Xcode 7.
For more information, you can visit the website. We've got documentation up there and the developer forums. You can also talk to Stefan Lesser, our Developer Tools Evangelist. We have a number of related sessions that are going on. We've got Swift in Practice, which is happening tomorrow afternoon, and Building Better Apps with Value Types in Swift. Also the What's New in Xcode and What's New in Swift sessions will be great all week. Thanks so much everybody and enjoy the rest of the WWDC. [Applause]
Looking for something specific? Enter a topic above and jump straight to the good stuff.
An error occurred when submitting your query. Please check your Internet connection and try again.