Getting Started with
Combine and SwiftUI in iOS
Scott Gardner
About Me
iOS app architect & developer since 2010
iOS Architect at Wayfair
Author of video courses & books on
Swift & iOS app development
Apple Certified Trainer for Swift and iOS
What’s In Store
Combine basics
UIKit vs. SwiftUI + Combine
Anatomy of a SwiftUI View
Create Crosstalk app
What’s In Store
Combine basics
UIKit vs. SwiftUI + Combine
Anatomy of a SwiftUI View
Create Crosstalk app
Wrap up
Want more?
Download resources:
bit.ly/gscsresources
What is Combine?
Reactive framework for asynchronous operations
Declarative vs. imperative
Unified approach
Data
Notification
Center
Grand Central
Dispatch
Delegates
ClosuresOperations
Timers
Why Combine?
Data
Combine
SwiftUI
Why Combine?
Combine
SwiftUI
Foundation Core Data
@FetchRequest
NSManagedObject
URLSession
NotificationCenter
Why Combine?
ObservableObject
@Published
@ObservedObject
(@State / @Binding)
Combine Basics
Publisher Subscriber
subscribes
gives subscription
requests values
sends values
sends completion
Combine Basics
Publisher Subscriber
subscribes
gives subscription
requests values
sends values
sends completion
Combine Basics
Publisher Subscriber
subscribes
gives subscription
requests values
sends values
sends completion
cancels
Combine Operators
1 2 3
map { $0 * 2 }
2 4 6
Publisher
Subscriber
UIKit + Traditional
Fetch data
Decode
Update state
Update UI
UIKit + Traditional
Fetch dataDecodeUpdate stateUpdate UI
SwiftUI + Combine
Fetch data
Decode
Update state
SwiftUI + Combine
Fetch dataDecodeUpdate state
SwiftUI + CombineUIKit + Traditional
Update UI in UIKit
OUT OF STOCK
Product: 8’ Gnome
Quantity: 0
Product: 8’ Gnome
Quantity: 0
If you loved gottfried,
you’ll fall even harder
for his truly giant big
brother who simply
cannot be missed.
Gottfried's big bro
thunders onto the scene
from Schleswig…
U
Update UI in UIKit
OUT OF STOCK
Product: 8’ Gnome
Quantity: 0
Product: 8’ Gnome
F
D
S
Quantity: 1
If you loved gottfried,
you’ll fall even harder
for his truly giant big
brother who simply
cannot be missed.
Gottfried's big bro
thunders onto the scene
from Schleswig…
U
Update UI in UIKit
OUT OF STOCK
Product: 8’ Gnome
Product: 8’ Gnome
F
D
S
Quantity: 1
If you loved gottfried,
you’ll fall even harder
for his truly giant big
brother who simply
cannot be missed.
Gottfried's big bro
thunders onto the scene
from Schleswig…
Quantity: 1
Update UI in Combine + SwiftUI
OUT OF STOCK
Product: 8’ Gnome
Quantity: 0
Product: 8’ Gnome
Quantity: 0
If you loved gottfried,
you’ll fall even harder
for his truly giant big
brother who simply
cannot be missed.
Gottfried's big bro
thunders onto the scene
from Schleswig…
Update UI in Combine + SwiftUI
Product: 8’ Gnome
Product: 8’ Gnome
F
D
S
Quantity: 1
If you loved gottfried,
you’ll fall even harder
for his truly giant big
brother who simply
cannot be missed.
Gottfried's big bro
thunders onto the scene
from Schleswig…
Quantity: 1
BUY NOW
struct ContentView: View {
@ObservedObject private var viewModel = GistsViewModel()
@State private var showSettings = false
var body: some View {
NavigationView {
VStack {
SearchBar(text: $viewModel.searchText)
List {
ForEach(viewModel.gists) { gist in
NavigationLink(destination: SafariView(url: gist.htmlURL)
.navigationBarTitle("")
.navigationBarHidden(true)) {
GistView(gist: gist)
}
}
}
.navigationBarTitle(Text("GitHub Gists"))
.navigationBarItems(trailing:
Button(action: { self.showSettings.toggle() }, label: {
Image(systemName: "gear")
.foregroundColor(.primary)
})
)
.edgesIgnoringSafeArea(.bottom)
}
.sheet(isPresented: $showSettings) {
SettingsView()
}
}
}
}
Anatomy of a SwiftUI View
struct ContentView: View {
@ObservedObject private var viewModel = GistsViewModel()
@State private var showSettings = false
var body: some View {
NavigationView {
VStack {
SearchBar(text: $viewModel.searchText)
List {
ForEach(viewModel.gists) { gist in
NavigationLink(destination: SafariView(url: gist.htmlURL)
.navigationBarTitle("")
.navigationBarHidden(true)) {
GistView(gist: gist)
}
}
}
.navigationBarTitle(Text("GitHub Gists"))
.navigationBarItems(trailing:
Button(action: { self.showSettings.toggle() }, label: {
Image(systemName: "gear")
.foregroundColor(.primary)
})
)
.edgesIgnoringSafeArea(.bottom)
}
.sheet(isPresented: $showSettings) {
SettingsView()
}
}
}
}
Anatomy of a SwiftUI View
Typically a struct, but can also be a final class
struct ContentView: View {
@ObservedObject private var viewModel = GistsViewModel()
@State private var showSettings = false
var body: some View {
NavigationView {
VStack {
SearchBar(text: $viewModel.searchText)
List {
ForEach(viewModel.gists) { gist in
NavigationLink(destination: SafariView(url: gist.htmlURL)
.navigationBarTitle("")
.navigationBarHidden(true)) {
GistView(gist: gist)
}
}
}
.navigationBarTitle(Text("GitHub Gists"))
.navigationBarItems(trailing:
Button(action: { self.showSettings.toggle() }, label: {
Image(systemName: "gear")
.foregroundColor(.primary)
})
)
.edgesIgnoringSafeArea(.bottom)
}
.sheet(isPresented: $showSettings) {
SettingsView()
}
}
}
}
Anatomy of a SwiftUI View
A protocol that requires a body property
struct ContentView: View {
@ObservedObject private var viewModel = GistsViewModel()
@State private var showSettings = false
var body: some View {
NavigationView {
VStack {
SearchBar(text: $viewModel.searchText)
List {
ForEach(viewModel.gists) { gist in
NavigationLink(destination: SafariView(url: gist.htmlURL)
.navigationBarTitle("")
.navigationBarHidden(true)) {
GistView(gist: gist)
}
}
}
.navigationBarTitle(Text("GitHub Gists"))
.navigationBarItems(trailing:
Button(action: { self.showSettings.toggle() }, label: {
Image(systemName: "gear")
.foregroundColor(.primary)
})
)
.edgesIgnoringSafeArea(.bottom)
}
.sheet(isPresented: $showSettings) {
SettingsView()
}
}
}
}
Anatomy of a SwiftUI View
A protocol that requires a body property
struct ContentView: View {
@ObservedObject private var viewModel = GistsViewModel()
@State private var showSettings = false
var body: some View {
NavigationView {
VStack {
SearchBar(text: $viewModel.searchText)
List {
ForEach(viewModel.gists) { gist in
NavigationLink(destination: SafariView(url: gist.htmlURL)
.navigationBarTitle("")
.navigationBarHidden(true)) {
GistView(gist: gist)
}
}
}
.navigationBarTitle(Text("GitHub Gists"))
.navigationBarItems(trailing:
Button(action: { self.showSettings.toggle() }, label: {
Image(systemName: "gear")
.foregroundColor(.primary)
})
)
.edgesIgnoringSafeArea(.bottom)
}
.sheet(isPresented: $showSettings) {
SettingsView()
}
}
}
}
Anatomy of a SwiftUI View
Property wrappers that manage observable state
struct ContentView: View {
@ObservedObject private var viewModel = GistsViewModel()
@State private var showSettings = false
var body: some View {
NavigationView {
VStack {
SearchBar(text: $viewModel.searchText)
List {
ForEach(viewModel.gists) { gist in
NavigationLink(destination: SafariView(url: gist.htmlURL)
.navigationBarTitle("")
.navigationBarHidden(true)) {
GistView(gist: gist)
}
}
}
.navigationBarTitle(Text("GitHub Gists"))
.navigationBarItems(trailing:
Button(action: { self.showSettings.toggle() }, label: {
Image(systemName: "gear")
.foregroundColor(.primary)
})
)
.edgesIgnoringSafeArea(.bottom)
}
.sheet(isPresented: $showSettings) {
SettingsView()
}
}
}
}
Anatomy of a SwiftUI View
struct ContentView: View {
@ObservedObject private var viewModel = GistsViewModel()
@State private var showSettings = false
var body: some View {
NavigationView {
VStack {
SearchBar(text: $viewModel.searchText)
List {
ForEach(viewModel.gists) { gist in
NavigationLink(destination: SafariView(url: gist.htmlURL)
.navigationBarTitle("")
.navigationBarHidden(true)) {
GistView(gist: gist)
}
}
}
.navigationBarTitle(Text("GitHub Gists"))
.navigationBarItems(trailing:
Button(action: { self.showSettings.toggle() }, label: {
Image(systemName: "gear")
.foregroundColor(.primary)
})
)
.edgesIgnoringSafeArea(.bottom)
}
.sheet(isPresented: $showSettings) {
SettingsView()
}
}
}
}
Anatomy of a SwiftUI View
struct ContentView: View {
@ObservedObject private var viewModel = GistsViewModel()
@State private var showSettings = false
var body: some View {
NavigationView {
VStack {
SearchBar(text: $viewModel.searchText)
List {
ForEach(viewModel.gists) { gist in
NavigationLink(destination: SafariView(url: gist.htmlURL)
.navigationBarTitle("")
.navigationBarHidden(true)) {
GistView(gist: gist)
}
}
}
.navigationBarTitle(Text("GitHub Gists"))
.navigationBarItems(trailing:
Button(action: { self.showSettings.toggle() }, label: {
Image(systemName: "gear")
.foregroundColor(.primary)
})
)
.edgesIgnoringSafeArea(.bottom)
}
.sheet(isPresented: $showSettings) {
SettingsView()
}
}
}
}
Anatomy of a SwiftUI View
struct ContentView: View {
@ObservedObject private var viewModel = GistsViewModel()
@State private var showSettings = false
var body: some View {
NavigationView {
VStack {
SearchBar(text: $viewModel.searchText)
List {
ForEach(viewModel.gists) { gist in
NavigationLink(destination: SafariView(url: gist.htmlURL)
.navigationBarTitle("")
.navigationBarHidden(true)) {
GistView(gist: gist)
}
}
}
.navigationBarTitle(Text("GitHub Gists"))
.navigationBarItems(trailing:
Button(action: { self.showSettings.toggle() }, label: {
Image(systemName: "gear")
.foregroundColor(.primary)
})
)
.edgesIgnoringSafeArea(.bottom)
}
.sheet(isPresented: $showSettings) {
SettingsView()
}
}
}
}
Anatomy of a SwiftUI View
Creates a visible navigation hierarchy
struct ContentView: View {
@ObservedObject private var viewModel = GistsViewModel()
@State private var showSettings = false
var body: some View {
NavigationView {
VStack {
SearchBar(text: $viewModel.searchText)
List {
ForEach(viewModel.gists) { gist in
NavigationLink(destination: SafariView(url: gist.htmlURL)
.navigationBarTitle("")
.navigationBarHidden(true)) {
GistView(gist: gist)
}
}
}
.navigationBarTitle(Text("GitHub Gists"))
.navigationBarItems(trailing:
Button(action: { self.showSettings.toggle() }, label: {
Image(systemName: "gear")
.foregroundColor(.primary)
})
)
.edgesIgnoringSafeArea(.bottom)
}
.sheet(isPresented: $showSettings) {
SettingsView()
}
}
}
}
Anatomy of a SwiftUI View
Creates a vertical stack view
struct ContentView: View {
@ObservedObject private var viewModel = GistsViewModel()
@State private var showSettings = false
var body: some View {
NavigationView {
VStack {
SearchBar(text: $viewModel.searchText)
List {
ForEach(viewModel.gists) { gist in
NavigationLink(destination: SafariView(url: gist.htmlURL)
.navigationBarTitle("")
.navigationBarHidden(true)) {
GistView(gist: gist)
}
}
}
.navigationBarTitle(Text("GitHub Gists"))
.navigationBarItems(trailing:
Button(action: { self.showSettings.toggle() }, label: {
Image(systemName: "gear")
.foregroundColor(.primary)
})
)
.edgesIgnoringSafeArea(.bottom)
}
.sheet(isPresented: $showSettings) {
SettingsView()
}
}
}
}
Anatomy of a SwiftUI View
A custom view that wraps a UISearchBar
struct ContentView: View {
@ObservedObject private var viewModel = GistsViewModel()
@State private var showSettings = false
var body: some View {
NavigationView {
VStack {
SearchBar(text: $viewModel.searchText)
List {
ForEach(viewModel.gists) { gist in
NavigationLink(destination: SafariView(url: gist.htmlURL)
.navigationBarTitle("")
.navigationBarHidden(true)) {
GistView(gist: gist)
}
}
}
.navigationBarTitle(Text("GitHub Gists"))
.navigationBarItems(trailing:
Button(action: { self.showSettings.toggle() }, label: {
Image(systemName: "gear")
.foregroundColor(.primary)
})
)
.edgesIgnoringSafeArea(.bottom)
}
.sheet(isPresented: $showSettings) {
SettingsView()
}
}
}
}
Anatomy of a SwiftUI View
Creates a table view
struct ContentView: View {
@ObservedObject private var viewModel = GistsViewModel()
@State private var showSettings = false
var body: some View {
NavigationView {
VStack {
SearchBar(text: $viewModel.searchText)
List {
ForEach(viewModel.gists) { gist in
NavigationLink(destination: SafariView(url: gist.htmlURL)
.navigationBarTitle("")
.navigationBarHidden(true)) {
GistView(gist: gist)
}
}
}
.navigationBarTitle(Text("GitHub Gists"))
.navigationBarItems(trailing:
Button(action: { self.showSettings.toggle() }, label: {
Image(systemName: "gear")
.foregroundColor(.primary)
})
)
.edgesIgnoringSafeArea(.bottom)
}
.sheet(isPresented: $showSettings) {
SettingsView()
}
}
}
}
Anatomy of a SwiftUI View
struct ContentView: View {
@ObservedObject private var viewModel = GistsViewModel()
@State private var showSettings = false
var body: some View {
NavigationView {
VStack {
SearchBar(text: $viewModel.searchText)
List {
ForEach(viewModel.gists) { gist in
NavigationLink(destination: SafariView(url: gist.htmlURL)
.navigationBarTitle("")
.navigationBarHidden(true)) {
GistView(gist: gist)
}
}
}
.navigationBarTitle(Text("GitHub Gists"))
.navigationBarItems(trailing:
Button(action: { self.showSettings.toggle() }, label: {
Image(systemName: "gear")
.foregroundColor(.primary)
})
)
.edgesIgnoringSafeArea(.bottom)
}
.sheet(isPresented: $showSettings) {
SettingsView()
}
}
}
}
Anatomy of a SwiftUI View
Creates views on demand from a collection
struct ContentView: View {
@ObservedObject private var viewModel = GistsViewModel()
@State private var showSettings = false
var body: some View {
NavigationView {
VStack {
SearchBar(text: $viewModel.searchText)
List {
ForEach(viewModel.gists) { gist in
NavigationLink(destination: SafariView(url: gist.htmlURL)
.navigationBarTitle("")
.navigationBarHidden(true)) {
GistView(gist: gist)
}
}
}
.navigationBarTitle(Text("GitHub Gists"))
.navigationBarItems(trailing:
Button(action: { self.showSettings.toggle() }, label: {
Image(systemName: "gear")
.foregroundColor(.primary)
})
)
.edgesIgnoringSafeArea(.bottom)
}
.sheet(isPresented: $showSettings) {
SettingsView()
}
}
}
}
Anatomy of a SwiftUI View
A button that triggers navigation
struct ContentView: View {
@ObservedObject private var viewModel = GistsViewModel()
@State private var showSettings = false
var body: some View {
NavigationView {
VStack {
SearchBar(text: $viewModel.searchText)
List {
ForEach(viewModel.gists) { gist in
NavigationLink(destination: SafariView(url: gist.htmlURL)
.navigationBarTitle("")
.navigationBarHidden(true)) {
GistView(gist: gist)
}
}
}
.navigationBarTitle(Text("GitHub Gists"))
.navigationBarItems(trailing:
Button(action: { self.showSettings.toggle() }, label: {
Image(systemName: "gear")
.foregroundColor(.primary)
})
)
.edgesIgnoringSafeArea(.bottom)
}
.sheet(isPresented: $showSettings) {
SettingsView()
}
}
}
}
Anatomy of a SwiftUI View
View modifiers
struct ContentView: View {
@ObservedObject private var viewModel = GistsViewModel()
@State private var showSettings = false
var body: some View {
NavigationView {
VStack {
SearchBar(text: $viewModel.searchText)
List {
ForEach(viewModel.gists) { gist in
NavigationLink(destination: SafariView(url: gist.htmlURL)
.navigationBarTitle("")
.navigationBarHidden(true)) {
GistView(gist: gist)
}
}
}
.navigationBarTitle(Text("GitHub Gists"))
.navigationBarItems(trailing:
Button(action: { self.showSettings.toggle() }, label: {
Image(systemName: "gear")
.foregroundColor(.primary)
})
)
.edgesIgnoringSafeArea(.bottom)
}
.sheet(isPresented: $showSettings) {
SettingsView()
}
}
}
}
Anatomy of a SwiftUI View
View modifiers
struct ContentView: View {
@ObservedObject private var viewModel = GistsViewModel()
@State private var showSettings = false
var body: some View {
NavigationView {
VStack {
SearchBar(text: $viewModel.searchText)
List {
ForEach(viewModel.gists) { gist in
NavigationLink(destination: SafariView(url: gist.htmlURL)
.navigationBarTitle("")
.navigationBarHidden(true)) {
GistView(gist: gist)
}
}
}
.navigationBarTitle(Text("GitHub Gists"))
.navigationBarItems(trailing:
Button(action: { self.showSettings.toggle() }, label: {
Image(systemName: "gear")
.foregroundColor(.primary)
})
)
.edgesIgnoringSafeArea(.bottom)
}
.sheet(isPresented: $showSettings) {
SettingsView()
}
}
}
}
Anatomy of a SwiftUI View
Presents a view as a sheet
struct ContentView: View {
@ObservedObject private var viewModel = GistsViewModel()
@State private var showSettings = false
var body: some View {
NavigationView {
VStack {
SearchBar(text: $viewModel.searchText)
List {
ForEach(viewModel.gists) { gist in
NavigationLink(destination: SafariView(url: gist.htmlURL)
.navigationBarTitle("")
.navigationBarHidden(true)) {
GistView(gist: gist)
}
}
}
.navigationBarTitle(Text("GitHub Gists"))
.navigationBarItems(trailing:
Button(action: { self.showSettings.toggle() }, label: {
Image(systemName: "gear")
.foregroundColor(.primary)
})
)
.edgesIgnoringSafeArea(.bottom)
}
.sheet(isPresented: $showSettings) {
SettingsView()
}
}
}
}
Anatomy of a SwiftUI View
A protocol that requires a body property
Typically a struct, but can also be a final class
Property wrappers that manage observable state
From Getting Started with Combine and SwiftUI in iOS - Scott Gardner - scotteg.com @scotteg
Creates a visible navigation hierarchy
Creates a vertical stack view
A custom view that wraps a UISearchBar
Creates a table view
Creates views on demand from a collection
A button that triggers navigation
View modifiers
Presents a view as a sheet
–Bruce Lee
“If you want to learn to swim,
jump into the water.”
Download resources:
bit.ly/gscsresources
Download final:
bit.ly/gscsfinal
bit.ly/combinebook bit.ly/swiftuibook
What property lets you receive a
bindable object from an ancestor?
@EnvironmentObject
What property wrapper lets you
connect an @State property to a
child view?
@Binding
Questions?
Thanks!
@scotteg
scotteg
scotteg.com
$scotteg

