WWDC Quick Look 💓 By SwiftGGTeam
What's new in SwiftUI

What's new in SwiftUI

Watch original video

Highlight

SwiftUI 2020 adds App, Scene, DocumentGroup, Widget, List(children:), LazyVGrid, ToolbarItem, matchedGeometryEffect, Link, openURL, UTType and SignInWithAppleButton By connecting to the same set of declarative models, application portals, windows, documents, widgets, complex lists, toolbars, animations, and system services can all be implemented directly using SwiftUI.

Core Content

The focus of SwiftUI in its first year was View. Developers can write declarative interfaces, but the complete app also handles portals, windows, menus, documents, list capabilities, toolbars, accessibility, and system services. The task of this session is to take these daily tasks back into SwiftUI’s declarative model.

(01:14) At the beginning, a complete App is given: @main marks the entrance, the body of App returns Scene, and WindowGroup hosts the View. The system will map this statement to iOS’s full-screen window, iPadOS’s multi-window, macOS’s multi-window and menu commands according to platform rules.

(09:18) The second problem is content scale. Ordinary lists and handwritten navigation will soon encounter hierarchical data, grid content, and scrolling performance. SwiftUI 2020 adds outline, lazy grid and lazy stack, so that galleries, sidebars and recursive lists can continue to be expressed in data-driven writing.

(12:19) The last part returns to the platform experience. There are SwiftUI entries for toolbars, labels, help tips, keyboard shortcuts, progress controls, Gauge, matched geometry, dynamic font scaling, Links, openURL, Uniform Type Identifiers, and Sign in with Apple. Developers can access these system capabilities without leaving SwiftUI.

Detailed Content

1. App and Scene become the entrance to SwiftUI

(01:26) The first clip officially displayed is a complete App. The App protocol and WindowGroup write the application entry into a declarative structure similar to View.

@main
struct HelloWorld: App {
    var body: some Scene {
        WindowGroup {
            Text("Hello, world!").padding()
        }
    }
}

Key points:

  • @main marks the application entry, replacing the manual life cycle entry in traditional templates.
  • HelloWorld complies with App, and body returns Scene.
  • WindowGroup is the Scene here; the transcript indicates that it will provide corresponding window behavior on different platforms.
  • Text("Hello, world!").padding() is the SwiftUI View in the window.

(04:46) It is also possible to combine Settings Scenes on macOS. The system sets up the standard preference menu and gives the window the correct style.

@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()
    }
}

Key points:

  • @StateObject stores the reading list in the App layer and is observed by the View in the window.
  • @SceneBuilder allows an App to declare multiple Scenes.
  • WindowGroup is responsible for the main window.
  • Settings is only compiled on macOS and is responsible for the preferences window.

2. DocumentGroup takes over document opening, editing and saving

(05:10) Document-based apps use DocumentGroup. transcript explains that it automatically manages document opening, editing, and saving scenarios and is available on iOS, iPadOS, and macOS.

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)
    }
}

Key points:

  • DocumentGroup(newDocument:) defines new document defaults.
  • file.$document passes the document content as binding to DocumentView.
  • UTType(exportedAs:) declares the application’s own file type.
  • init and write of FileDocument are responsible for reading and writing document data.

(05:49) Document App can also add menu commands. commands will place the custom menu in the appropriate location for the platform, and keyboardShortcut will display the shortcut keys.

DocumentGroup(newDocument: ShapeDocument()) { file in
    DocumentView(document: file.$document)
}
.commands {
    CommandMenu("Shapes") {
        Button("Add Shape...", action: addShape)
            .keyboardShortcut("N")
        Button("Add Text", action: addText)
            .keyboardShortcut("T")
    }
}

Key points:

  • commands are hung on the Scene, not on a single View.
  • CommandMenu("Shapes") creates a custom menu.
  • Two Button are bound to actions respectively.
  • keyboardShortcut declares a keyboard shortcut for a menu item.

3. Lists, grids and lazy stacks handle large amounts of content

(09:22) List adds a new children key path, which can directly display recursive hierarchical data. This capability reduces frequent push-and-pop navigation in content-based apps.

struct OutlineContentView: View {
    var graphics: [Graphic]

    var body: some View {
        List(graphics, children: \.children) { graphic in
            GraphicRow(graphic)
        }
        .listStyle(SidebarListStyle())
    }
}

struct Graphic: Identifiable {
    var id: String
    var name: String
    var icon: Image
    var children: [Graphic]?
}

Key points:

  • List(graphics, children: \.children) reads the children of each node.
  • children of Graphic is an optional array, corresponding to expandable or leaf nodes.
  • SidebarListStyle() uses sidebar styles.
  • Each node is still rendered with a normal SwiftUI View.

