스트리밍은 대부분의 브라우저와
Developer 앱에서 사용할 수 있습니다.
-
App essentials in SwiftUI
Thanks to the new App protocol, SwiftUI now supports building entire apps! See how Apps, Scenes, and Views fit together. Learn how easy it is to implement the features people expect from a best-in-class product while saving time and reducing complexity. Easily add expected functionality to your interface using the new commands modifier, and explore the ins and outs of the new WindowGroup API. To get the most out of this session, you should have some experience with SwiftUI. Watch “Introduction to SwiftUI” for a primer. Want more SwiftUI? Take your pick: “What's new in SwiftUI”, “Data essentials in Swift UI ”, "Stacks, grids, and outlines in SwiftUI", and “Build document-based apps in SwiftUI”.
리소스
관련 비디오
WWDC20
-
다운로드
Hello and welcome to WWDC.
Welcome to "App Essentials in SwiftUI." My name is Matt Ricketson, and I work on SwiftUI. Later on, I'll be joined by my colleague Jeff. Last year we introduced SwiftUI, a powerful new way to build great user interfaces on all of Apple's platforms. We build user interfaces using views, and SwiftUI provides a suite of APIs for modifying views and composing them together.
This year, we're extending the framework with new APIs for declaring scenes and apps, a big expansion to what you can build using SwiftUI. The bottom line is that you can now build an entire app with just SwiftUI. In this session, we're going to introduce these new APIs and explain how views, scenes and apps work together.
Next, we'll go in-depth on SwiftUI's scene architecture and show how you can customize the scenes in your app. And finally, we'll give a quick overview of the different APIs available for customizing your apps and where you can go to learn more. For now, let's start by talking about views, which you will already be familiar with if you've used SwiftUI before. Views are important because each view defines a piece of your UI. When you look at an app, everything you see is a view. Individual images and pieces of text are views. The containers holding them are also views. In fact, every pixel you see on screen is in some way defined by a view.
But not all views belong to the same app, since apps do not have complete control over the entire screen. Instead, the platform controls how apps are presented, showing pieces of apps in distinct regions.
In SwiftUI, we refer to these distinct regions as "scenes." A window is the most common way that a scene's content is shown on screen. Some platforms, like iPadOS, can show multiple windows side-by-side. Other platforms, such as iOS, watchOS and tvOS, prefer to show only a single full-screen window for each app. macOS is another great example of how scene content can appear in different ways. In this case, we see a collection of related windows, where each window is a manifestation of a different scene's content.
macOS also allows you to gather up related windows into a single, tabbed window. In this case, our scenes are represented as individual tabs instead.
This shared window is also represented by its own scene, serving as a container for the child scenes associated with each tab. These collections of scenes make up the entire content of an app. Together, apps, scenes and views form a unified hierarchy of ownership.
As we mentioned earlier, views serve as the basic building blocks, rendering everything you see on screen, and can be composed together to form more complex user interfaces.
Views form the content of scenes, allowing them to be independently displayed by the platform.
Like views, those scenes can also be composed together to form more complex scenes, like in our tabbed-window example that we saw earlier.
And finally, all these scenes form the content of your app.
Now that we understand how apps, scenes and views work together, let's look at how this plays out in your SwiftUI code.
Here we see a basic app I wrote in SwiftUI to help me keep track of the books I'm reading in my book club. As you can see, apps in SwiftUI have a concise declaration, meaning basic apps like this can fit into only a handful of lines of code. Without that extra boilerplate, you can focus immediately on the code that's unique to your app.
In this case, we've defined the actual interface of our app using a view called ReadingListViewer.
ReadingListViewer is a custom view I built separately that allows me to browse my reading list.
Our ReadingListViewer is contained within a scene called WindowGroup.
The WindowGroup scene manages the window that our ReadingListViewer will render into.
It can also create additional windows, or new tabs within the same window, on platforms that support those features. Jeff will explain how WindowGroup works in much more detail later in the talk.
Our WindowGroup scene is contained within an app, represented by a custom struct conforming to the App protocol.
You'll notice that the structure of the code matches the hierarchy of ownership we saw earlier-- the app contains the scenes, and the scenes contain views.
You might also notice that our app declaration looks similar to a custom-view declaration. For example, both views and apps are able to declare data dependencies. Here, our ReadingListViewer observes a ReadingListStore object.
Our app also depends on a ReadingListStore object, but declares itself as the owner of that object using the StateObject property wrapper, which is a new feature in SwiftUI this year.
Both views and apps also declare a "body" property, defining their user-interface content.
We talked earlier about how views are composed of other views, which is why a view's body returns a view. An app, however, is built using scenes, and so its body property returns a scene instead.
Finally, you probably noticed the @main attribute decorating our app. This is a new attribute in Swift 5.3 that allows a type to serve as the entry point of our program's execution.
Normally, a Swift program would require a main.swift file to execute. With the @main attribute, we can delegate that responsibility to our app struct instead, which automatically performs all the necessary setup on launch to get our app to appear on screen.
Now that we've reviewed the code, let's take a step back.
What we see here is a complete declaration for a basic app in SwiftUI, fitting into only a handful of lines of code. But don't let this deceive you. There is a ton of automatic, intelligent behavior packed into this simple declaration.
To really understand how this app works, we need to talk more about that WindowGroup scene, which manages our user interface. And for that, I'm going to hand things over to Jeff. Thanks, Matt. Hi, everyone.
First, I'd like to show you some of the concepts that Matt outlined in practice, with a short demo, and then I'll discuss some of the finer points of WindowGroup.
I'm a pretty avid reader, so I've been working on a little app to track my progress in all of the books I'm currently reading. As you can see, my app launches with an initial window on iPadOS using the view I've specified as content. Looks like I'm in the middle of quite a few books, so let's open some new windows so I can check my progress. If I open App Exposé, I can easily create a new one here and navigate to a different book. WindowGroup provides this functionality for my app automatically on iPadOS. You'll notice that each of my windows is reflecting a different state in the interface. The selected book is different in each one, and changing it in one does not affect the others. This is a key aspect to scenes in SwiftUI. An app can provide a shared model for each scene to use, but the state of the views in those scenes will be independent. I'd also like to point out something in the App Switcher. Each of my windows is showing the name of my app, as well as the name of the selected book. This is done via a new view modifier that we've introduced this year, "navigation title," which on iOS can be used to populate the title in the navigation bar and the App Switcher. This is one example of a view modifier which can affect the state of its parent scene.
On the Mac, it's very common for apps to support multiple windows. By using WindowGroup in your app, SwiftUI will provide a menu item in the file menu, which supports creating new scene instances.
This item can also be invoked via the standard Command-N keyboard shortcut.
You'll notice how navigation title applies here on macOS. The book title is being displayed in the window's title-bar area.
It also will be used in the windows menu.
Providing a good title here is important for the users since, in addition to giving them more context for what they're interacting with, it also helps them select their desired window from the list of open ones. Besides supporting multiple windows, macOS also supports grouping its windows together. Via the window menu, I can merge my open windows into a single, tabbed interface...
with each tab being represented by a separate scene.
Again, I didn't have to write any code for this functionality. SwiftUI provides this automatically.
Now that you've seen some of the practical implications of using the WindowGroup scene in your app, let's go over a few more details. To recap what I showed you just now, WindowGroup is a scene which lets you express the primary interface of your app.
The view that you provide to it will be used as the definition of that interface.
And this works on all of our platforms in the expected way. For example, on iOS and watchOS, your view will be presented in a window which occupies the entire screen of the device. While on macOS, this window will be sized according to your view's definition. The life cycle of scenes are managed by the platform they are running on.
Using macOS as an example, when the platform needs to create a window for your app, WindowGroup will instantiate a new child scene which by default will display its content within a window.
On platforms which support multiple windows, such as macOS and iPadOS, WindowGroup can instantiate multiple children.
This can happen in response to user actions such as clicking a menu item or invoking a multitasking gesture.
While each scene shares the definition of its user interface, the views which make up this definition all have their own independent state.
And this means that changing the view state in one window does not affect the state of the others.
This functionality lets you provide a template to be used for your interface, while letting your users customize this interface through the state you provide.
Since the platform is in charge of the life cycle of scenes, we've introduced a new property wrapper this year to help you manage restoration of your view state. The SceneStorage property wrapper can be used to persist your view state.
It takes a unique key which identifies the state to be stored. This state will then be automatically saved and restored at the appropriate times by SwiftUI.
Now that I've shown you more about how scenes and specifically WindowGroup work, I'd like to hand things back to Matt who will give you some more info about how you can customize your apps further. Thanks, Jeff. Before we finish up, I'd like to show you just a few other new app-related features available this year.
The BookClub app we saw earlier was a data-driven app backed by a shared data model. But there are other kinds of apps as well, such as document-based apps, like we see here with the ShapeEdit app.
New this year is the DocumentGroup scene type, which automatically manages opening, editing and saving document-based scenes.
To learn more, you should check out this year's "Build Document-Based Apps in SwiftUI" talk.
Now, going back to our BookClub example, a common feature of macOS apps is a preferences window.
This year we're exposing the new "settings" scene type, available on macOS, which automatically sets up the standard preferences window. The settings scene will automatically set up the preferences command in the app menu and also give the window the correct style treatment.
Speaking of menu commands, SwiftUI is also providing an API for you to add custom commands to your scenes using the new "commands" modifier.
BookCommands is a custom type that I've defined. Let's take a quick look.
The commands API is powerful and flexible, using the same declarative, state-driven programming model we use in views, scenes and apps. You can encapsulate commands in custom types, target actions based on user focus, similar to the responder chain in AppKit or UIKit, and use normal views to build the commands themselves. Check out our reference documentation for more information about working with commands. That was just a peek at the new app-related APIs available in SwiftUI this year. I'd recommend also checking out these other SwiftUI talks, which will help you with building the content of your apps.
"Data Essentials in SwiftUI" will build your knowledge of how to properly pass data between your apps, scenes and views. And "What's New in Swift" will show you the latest improvements in the language that can improve all of your SwiftUI code.
We really look forward to seeing all the great SwiftUI apps you build, and we hope you share your creations with us on the forums. We love all your feedback and can't wait to see what you build next. Thanks and have a great WWDC.
-
-
3:57 - Book club app
@main struct BookClubApp: App { @StateObject private var store = ReadingListStore() var body: some Scene { WindowGroup { ReadingListViewer(store: store) } } } struct ReadingListViewer: View { @ObservedObject var store: ReadingListStore var body: some View { NavigationView { List(store.books) { book in Text(book.title) } .navigationTitle("Currently Reading") } } } class ReadingListStore: ObservableObject { init() {} var books = [ Book(title: "Book #1", author: "Author #1"), Book(title: "Book #2", author: "Author #2"), Book(title: "Book #3", author: "Author #3") ] } struct Book: Identifiable { let id = UUID() let title: String let author: String }
-
10:21 - Window groups
@main struct BookClubApp: App { @StateObject private var store = ReadingListStore() var body: some Scene { WindowGroup { ReadingListViewer(store: store) } } } struct ReadingListViewer: View { @ObservedObject var store: ReadingListStore var body: some View { NavigationView { List(store.books) { book in Text(book.title) } .navigationTitle("Currently Reading") } } } class ReadingListStore: ObservableObject { init() {} var books = [ Book(title: "Book #1", author: "Author #1"), Book(title: "Book #2", author: "Author #2"), Book(title: "Book #3", author: "Author #3") ] } struct Book: Identifiable { let id = UUID() let title: String let author: String }
-
12:07 - Scene storage
@main struct BookClubApp: App { @StateObject private var store = ReadingListStore() var body: some Scene { WindowGroup { ReadingListViewer(store: store) } } } struct ReadingListViewer: View { @ObservedObject var store: ReadingListStore @SceneStorage("selectedItem") private var selectedItem: String? var selectedID: Binding<UUID?> { Binding<UUID?>( get: { selectedItem.flatMap { UUID(uuidString: $0) } }, set: { selectedItem = $0?.uuidString } ) } var body: some View { NavigationView { List(store.books) { book in NavigationLink( destination: Text(book.title), tag: book.id, selection: selectedID ) { Text(book.title) } } .navigationTitle("Currently Reading") } } } class ReadingListStore: ObservableObject { init() {} var books = [ Book(title: "Book #1", author: "Author #1"), Book(title: "Book #2", author: "Author #2"), Book(title: "Book #3", author: "Author #3") ] } struct Book: Identifiable { let id = UUID() let title: String let author: String }
-
12:59 - Document groups
import SwiftUI import UniformTypeIdentifiers @main struct ShapeEditApp: App { var body: some Scene { DocumentGroup(newDocument: ShapeDocument()) { file in DocumentView(document: file.$document) } } } struct DocumentView: View { @Binding var document: ShapeDocument var body: some View { Text(document.title) .frame(width: 300, height: 200) } } struct ShapeDocument: Codable { var title: String = "Untitled" } extension UTType { static let shapeEditDocument = UTType(exportedAs: "com.example.ShapeEdit.shapes") } extension ShapeDocument: FileDocument { static var readableContentTypes: [UTType] { [.shapeEditDocument] } init(fileWrapper: FileWrapper, contentType: UTType) throws { let data = fileWrapper.regularFileContents! self = try JSONDecoder().decode(Self.self, from: data) } func write(to fileWrapper: inout FileWrapper, contentType: UTType) throws { let data = try JSONEncoder().encode(self) fileWrapper = FileWrapper(regularFileWithContents: data) } }
-
13:27 - Settings scene
@main struct BookClubApp: App { @StateObject private var store = ReadingListStore() @SceneBuilder var body: some Scene { WindowGroup { ReadingListViewer(store: store) } #if os(macOS) Settings { BookClubSettingsView() } #endif } } struct BookClubSettingsView: View { var body: some View { Text("Add your settings UI here.") .padding() } } struct ReadingListViewer: View { @ObservedObject var store: ReadingListStore var body: some View { NavigationView { List(store.books) { book in Text(book.title) } .navigationTitle("Currently Reading") } } } class ReadingListStore: ObservableObject { init() {} var books = [ Book2(title: "Book #1", author: "Author #1"), Book2(title: "Book #2", author: "Author #2"), Book2(title: "Book #3", author: "Author #3") ] } struct Book: Identifiable { let id = UUID() let title: String let author: String }
-
14:07 - BookCommands
struct BookCommands: Commands { @FocusedBinding(\.selectedBook) private var selectedBook: Book? var body: some Commands { CommandMenu("Book") { Section { Button("Update Progress...", action: updateProgress) .keyboardShortcut("u") Button("Mark Completed", action: markCompleted) .keyboardShortcut("C") } .disabled(selectedBook == nil) } } private func updateProgress() { selectedBook?.updateProgress() } private func markCompleted() { selectedBook?.markCompleted() } }
-
-
찾고 계신 콘텐츠가 있나요? 위에 주제를 입력하고 원하는 내용을 바로 검색해 보세요.
쿼리를 제출하는 중에 오류가 발생했습니다. 인터넷 연결을 확인하고 다시 시도해 주세요.