Getting Started with Combine And SwiftUI

  • 1.
    Getting Started with Combineand SwiftUI in iOS Scott Gardner
  • 2.
    About Me iOS apparchitect & developer since 2010 iOS Architect at Wayfair Author of video courses & books on Swift & iOS app development Apple Certified Trainer for Swift and iOS
  • 3.
    What’s In Store Combinebasics UIKit vs. SwiftUI + Combine Anatomy of a SwiftUI View Create Crosstalk app
  • 4.
    What’s In Store Combinebasics UIKit vs. SwiftUI + Combine Anatomy of a SwiftUI View Create Crosstalk app Wrap up Want more?
  • 5.
  • 6.
    What is Combine? Reactiveframework for asynchronous operations Declarative vs. imperative Unified approach
  • 7.
  • 8.
  • 9.
    Combine SwiftUI Foundation Core Data @FetchRequest NSManagedObject URLSession NotificationCenter WhyCombine? ObservableObject @Published @ObservedObject (@State / @Binding)
  • 10.
    Combine Basics Publisher Subscriber subscribes givessubscription requests values sends values sends completion
  • 11.
    Combine Basics Publisher Subscriber subscribes givessubscription requests values sends values sends completion
  • 12.
    Combine Basics Publisher Subscriber subscribes givessubscription requests values sends values sends completion cancels
  • 13.
    Combine Operators 1 23 map { $0 * 2 } 2 4 6 Publisher Subscriber
  • 14.
    UIKit + Traditional Fetchdata Decode Update state Update UI
  • 15.
    UIKit + Traditional FetchdataDecodeUpdate stateUpdate UI
  • 16.
    SwiftUI + Combine Fetchdata Decode Update state
  • 17.
    SwiftUI + Combine FetchdataDecodeUpdate state
  • 18.
  • 19.
    Update UI inUIKit OUT OF STOCK Product: 8’ Gnome Quantity: 0 Product: 8’ Gnome Quantity: 0 If you loved gottfried, you’ll fall even harder for his truly giant big brother who simply cannot be missed. Gottfried's big bro thunders onto the scene from Schleswig…
  • 20.
    U Update UI inUIKit OUT OF STOCK Product: 8’ Gnome Quantity: 0 Product: 8’ Gnome F D S Quantity: 1 If you loved gottfried, you’ll fall even harder for his truly giant big brother who simply cannot be missed. Gottfried's big bro thunders onto the scene from Schleswig…
  • 21.
    U Update UI inUIKit OUT OF STOCK Product: 8’ Gnome Product: 8’ Gnome F D S Quantity: 1 If you loved gottfried, you’ll fall even harder for his truly giant big brother who simply cannot be missed. Gottfried's big bro thunders onto the scene from Schleswig… Quantity: 1
  • 22.
    Update UI inCombine + SwiftUI OUT OF STOCK Product: 8’ Gnome Quantity: 0 Product: 8’ Gnome Quantity: 0 If you loved gottfried, you’ll fall even harder for his truly giant big brother who simply cannot be missed. Gottfried's big bro thunders onto the scene from Schleswig…
  • 23.
    Update UI inCombine + SwiftUI Product: 8’ Gnome Product: 8’ Gnome F D S Quantity: 1 If you loved gottfried, you’ll fall even harder for his truly giant big brother who simply cannot be missed. Gottfried's big bro thunders onto the scene from Schleswig… Quantity: 1 BUY NOW
  • 24.
    struct ContentView: View{ @ObservedObject private var viewModel = GistsViewModel() @State private var showSettings = false var body: some View { NavigationView { VStack { SearchBar(text: $viewModel.searchText) List { ForEach(viewModel.gists) { gist in NavigationLink(destination: SafariView(url: gist.htmlURL) .navigationBarTitle("") .navigationBarHidden(true)) { GistView(gist: gist) } } } .navigationBarTitle(Text("GitHub Gists")) .navigationBarItems(trailing: Button(action: { self.showSettings.toggle() }, label: { Image(systemName: "gear") .foregroundColor(.primary) }) ) .edgesIgnoringSafeArea(.bottom) } .sheet(isPresented: $showSettings) { SettingsView() } } } } Anatomy of a SwiftUI View
  • 25.
    struct ContentView: View{ @ObservedObject private var viewModel = GistsViewModel() @State private var showSettings = false var body: some View { NavigationView { VStack { SearchBar(text: $viewModel.searchText) List { ForEach(viewModel.gists) { gist in NavigationLink(destination: SafariView(url: gist.htmlURL) .navigationBarTitle("") .navigationBarHidden(true)) { GistView(gist: gist) } } } .navigationBarTitle(Text("GitHub Gists")) .navigationBarItems(trailing: Button(action: { self.showSettings.toggle() }, label: { Image(systemName: "gear") .foregroundColor(.primary) }) ) .edgesIgnoringSafeArea(.bottom) } .sheet(isPresented: $showSettings) { SettingsView() } } } } Anatomy of a SwiftUI View Typically a struct, but can also be a final class
  • 26.
    struct ContentView: View{ @ObservedObject private var viewModel = GistsViewModel() @State private var showSettings = false var body: some View { NavigationView { VStack { SearchBar(text: $viewModel.searchText) List { ForEach(viewModel.gists) { gist in NavigationLink(destination: SafariView(url: gist.htmlURL) .navigationBarTitle("") .navigationBarHidden(true)) { GistView(gist: gist) } } } .navigationBarTitle(Text("GitHub Gists")) .navigationBarItems(trailing: Button(action: { self.showSettings.toggle() }, label: { Image(systemName: "gear") .foregroundColor(.primary) }) ) .edgesIgnoringSafeArea(.bottom) } .sheet(isPresented: $showSettings) { SettingsView() } } } } Anatomy of a SwiftUI View A protocol that requires a body property
  • 27.
    struct ContentView: View{ @ObservedObject private var viewModel = GistsViewModel() @State private var showSettings = false var body: some View { NavigationView { VStack { SearchBar(text: $viewModel.searchText) List { ForEach(viewModel.gists) { gist in NavigationLink(destination: SafariView(url: gist.htmlURL) .navigationBarTitle("") .navigationBarHidden(true)) { GistView(gist: gist) } } } .navigationBarTitle(Text("GitHub Gists")) .navigationBarItems(trailing: Button(action: { self.showSettings.toggle() }, label: { Image(systemName: "gear") .foregroundColor(.primary) }) ) .edgesIgnoringSafeArea(.bottom) } .sheet(isPresented: $showSettings) { SettingsView() } } } } Anatomy of a SwiftUI View A protocol that requires a body property
  • 28.
    struct ContentView: View{ @ObservedObject private var viewModel = GistsViewModel() @State private var showSettings = false var body: some View { NavigationView { VStack { SearchBar(text: $viewModel.searchText) List { ForEach(viewModel.gists) { gist in NavigationLink(destination: SafariView(url: gist.htmlURL) .navigationBarTitle("") .navigationBarHidden(true)) { GistView(gist: gist) } } } .navigationBarTitle(Text("GitHub Gists")) .navigationBarItems(trailing: Button(action: { self.showSettings.toggle() }, label: { Image(systemName: "gear") .foregroundColor(.primary) }) ) .edgesIgnoringSafeArea(.bottom) } .sheet(isPresented: $showSettings) { SettingsView() } } } } Anatomy of a SwiftUI View Property wrappers that manage observable state
  • 29.
    struct ContentView: View{ @ObservedObject private var viewModel = GistsViewModel() @State private var showSettings = false var body: some View { NavigationView { VStack { SearchBar(text: $viewModel.searchText) List { ForEach(viewModel.gists) { gist in NavigationLink(destination: SafariView(url: gist.htmlURL) .navigationBarTitle("") .navigationBarHidden(true)) { GistView(gist: gist) } } } .navigationBarTitle(Text("GitHub Gists")) .navigationBarItems(trailing: Button(action: { self.showSettings.toggle() }, label: { Image(systemName: "gear") .foregroundColor(.primary) }) ) .edgesIgnoringSafeArea(.bottom) } .sheet(isPresented: $showSettings) { SettingsView() } } } } Anatomy of a SwiftUI View
  • 30.
    struct ContentView: View{ @ObservedObject private var viewModel = GistsViewModel() @State private var showSettings = false var body: some View { NavigationView { VStack { SearchBar(text: $viewModel.searchText) List { ForEach(viewModel.gists) { gist in NavigationLink(destination: SafariView(url: gist.htmlURL) .navigationBarTitle("") .navigationBarHidden(true)) { GistView(gist: gist) } } } .navigationBarTitle(Text("GitHub Gists")) .navigationBarItems(trailing: Button(action: { self.showSettings.toggle() }, label: { Image(systemName: "gear") .foregroundColor(.primary) }) ) .edgesIgnoringSafeArea(.bottom) } .sheet(isPresented: $showSettings) { SettingsView() } } } } Anatomy of a SwiftUI View
  • 31.
    struct ContentView: View{ @ObservedObject private var viewModel = GistsViewModel() @State private var showSettings = false var body: some View { NavigationView { VStack { SearchBar(text: $viewModel.searchText) List { ForEach(viewModel.gists) { gist in NavigationLink(destination: SafariView(url: gist.htmlURL) .navigationBarTitle("") .navigationBarHidden(true)) { GistView(gist: gist) } } } .navigationBarTitle(Text("GitHub Gists")) .navigationBarItems(trailing: Button(action: { self.showSettings.toggle() }, label: { Image(systemName: "gear") .foregroundColor(.primary) }) ) .edgesIgnoringSafeArea(.bottom) } .sheet(isPresented: $showSettings) { SettingsView() } } } } Anatomy of a SwiftUI View
  • 32.
    struct ContentView: View{ @ObservedObject private var viewModel = GistsViewModel() @State private var showSettings = false var body: some View { NavigationView { VStack { SearchBar(text: $viewModel.searchText) List { ForEach(viewModel.gists) { gist in NavigationLink(destination: SafariView(url: gist.htmlURL) .navigationBarTitle("") .navigationBarHidden(true)) { GistView(gist: gist) } } } .navigationBarTitle(Text("GitHub Gists")) .navigationBarItems(trailing: Button(action: { self.showSettings.toggle() }, label: { Image(systemName: "gear") .foregroundColor(.primary) }) ) .edgesIgnoringSafeArea(.bottom) } .sheet(isPresented: $showSettings) { SettingsView() } } } } Anatomy of a SwiftUI View Creates a visible navigation hierarchy
  • 33.
    struct ContentView: View{ @ObservedObject private var viewModel = GistsViewModel() @State private var showSettings = false var body: some View { NavigationView { VStack { SearchBar(text: $viewModel.searchText) List { ForEach(viewModel.gists) { gist in NavigationLink(destination: SafariView(url: gist.htmlURL) .navigationBarTitle("") .navigationBarHidden(true)) { GistView(gist: gist) } } } .navigationBarTitle(Text("GitHub Gists")) .navigationBarItems(trailing: Button(action: { self.showSettings.toggle() }, label: { Image(systemName: "gear") .foregroundColor(.primary) }) ) .edgesIgnoringSafeArea(.bottom) } .sheet(isPresented: $showSettings) { SettingsView() } } } } Anatomy of a SwiftUI View Creates a vertical stack view
  • 34.
    struct ContentView: View{ @ObservedObject private var viewModel = GistsViewModel() @State private var showSettings = false var body: some View { NavigationView { VStack { SearchBar(text: $viewModel.searchText) List { ForEach(viewModel.gists) { gist in NavigationLink(destination: SafariView(url: gist.htmlURL) .navigationBarTitle("") .navigationBarHidden(true)) { GistView(gist: gist) } } } .navigationBarTitle(Text("GitHub Gists")) .navigationBarItems(trailing: Button(action: { self.showSettings.toggle() }, label: { Image(systemName: "gear") .foregroundColor(.primary) }) ) .edgesIgnoringSafeArea(.bottom) } .sheet(isPresented: $showSettings) { SettingsView() } } } } Anatomy of a SwiftUI View A custom view that wraps a UISearchBar
  • 35.
    struct ContentView: View{ @ObservedObject private var viewModel = GistsViewModel() @State private var showSettings = false var body: some View { NavigationView { VStack { SearchBar(text: $viewModel.searchText) List { ForEach(viewModel.gists) { gist in NavigationLink(destination: SafariView(url: gist.htmlURL) .navigationBarTitle("") .navigationBarHidden(true)) { GistView(gist: gist) } } } .navigationBarTitle(Text("GitHub Gists")) .navigationBarItems(trailing: Button(action: { self.showSettings.toggle() }, label: { Image(systemName: "gear") .foregroundColor(.primary) }) ) .edgesIgnoringSafeArea(.bottom) } .sheet(isPresented: $showSettings) { SettingsView() } } } } Anatomy of a SwiftUI View Creates a table view
  • 36.
    struct ContentView: View{ @ObservedObject private var viewModel = GistsViewModel() @State private var showSettings = false var body: some View { NavigationView { VStack { SearchBar(text: $viewModel.searchText) List { ForEach(viewModel.gists) { gist in NavigationLink(destination: SafariView(url: gist.htmlURL) .navigationBarTitle("") .navigationBarHidden(true)) { GistView(gist: gist) } } } .navigationBarTitle(Text("GitHub Gists")) .navigationBarItems(trailing: Button(action: { self.showSettings.toggle() }, label: { Image(systemName: "gear") .foregroundColor(.primary) }) ) .edgesIgnoringSafeArea(.bottom) } .sheet(isPresented: $showSettings) { SettingsView() } } } } Anatomy of a SwiftUI View
  • 37.
    struct ContentView: View{ @ObservedObject private var viewModel = GistsViewModel() @State private var showSettings = false var body: some View { NavigationView { VStack { SearchBar(text: $viewModel.searchText) List { ForEach(viewModel.gists) { gist in NavigationLink(destination: SafariView(url: gist.htmlURL) .navigationBarTitle("") .navigationBarHidden(true)) { GistView(gist: gist) } } } .navigationBarTitle(Text("GitHub Gists")) .navigationBarItems(trailing: Button(action: { self.showSettings.toggle() }, label: { Image(systemName: "gear") .foregroundColor(.primary) }) ) .edgesIgnoringSafeArea(.bottom) } .sheet(isPresented: $showSettings) { SettingsView() } } } } Anatomy of a SwiftUI View Creates views on demand from a collection
  • 38.
    struct ContentView: View{ @ObservedObject private var viewModel = GistsViewModel() @State private var showSettings = false var body: some View { NavigationView { VStack { SearchBar(text: $viewModel.searchText) List { ForEach(viewModel.gists) { gist in NavigationLink(destination: SafariView(url: gist.htmlURL) .navigationBarTitle("") .navigationBarHidden(true)) { GistView(gist: gist) } } } .navigationBarTitle(Text("GitHub Gists")) .navigationBarItems(trailing: Button(action: { self.showSettings.toggle() }, label: { Image(systemName: "gear") .foregroundColor(.primary) }) ) .edgesIgnoringSafeArea(.bottom) } .sheet(isPresented: $showSettings) { SettingsView() } } } } Anatomy of a SwiftUI View A button that triggers navigation
  • 39.
    struct ContentView: View{ @ObservedObject private var viewModel = GistsViewModel() @State private var showSettings = false var body: some View { NavigationView { VStack { SearchBar(text: $viewModel.searchText) List { ForEach(viewModel.gists) { gist in NavigationLink(destination: SafariView(url: gist.htmlURL) .navigationBarTitle("") .navigationBarHidden(true)) { GistView(gist: gist) } } } .navigationBarTitle(Text("GitHub Gists")) .navigationBarItems(trailing: Button(action: { self.showSettings.toggle() }, label: { Image(systemName: "gear") .foregroundColor(.primary) }) ) .edgesIgnoringSafeArea(.bottom) } .sheet(isPresented: $showSettings) { SettingsView() } } } } Anatomy of a SwiftUI View View modifiers
  • 40.
    struct ContentView: View{ @ObservedObject private var viewModel = GistsViewModel() @State private var showSettings = false var body: some View { NavigationView { VStack { SearchBar(text: $viewModel.searchText) List { ForEach(viewModel.gists) { gist in NavigationLink(destination: SafariView(url: gist.htmlURL) .navigationBarTitle("") .navigationBarHidden(true)) { GistView(gist: gist) } } } .navigationBarTitle(Text("GitHub Gists")) .navigationBarItems(trailing: Button(action: { self.showSettings.toggle() }, label: { Image(systemName: "gear") .foregroundColor(.primary) }) ) .edgesIgnoringSafeArea(.bottom) } .sheet(isPresented: $showSettings) { SettingsView() } } } } Anatomy of a SwiftUI View View modifiers
  • 41.
    struct ContentView: View{ @ObservedObject private var viewModel = GistsViewModel() @State private var showSettings = false var body: some View { NavigationView { VStack { SearchBar(text: $viewModel.searchText) List { ForEach(viewModel.gists) { gist in NavigationLink(destination: SafariView(url: gist.htmlURL) .navigationBarTitle("") .navigationBarHidden(true)) { GistView(gist: gist) } } } .navigationBarTitle(Text("GitHub Gists")) .navigationBarItems(trailing: Button(action: { self.showSettings.toggle() }, label: { Image(systemName: "gear") .foregroundColor(.primary) }) ) .edgesIgnoringSafeArea(.bottom) } .sheet(isPresented: $showSettings) { SettingsView() } } } } Anatomy of a SwiftUI View Presents a view as a sheet
  • 42.
    struct ContentView: View{ @ObservedObject private var viewModel = GistsViewModel() @State private var showSettings = false var body: some View { NavigationView { VStack { SearchBar(text: $viewModel.searchText) List { ForEach(viewModel.gists) { gist in NavigationLink(destination: SafariView(url: gist.htmlURL) .navigationBarTitle("") .navigationBarHidden(true)) { GistView(gist: gist) } } } .navigationBarTitle(Text("GitHub Gists")) .navigationBarItems(trailing: Button(action: { self.showSettings.toggle() }, label: { Image(systemName: "gear") .foregroundColor(.primary) }) ) .edgesIgnoringSafeArea(.bottom) } .sheet(isPresented: $showSettings) { SettingsView() } } } } Anatomy of a SwiftUI View A protocol that requires a body property Typically a struct, but can also be a final class Property wrappers that manage observable state From Getting Started with Combine and SwiftUI in iOS - Scott Gardner - scotteg.com @scotteg Creates a visible navigation hierarchy Creates a vertical stack view A custom view that wraps a UISearchBar Creates a table view Creates views on demand from a collection A button that triggers navigation View modifiers Presents a view as a sheet
  • 43.
    –Bruce Lee “If youwant to learn to swim, jump into the water.”
  • 44.
  • 45.
  • 47.
  • 48.
    What property letsyou receive a bindable object from an ancestor?
  • 49.
  • 50.
    What property wrapperlets you connect an @State property to a child view?
  • 51.
  • 52.
  • 53.