In this talk, I'm going to show how to build data-driven SwiftUI applications that uses Cloud Firestore to store data.
You will learn how to architect your SwiftUI app so both its local and remote state stay in sync in real time
35. let db = Firestore.firestore()
do {
_ = try db.collection(“movies")
.addDocument(from: movie)
}
catch {
print(“Error: (error.localizedDescription).")
}
Saving Data to Firestore
36. let db = Firestore.firestore()
do {
_ = try db.collection(“movies")
.addDocument(from: movie)
}
catch {
print(“Error: (error.localizedDescription).")
}
Saving Data to Firestore
40. /
/
Model
struct Movie: Codable, Identifiable {
@DocumentID var id: String?
var title: String
var releaseDate: Date
var rating: Double
}
Architecture: Model
41. class MovieViewModel: ObservableObject {
@Published var movies = [Movie]()
private var db = Firestore.firestore()
func subscribe() {
db.collection("movies").addSnapshotListener { (querySnapshot, error) in
guard let documents = querySnapshot
?
.
documents else { return }
self.movies = documents.compactMap { queryDocumentSnapshot in
try? queryDocumentSnapshot.data(as: Movie.self)
}
}
}
}
Architecture: ViewModel
42. struct SimpleMovieList: View {
@StateObject var viewModel = MovieViewModel()
var body: some View {
List(viewModel.movies) { item in
Image(item.coverImageName)
VStack(alignment: .leading) {
Text(item.title)
Text(item.releaseDate.asString())
}
}
}
}
Architecture: View
46. Thanks! Peter Friese, Developer Advocate, Google
@pete
rf
riese
h
tt
ps://pete
rf
riese.dev
h
tt
ps://medium.com/@pete
rf
riese
h
tt
ps://medium.com/
fi
rebase-developers
h
tt
ps://stackove
rf
low.com/questions/tagged/swi
ft
ui+
fi
rebase
h
tt
ps://github.com/
fi
rebase/
fi
rebase-ios-sdk
50. Data Flow
• Prope
rt
ies
• @State
• @Binding
• @ObservedObject
• @StateObject (✨ new in Swi
ft
UI 2)
• @EnvironmentObject
• Sarah Reichelt: Swi
ft
UI Data Flow
(bit.ly/Swi
ft
UIDataFlow)
52. struct DetailsView: View {
let movie: Movie
var body: some View {
ScrollView(.vertical) {
VStack(alignment: .leading) {
Text(movie.title).font(.title)
Text(movie.description).font(.body)
}
.padding()
}
.edgesIgnoringSafeArea(.all)
.background(Color(UIColor.secondarySystemBackground)
.edgesIgnoringSafeArea(.all))
}
}
Data Flow - Prope
rt
y
53. struct DetailsView: View {
let movie: Movie
var body: some View {
ScrollView(.vertical) {
VStack(alignment: .leading) {
Text(movie.title).font(.title)
Text(movie.description).font(.body)
}
.padding()
}
.edgesIgnoringSafeArea(.all)
.background(Color(UIColor.secondarySystemBackground)
.edgesIgnoringSafeArea(.all))
}
}
Data Flow - Prope
rt
y
Use for data thatdoesn’t change
54. struct GridView: View {
let movies: [Movie]
@State private var selection: Movie? = nil
var body: some View {
LazyVGrid(columns: columns, spacing: 20) {
ForEach(movies) { movie in
if (movie.id
!
=
selection
?
.
id) {
CardView(movie: movie)
.onTapGesture { select(movie) }
.matchedGeometryEffect(id: movie.id, in: ns)
}
else {
CardView(movie: movie)
.opacity(0)
}
Data Flow - @State
55. struct GridView: View {
let movies: [Movie]
@State private var selection: Movie? = nil
var body: some View {
LazyVGrid(columns: columns, spacing: 20) {
ForEach(movies) { movie in
if (movie.id
!
=
selection
?
.
id) {
CardView(movie: movie)
.onTapGesture { select(movie) }
.matchedGeometryEffect(id: movie.id, in: ns)
}
else {
CardView(movie: movie)
.opacity(0)
}
Data Flow - @State
56. struct GridView: View {
let movies: [Movie]
@State private var selection: Movie? = nil
var body: some View {
LazyVGrid(columns: columns, spacing: 20) {
ForEach(movies) { movie in
if (movie.id
!
=
selection
?
.
id) {
CardView(movie: movie)
.onTapGesture { select(movie) }
.matchedGeometryEffect(id: movie.id, in: ns)
}
else {
CardView(movie: movie)
.opacity(0)
}
Data Flow - @State
Use for UI state
57. struct DiscoverMoviesView: View {
@StateObject var viewModel = DiscoverViewModel()
@Environment(.presentationMode) var presentationMode
@EnvironmentObject var movieStore: MovieStore
var body: some View {
NavigationView {
VStack {
SearchBar(text: $viewModel.searchText)
List {
ForEach(viewModel.tmdbMovies) { item in
Data Flow - @StateObject
58. struct DiscoverMoviesView: View {
@StateObject var viewModel = DiscoverViewModel()
@Environment(.presentationMode) var presentationMode
@EnvironmentObject var movieStore: MovieStore
var body: some View {
NavigationView {
VStack {
SearchBar(text: $viewModel.searchText)
List {
ForEach(viewModel.tmdbMovies) { item in
Data Flow - @StateObject
Use for viewmodels
59. struct DiscoverMoviesView: View {
@EnvironmentObject var movieStore: MovieStore
func addMovie(movie: TMDBMovie) {
print("Adding (movie.title)")
let newMovie = Movie(from: movie)
movieStore.addMovie(newMovie)
dismiss()
}
}
Data Flow - @EnvironmentObject
60. struct DiscoverMoviesView: View {
@EnvironmentObject var movieStore: MovieStore
func addMovie(movie: TMDBMovie) {
print("Adding (movie.title)")
let newMovie = Movie(from: movie)
movieStore.addMovie(newMovie)
dismiss()
}
}
Data Flow - @EnvironmentObject
Use for passingstuff down
62. import SwiftUI
import Firebase
@main
struct JewelCaseApp: App {
@StateObject var movieStore = MovieStore()
init() {
FirebaseApp.configure()
}
var body: some Scene {
WindowGroup {
ContentView()
.environmentObject(movieStore)
}
}
}
Sett ing up Firebase for Swi
ft
UI 2
😱 No more
AppDelegate
63. import SwiftUI
import Firebase
@main
struct JewelCaseApp: App {
@StateObject var movieStore = MovieStore()
init() {
FirebaseApp.configure()
}
var body: some Scene {
WindowGroup {
ContentView()
.environmentObject(movieStore)
}
}
}
Sett ing up Firebase for Swi
ft
UI 2
Let’s use theinitialiser
64. import SwiftUI
import Firebase
@main
struct JewelCaseApp: App {
@StateObject var movieStore = MovieStore()
init() {
FirebaseApp.configure()
}
var body: some Scene {
WindowGroup {
ContentView()
.environmentObject(movieStore)
}
}
}
Sett ing up Firebase for Swi
ft
UI 2