Video hosted by Apple at devstreaming-cdn.apple.com

Configure player

Close

WWDC Index does not host video files

If you have access to video files, you can configure a URL pattern to be used in a video player.

URL pattern

preview

Use any of these variables in your URL pattern, the pattern is stored in your browsers' local storage.

$id
ID of session: wwdc2020-10649
$eventId
ID of event: wwdc2020
$eventContentId
ID of session without event part: 10649
$eventShortId
Shortened ID of event: wwdc20
$year
Year of session: 2020
$extension
Extension of original filename: mp4
$filenameAlmostEvery
Filename from "(Almost) Every..." gist: [2020] [Session 10649] Add custo...

WWDC20 • Session 10649

Add custom views and modifiers to the Xcode Library

Developer Tools • iOS, macOS, tvOS, watchOS • 13:10

The Xcode Library is an easy way for you to discover available SwiftUI views and drag and drop them to the Xcode Previews canvas, enabling rich visual editing of your app. We’ll show you how to extend the content of the Xcode Library with your own views and modifiers, optimizing for reusability and discoverability within your app or Swift packages. For more on Xcode Previews, check out "Structure your app for SwiftUI previews", and "Visually edit SwiftUI views".

Speaker: Anton Vladimirov

Open in Apple Developer site

Transcript

Hello and welcome to WWDC. Hello and welcome to WWDC. My name is Anton, and in this session we are going to take some time to talk about your SwiftUI views and modifiers inside Xcode. Since the very beginning, Xcode Previews was built to put your content front and center. For example, your views are previewable without doing any extra work, and most user modifiers are inspectable right out of the box as well.

In Xcode 12, we are taking a step further by letting you add your SwiftUI views and modifiers to the Xcode Library. Let's take a step back and talk about some benefits of the Xcode Library. First, discoverability. As your code base grows, it can become hard for the users of your code to know exactly what the key parts of your API are. The Library is a place where developers look to discover available visual content, and by placing your most important and most reusable views there, you make them easier to find.

But discoverability is just the beginning. Since each asset in the Library comes in a compilable form, it can serve as an excellent way of educating the users of your code about how any particular view or modifier was intended to be used with a clear example that can often serve as an excellent starting point.

And finally, every asset in the Library can be dragged and dropped into the canvas, and inserting views and modifiers from the Library allows Xcode Previews to continue rendering, enabling rich visual editing for your content. So now let's look at what you need to do to expose your content in the Library.

And this is where the Library takes a page from Xcode Previews' playbook. Since the view or modifier you're adding to your Library is defined in Swift code, the most natural place to define how to expose it in the Library is also in Swift code, right next to the view or the modifier itself. The big advantage of doing that is that the compiler can help us ensure that the Library items remain functional as your API changes. To extend the Xcode Library, we need to declare a new type that conforms to the Library content provider protocol.

This protocol has two requirements: the views property, which you can use to extend the views Xcode Library, and the modifiers function, which you can use to extend the modifiers Xcode Library. There are some differences between the two which we will discuss in a moment, but for now notice that both of them return an array of Library items. Let's look at how to create a Library item.

The minimum amount of data that Xcode needs to create a Library item is a completion that will get inserted when the user picks that item from the Library. But you can also optionally specify additional information, such as that Library item's visibility... a more descriptive title for the item, or its category. There's a lot to unpack here, and the best way to discover this API is with a demo. So let's take a look.

So I am here in a Fruta app that my team has been working on for a while. This is an app that allows the user to explore different kinds of smoothies, and in particular, I'm looking at a smoothie row view, which is a view designed to showcase a particular smoothie with information such as its title, its ingredients, its image and so on. This view is used in a couple places in our app, so I'd like to add it to the Library to make their use easier. I'm gonna do that now.

We just scroll to the bottom of this file and add a new type that conforms to the Library content provider protocol. This type can be named anything, but for the purposes of this demo, I'm going to call it LibraryContent. Since what I'm adding is a view, I'm gonna add a views property. And now I need to add a Library item.

To finish completing the Library item, I need to provide a completion that represents my view. Since what I'm adding is a smoothie row view, I'm just going to instantiate a SmoothieRowView here. I'm going to provide some default data here for my smoothie. I'm gonna pick out of the list of preexisting ones. And I like lemonberry, so I'm just gonna pick lemonberry here. And that's really all we need to create a Library item for this view. I'm ready to use it. I'm going to switch to the smoothie list.

As the name applies, this is just a view that takes an array of smoothies and shows them in a navigable list. It's empty right now, as you can see from the preview. I'm going to populate it with my smoothie row view. To do that, I'm going to click on the Library icon in the toolbar, which is a little plus, and I'm gonna search for "smoothie." As you can see, Xcode already discovered my Library item and populated the Library with it.

It even gave it a category, Fruta, which matches the name of my project. This makes local items from my project easier to find. It also gave it a rich title, Smoothie Row View, which is based on the completion I provided. So now I can just drag this Library item right into my code... and my preview will update automatically to reflect the change that I just made. Notice that all of the rows in this list right now are saying "lemonberry," and that's because Xcode inserted exactly what I specified with a lemonberry placeholder.