(10:09) The grid uses lazy-loading layout. GridItem(.adaptive(minimum: 176)) will adjust the number of columns to the available width.

struct ContentView: View {
    var items: [Item]

    var body: some View {
        ScrollView {
            LazyVGrid(columns: [GridItem(.adaptive(minimum: 176))]) {
                ForEach(items) { item in
                    ItemView(item: item)
                }
            }
            .padding()
        }
    }
}

Key points:

  • ScrollView provides a scrolling container.
  • LazyVGrid only loads grid content on demand.
  • GridItem(.adaptive(minimum: 176)) makes the number of columns follow the spatial changes.
  • ForEach(items) maps data arrays into cells.

(10:58) Custom long lists can be used with LazyVStack. The official gallery example also shows that the view builder supports switch, which can switch multiple layouts in the same stack.

struct WildlifeList: View {
    var rows: [ImageRow]

    var body: some View {
        ScrollView {
            LazyVStack(spacing: 2) {
                ForEach(rows) { row in
                    switch row.content {
                    case let .singleImage(image):
                        SingleImageLayout(image: image)
                    case let .imageGroup(images):
                        ImageGroupLayout(images: images)
                    case let .imageRow(images):
                        ImageRowLayout(images: images)
                    }
                }
            }
        }
    }
}

Key points:

  • LazyVStack suitable for custom scroll layout.
  • switch row.content selects different Views by data type.
  • SingleImageLayout, ImageGroupLayout and ImageRowLayout together form an irregular gallery.
  • The transcript clearly states that these contents are combined to form a seamless gallery.

4. Toolbar, Label, help prompts and new controls unify platform interaction

(12:24) SwiftUI adds a new toolbar modifier. Toolbar items can be ordinary Views, which are placed in the platform’s preferred location by default.

struct ContentView: View {
    var body: some View {
        List {
            Text("Book List")
        }
        .toolbar {
            Button(action: recordProgress) {
                Label("Record Progress", systemImage: "book.circle")
            }
        }
    }

    private func recordProgress() {}
}

Key points:

  • toolbar is hung on the View, and the toolbar content is placed inside.
  • Button is a normal SwiftUI control.
  • Label provides both title and icon.
  • The transcript description toolbar will be displayed in the default location of the platform.

(12:40) Use ToolbarItem when you need to control the position. It can declare semantic placements such as primary operations, confirm operations, cancel operations, and principal positions.

struct ContentView: View {
    var body: some View {
        List {
            Text("Book List")
        }
        .toolbar {
            ToolbarItem(placement: .primaryAction) {
                Button(action: recordProgress) {
                    Label("Record Progress", systemImage: "book.circle")
                }
            }
        }
    }

    private func recordProgress() {}
}

Key points:

  • ToolbarItem wraps a toolbar control.
  • .primaryAction describes the control role.
  • SwiftUI determines the specific location based on the platform.
  • Label displays the icon in the toolbar by default, and the title is used for accessibility.

(15:28) The help modifier becomes a tooltip on macOS and also provides an accessibility hint.

struct ContentView: View {
    var body: some View {
        Button(action: recordProgress) {
            Label("Progress", systemImage: "book.circle")
        }
        .help("Record new progress entry")
    }

    private func recordProgress() {}
}

Key points:

  • help describes control effects.
  • macOS will render this as Tool Tips.
  • transcript Description It also provides VoiceOver hint on all platforms.

(17:08) New controls cover progress and capacity expressions. ProgressView represents definite or uncertain progress, Gauge represents the position of a certain value relative to capacity.

struct ContentView: View {
    var percentComplete: Double

    var body: some View {
        ProgressView("Downloading Photo", value: percentComplete)
    }
}

Key points:

  • percentComplete is the determined progress value.
  • ProgressView receives both title and progress.
  • transcript explains that it has two styles: linear and circular.

5. Animation, responsive visuals and system services have direct SwiftUI API

(19:17) matchedGeometryEffect connects the same element in two areas. In the example, when the album moves from the grid to the selected row, SwiftUI interpolates the frame to form a continuous transition.

struct ContentView: View {
    @Namespace private var namespace
    @State private var selectedAlbumIDs: Set<Album.ID> = []

    var body: some View {
        VStack(spacing: 0) {
            ScrollView {
                albumGrid.padding(.horizontal)
            }

            Divider().zIndex(-1)

            selectedAlbumRow
                .frame(height: AlbumCell.albumSize)
                .padding(.top, 8)
        }
        .buttonStyle(PlainButtonStyle())
    }

