Dunfey · Hotel WWDC as data, est. 1983
Front desk everything
Years
Topics

2020 SwiftSystem ServicesSwiftUI & UI Frameworks

WWDC20 · 36 min · Swift / System Services / SwiftUI & UI Frameworks

Data Essentials in SwiftUI

Data is a complex part of any app, but SwiftUI makes it easy to ensure a smooth, data-driven experience from prototyping to production. Discover @State and @Binding, two powerful tools that can preserve and seamlessly update your Source of Truth. We’ll also show you how ObservableObject lets you connect your views to your data model. Learn about some tricky challenges and cool new ways to solve them — directly from the experts! To get the most out of this session, you should be familiar with SwiftUI. Watch “App essentials in SwiftUI” and "Introduction to SwiftUI"

Watch at developer.apple.com ↗

Transcript all transcripts

Code shown on screen · 14 snippets

BookCard swift · at 2:09 ↗
struct BookCard : View {
    let book: Book
    let progress: Double

    var body: some View {
        HStack {
            Cover(book.coverName)
            VStack(alignment: .leading) {
                TitleText(book.title)
                AuthorText(book.author)
            }
            Spacer()
            RingProgressView(value: progress)              
        }
    }
}
EditorConfig swift · at 3:35 ↗
struct EditorConfig {
    var isEditorPresented = false
    var note = ""
    var progress: Double = 0
    mutating func present(initialProgress: Double) {
        progress = initialProgress
        note = ""
        isEditorPresented = true
    }
}
struct BookView: View {
    @State private var editorConfig = EditorConfig()
    func presentEditor() { editorConfig.present() }
    var body: some View {
        
        Button(action: presentEditor) {  }
        
    }
}
ProgressEditor swift · at 5:59 ↗
struct EditorConfig {
    var isEditorPresented = false
    var note = ""
    var progress: Double = 0
}
struct BookView: View {
    @State private var editorConfig = EditorConfig()
    var body: some View {
        
        ProgressEditor(editorConfig: $editorConfig)
        
    }
}

struct ProgressEditor: View {
    @Binding var editorConfig: EditorConfig
    
        TextEditor($editorConfig.note)
    
}
CurrentlyReading swift · at 13:15 ↗
/// The current reading progress for a specific book.
class CurrentlyReading: ObservableObject {
    let book: Book
    @Published var progress: ReadingProgress

    // …
}

struct ReadingProgress {
    struct Entry : Identifiable {
        let id: UUID
        let progress: Double
        let time: Date
        let note: String?
    }

    var entries: [Entry]
}
BookView swift · at 15:36 ↗
struct BookView: View {
    @ObservedObject var currentlyReading: CurrentlyReading

    var body: some View {
        VStack {
            BookCard(
                currentlyReading: currentlyReading)

            //…

            ProgressDetailsList(
                progress: currentlyReading.progress)
        }
    }
}
CurrentlyReading with isFinished swift · at 17:50 ↗
class CurrentlyReading: ObservableObject {
    let book: Book
    @Published var progress = ReadingProgress()
    @Published var isFinished = false

    var currentProgress: Double {
        isFinished ? 1.0 : progress.progress
    }
}
BookView with Toggle swift · at 18:21 ↗
struct BookView: View {
    @ObservedObject var currentlyReading: CurrentlyReading

    var body: some View {
        VStack {
            BookCard(
                currentlyReading: currentlyReading)

            HStack {
                Button(action: presentEditor) { /* … */ }
                    .disabled(currentlyReading.isFinished)

                Toggle(
                    isOn: $currentlyReading.isFinished
                ) {
                    Label(
                        "I'm Done",
                        systemImage: "checkmark.circle.fill")
                }
            }
            //…
        }
    }
}
CoverImageLoader swift · at 19:58 ↗
class CoverImageLoader: ObservableObject {
    @Published public private(set) var image: Image? = nil

    func load(_ name: String) {
        // …
    }

    func cancel() {
        // …
    }

    deinit {
        cancel()
    }
}
BookCoverView swift · at 20:20 ↗
struct BookCoverView: View {
    @StateObject var loader = CoverImageLoader()

    var coverName: String
    var size: CGFloat

    var body: some View {
        CoverImage(loader.image, size: size)
            .onAppear { loader.load(coverName) }
    }
}
ReadingListViewer (Bad) swift · at 25:36 ↗
struct ReadingListViewer: View {
    var body: some View {
        NavigationView {
            ReadingList()
            Placeholder()
        }
    }
}

struct ReadingList: View {
    @ObservedObject var store = ReadingListStore()

    var body: some View {
        // ...
    }
}
ReadingListViewer (Good) swift · at 26:39 ↗
struct ReadingListViewer: View {
    var body: some View {
        NavigationView {
            ReadingList()
            Placeholder()
        }
    }
}

struct ReadingList: View {
    @StateObject var store = ReadingListStore()

    var body: some View {
        // ...
    }
}
App-wide Source of Truth swift · at 30:52 ↗
@main
struct BookClubApp: App {
    @StateObject private var store = ReadingListStore()

    var body: some Scene {
        WindowGroup {
            ReadingListViewer(store: store)
        }
    }
}
SceneStorage swift · at 32:43 ↗
struct ReadingListViewer: View {
    @SceneStorage("selection") var selection: String?

    var body: some View {
        NavigationView {
            ReadingList(selection: $selection)
            BookDetailPlaceholder()
        }
    }
}
AppStorage swift · at 33:49 ↗
struct BookClubSettings: View {
    @AppStorage("updateArtwork") private var updateArtwork = true
    @AppStorage("syncProgress") private var syncProgress = true

    var body: some View {
        Form {
            Toggle(isOn: $updateArtwork) {
                //...
            }

            Toggle(isOn: $syncProgress) {
                //...
            }
        }
    }
}

Resources