But there's a slight difference here. The argument for the smoothie row view here, lemonberry, that I provided, is tokenized. And that's because Library items are meant to be a starting point that can be customized based on the insertion context for the view that you are inserting. So I'm going to select the lemonberry token here and replace it with the data from the list-- in this case, just "smoothie." So the preview updates to show me the content of the list with the different smoothies provided from its preview. That's exactly what I want.

So now let's talk about customizations options for this Library item. And for that, I'm going to go back to the LibraryContent struct I'm working on. First, let's talk about the category. The project category is very useful for projects that are small and add a small number of items, such as this demo.

But for a larger project that adds a lot of items, this category will become pretty unwieldy pretty quickly. If you've ever used SwiftUI's Xcode Library, you noticed that SwiftUI deals with that by adding categories that correspond to functionality, such as controls, layout, effects and so on. We can do the same for our Library items as well.

I'm going to go back to my code and add an argument here to specify the category. I'm going to pick control because this view is most like control. It provides data and allows me to interact with it. Now if I bring up the Library with a Command-Shift-L shortcut and search for "smoothie," you'll see that the category of this Library item was updated. It's still prepended with Fruta, so it's still easy for me to find the local ones, but now it's clearly indicated that my view is a control. The icon for the Library item changed as well to indicate that it's control with the color blue.

So views don't have to correspond to Library items one-to-one. It is perfectly reasonable to create multiple Library items representing the same view in different configurations. In this particular case, smoothie row view has another argument that lets me specify whether I want to see local popularity of a particular smoothie. I'm going to add a Library item for that configuration as well.

I'm going to go back to my views property and add another Library item. And it also creates a SmoothieRowView, but now it also uses showNearbyPopularity flag and sets its default to "true." If I bring up my Library again... I can see that my Library item got added, just like I would expect, but there's a problem.

It's really hard for me to tell which Library item represents which instantiations. I can fix that by adding another argument to this Library item instantiation that specifies its title. In this case, I'm gonna say "Smoothie Row View With Popularity." And while I'm here, I can also fix its category to match the other smoothie row view as well.

Now, if I bring up the Library, I can see that my Library item got updated, and it's much easier for me to tell which one is which. So that's a quick intro in how to add a view to the Xcode Library. But what about modifiers? It turns out the idea is very similar, but has a little bit of a detail. Let me set the stage for a second.

As I audited my code, I found out that a lot of times when we use an image, we also use these three modifiers in a row: resizable, aspectRatio and frame. And the result of these modifiers is to resize an image to a particular size while keeping its aspect ratio.

We use it enough that I actually separated this functionality into its own modifier. I created an extension on Image, and I created a function called resizedToFill, which takes a width and height. And this function just takes the image and applies the three modifiers to it and returns the result. I'd like to add resizedToFill to the modifiers Library. To do that, I'm going to implement the second requirement of the Library content provider protocol, the modifiers function. And this is very similar to the views property, but it requires a base argument.

When figuring out what the completion for the Library item should be, Xcode needs a way to be able to tell which part of the completion is the modifier and which part is the thing it modifies. And the base is the way for us to communicate that. So since my modifier is declared on Image, I'm going to set this type to Image.

And then I'm going to add my Library item. Its completion is base with resizedToFill called on it. And I just need to populate this with sample data. And just like that, my modifier item is ready to be used. I'm going to scroll back to my image and delete the three modifiers I'm trying to replace.

I'm going to bring up the Library again, but this time I'm going to switch to the Modifiers tab and search for "resized." And here's my new modifier. I can use it right now by hitting "return," and what gets inserted is exactly what I would expect. Xcode stripped the base part and just inserted the modifier itself and tokenized the arguments. So I can customize them here with size... and size, like it was before. And just like that, I used my modifier from the Library.

You may have noticed that at no point during this demo we had to build or run our project to populate the Library. This is because Xcode can harvest the Library definitions by simply scanning your source for Library content providers and parsing their declarations. There are several advantages to this.

First, it means that if your project is not in a runnable state, it can still contribute content to the Library, which is really handy when you are in the middle of a UI rework, your project doesn't run, but you still want the content of the Library. It also means that there's no additional build configuration required to enable this feature, and since Library content provider code is never actually executed, the compiler will strip it when your project is built for distribution.

So this approach works really well for building a library of content for your work space or project, but since Xcode scans all source files in your work space for library content, including any dependencies you have, it actually works really well with Swift packages as well. In our project, we have a dependency on the nutrition facts we've packaged, which provides facilities for visualizing nutritional information. I'm looking for a view that will allow me to display caloric information of a smoothie in my row view. I can use the Library to explore the content of the nutrition facts package for a view I want.

I'm going to bring up the Library again. But this time I'm going to switch back to the Views tab and scroll down until I find the nutrition facts category. This is a category that Xcode created for the content of the nutrition facts Swift package. I can see that there's a Calorie Count View here, which sounds like exactly what I want.

I'm going to hit "return" to insert it, and my preview will update to reflect the change that I just made. This basically looks exactly like what I want, so all I need to do now is to populate this view with data from my model. Fortunately, I already have nutrition facts, so I can just type smoothie.nutritionFact here and have my view updated.

And just like that, I was able to discover and use content from a Swift package without ever opening its source code. To summarize, in this session, we've learned how to extend the Xcode Library by creating a type that conforms to the Library content provider protocol, implementing one or both of its requirements and returning instances of Library items that correspond to the individual items. Thank you, and have a great WWDC.