    private var albumGrid: some View {
        LazyVGrid(columns: [GridItem(.adaptive(minimum: AlbumCell.albumSize))], spacing: 8) {
           ForEach(unselectedAlbums) { album in
              Button(action: { select(album) }) {
                 AlbumCell(album)
              }
              .matchedGeometryEffect(id: album.id, in: namespace)
           }
        }
    }

    private var selectedAlbumRow: some View {
        HStack {
            ForEach(selectedAlbums) { album in
                AlbumCell(album)
                .matchedGeometryEffect(id: album.id, in: namespace)
            }
        }
    }
}

Key points:

  • @Namespace provides a namespace for geometric matching.
  • Two matchedGeometryEffect uses the same album.id.
  • Grid and selected row share the same set of identifiers.
  • withAnimation(.spring(response: 0.5)) appears in the official complete fragment and is used to trigger the selection animation.

(20:34) Responsive typography has also been enhanced. Custom fonts will scale with Dynamic Type, and non-text metrics can use @ScaledMetric.

struct ContentView: View {
    var album: Album
    @ScaledMetric private var padding: CGFloat = 10

    var body: some View {
        VStack {
            Text(album.name)
                .font(.custom("AvenirNext-Bold", size: 30))

            Text("\(Image(systemName: "music.mic")) \(album.artist)")
                .font(.custom("AvenirNext-Bold", size: 17))

        }
        .padding(padding)
        .background(RoundedRectangle(cornerRadius: 16, style: .continuous).fill(Color.purple))
    }
}

Key points:

  • @ScaledMetric makes padding follow Dynamic Type.
  • Custom fonts continue to use .font(.custom(...)).
  • Image(systemName:) can be embedded in Text and respond to font size changes as part of the text.

(23:15) The system integration part will first talk about Link and openURL. Link will open a URL or universal link, openURL is used to trigger the opening from code in the View environment.

let customPublisher = NotificationCenter.default.publisher(for: .init("CustomURLRequestNotification"))
let apple = URL(string: "https://developer.apple.com/tutorials/swiftui/")!

struct ContentView: View {
    @Environment(\.openURL) private var openURL

    var body: some View {
        Text("OpenURL Environment Action")
            .onReceive(customPublisher) { output in
                if output.userInfo!["shouldOpenURL"] as! Bool {
                    openURL(apple)
                }
            }
    }
}

Key points:

  • @Environment(\.openURL) reads the open URL action in the environment.
  • onReceive listens for publisher.
  • Call openURL(apple) after the condition is met.
  • transcript Description SwiftUI opens a URL relative to the window that contains it.

(25:16) Sign in with Apple also has SwiftUI controls. Just import both AuthenticationServices and SwiftUI to use it.

import AuthenticationServices
import SwiftUI

struct ContentView: View {
    var body: some View {
        SignInWithAppleButton(
            .signUp,
            onRequest: handleRequest,
            onCompletion: handleCompletion
        )
        .signInWithAppleButtonStyle(.black)
    }

    private func handleRequest(request: ASAuthorizationAppleIDRequest) {}
    private func handleCompletion(result: Result<ASAuthorization, Error>) {}
}

Key points:

  • SignInWithAppleButton from the combination of AuthenticationServices and SwiftUI.
  • .signUp specifies the button purpose.
  • onRequest and onCompletion handle authorization requests and results.
  • .signInWithAppleButtonStyle(.black) sets the official style.

Core Takeaways

  • Make a multi-window reading or data management App: Use App, WindowGroup and @StateObject to place shared models, so that the same set of SwiftUI entrances can cover iPhone, iPad, Mac, Apple Watch and Apple TV. At the beginning, build the entry according to the BookClub structure, and then add Settings Scene to macOS.
  • Make a simple document editor: use DocumentGroup to manage opening, editing and saving, use FileDocument to read and write models, and use UTType(exportedAs:) to declare file types. The first version only requires a title field and a DocumentView, and then uses commands to add menu actions.
  • Make a gallery that can carry a large amount of content: Use LazyVGrid to make adaptive grids, use LazyVStack to handle irregular long lists, and use matchedGeometryEffect to add continuous animation to item movement. To start, you can reuse the official album selector structure and replace the data with image or video thumbnails.
  • Complete platform interaction details for production apps: Use toolbar, ToolbarItem, Label, help and keyboardShortcut to place common operations in appropriate locations on the platform. Start with a details page and map save, cancel, share and main operations to the corresponding placements.
  • Integrate system capabilities into SwiftUI pages: use Link to handle tutorials, news or universal links in the app, use openURL to respond to asynchronous events, and use SignInWithAppleButton to put in the login process. The first step is to make the external links and login buttons into independent SwiftUI Views, and then embed them into the existing page.

Comments

GitHub Issues · utterances