In this tutorial, we’ll explore how to create SwiftUI Apps with Firestore as a backend for fetching and displaying data. We’ll build an app with five views, each showcasing its unique data set. By the end, you’ll have a solid understanding of integrating Firestore with SwiftUI and present dynamic content in your app.
Prerequisites
To follow along, you should understand SwiftUI and have Xcode installed on your Mac. Additionally, you’ll need a Firestore project set up with appropriate collections and documents containing the required data.
Let’s dive into the step-by-step process of creating our SwiftUI app with Firestore integration:
Step 1: Setting Up the Project
- Open Xcode and create a new SwiftUI project.
- Choose a suitable name for your project and select SwiftUI as the User Interface option.
- Once the project is created, open the ContentView.swift file.
Step 2: Importing Firebase and Firestore Dependencies
For setting up Firebase and Firestore in your app, head over to the official documentation and follow their steps here. Once you have this done, come back and follow along.
Step 3: Building the SwiftUI Views
- Define a struct for each of the five views, such as
View1
,View2
,View3
,View4
, andView5
. - Implement the body property for each view, displaying the relevant data obtained from Firestore.
Step 4: Fetching Data from Firestore
- Create a new Swift file, e.g.,
FirestoreManager.swift
, to handle Firestore interactions. - Import Firebase and Firestore at the top of the file.
- Define a class, e.g.,
FirestoreManager
with a static method for fetching data from Firestore. For example:
import Firebase
import FirebaseFirestore
class FirestoreManager {
static func fetchData(completion: @escaping ([DocumentSnapshot]?, Error?) -> Void) {
let db = Firestore.firestore()
db.collection("your_collection_name").getDocuments(completion: completion)
}
}
- Update each view struct to include a property for storing the fetched data. For example:
struct View1: View {
@State private var data: [DocumentSnapshot] = []
var body: some View {
// Display the data obtained from Firestore
}
}
- In the
onAppear
modifier for each view, call thefetchData
method fromFirestoreManager
and update the corresponding@State
property.
Step 5: Creating the Five Views with Data Fetching
Let’s look at some concrete examples using the pattern described above.
View 1: Users
struct UsersView: View {
@State private var users: [User] = []
var body: some View {
List(users) { user in
VStack(alignment: .leading) {
Text(user.name)
.font(.headline)
Text(user.email)
.font(.subheadline)
}
}
.onAppear {
FirestoreManager.fetchUsers { fetchedUsers, error in
if let fetchedUsers = fetchedUsers {
self.users = fetchedUsers
}
}
}
}
}
View 2: Places
struct PlacesView: View {
@State private var places: [Place] = []
var body: some View {
List(places) { place in
VStack(alignment: .leading) {
Text(place.name)
.font(.headline)
Text(place.address)
.font(.subheadline)
}
}
.onAppear {
FirestoreManager.fetchPlaces { fetchedPlaces, error in
if let fetchedPlaces = fetchedPlaces {
self.places = fetchedPlaces
}
}
}
}
}
View 3: Events
struct EventsView: View {
@State private var events: [Event] = []
var body: some View {
List(events) { event in
VStack(alignment: .leading) {
Text(event.title)
.font(.headline)
Text(event.date)
.font(.subheadline)
}
}
.onAppear {
FirestoreManager.fetchEvents { fetchedEvents, error in
if let fetchedEvents = fetchedEvents {
self.events = fetchedEvents
}
}
}
}
}
View 4: Flights
struct FlightsView: View {
@State private var flights: [Flight] = []
var body: some View {
List(flights) { flight in
VStack(alignment: .leading) {
Text(flight.number)
.font(.headline)
Text(flight.departure)
.font(.subheadline)
}
}
.onAppear {
FirestoreManager.fetchFlights { fetchedFlights, error in
if let fetchedFlights = fetchedFlights {
self.flights = fetchedFlights
}
}
}
}
}
View 5: Car Rentals
struct CarRentalsView: View {
@State private var carRentals: [CarRental] = []
var body: some View {
List(carRentals) { carRental in
VStack(alignment: .leading) {
Text(carRental.company)
.font(.headline)
Text(carRental.location)
.font(.subheadline)
}
}
.onAppear {
FirestoreManager.fetchCarRentals { fetchedCarRentals, error in
if let fetchedCarRentals = fetchedCarRentals {
self.carRentals = fetchedCarRentals
}
}
}
}
}
Note: Please replace the placeholder types (User, Place, Event, Flight, CarRental) with your own model types that match the structure of your data in Firestore.
By implementing these five views, each with its own data fetching logic, you can showcase different sets of data fetched from Firestore in your SwiftUI app. Remember to update the FirestoreManager class with the appropriate methods to fetch each data type.
Feel free to customize the UI and enhance the views based on your requirements. Happy coding!
What about progress indicators?
Adding a loading view while the data is being fetched is definitely a good practice, as it enhances the user experience and provides visual feedback that the app is actively retrieving data. Let’s modify the code to include a loading view for each of the five views:
View 1: Users
struct UsersView: View {
@State private var users: [User] = []
@State private var isLoading = true
var body: some View {
if isLoading {
ProgressView() // Show a loading indicator
} else {
List(users) { user in
VStack(alignment: .leading) {
Text(user.name)
.font(.headline)
Text(user.email)
.font(.subheadline)
}
}
}
.onAppear {
isLoading = true // Show loading indicator initially
FirestoreManager.fetchUsers { fetchedUsers, error in
if let fetchedUsers = fetchedUsers {
self.users = fetchedUsers
}
isLoading = false // Hide loading indicator after data is fetched
}
}
}
}
View 2: Places
struct PlacesView: View {
@State private var places: [Place] = []
@State private var isLoading = true
var body: some View {
if isLoading {
ProgressView() // Show a loading indicator
} else {
List(places) { place in
VStack(alignment: .leading) {
Text(place.name)
.font(.headline)
Text(place.address)
.font(.subheadline)
}
}
}
.onAppear {
isLoading = true // Show loading indicator initially
FirestoreManager.fetchPlaces { fetchedPlaces, error in
if let fetchedPlaces = fetchedPlaces {
self.places = fetchedPlaces
}
isLoading = false // Hide loading indicator after data is fetched
}
}
}
}
View 3: Events
struct EventsView: View {
@State private var events: [Event] = []
@State private var isLoading = true
var body: some View {
if isLoading {
ProgressView() // Show a loading indicator
} else {
List(events) { event in
VStack(alignment: .leading) {
Text(event.title)
.font(.headline)
Text(event.date)
.font(.subheadline)
}
}
}
.onAppear {
isLoading = true // Show loading indicator initially
FirestoreManager.fetchEvents { fetchedEvents, error in
if let fetchedEvents = fetchedEvents {
self.events = fetchedEvents
}
isLoading = false // Hide loading indicator after data is fetched
}
}
}
}
View 4: Flights
struct FlightsView: View {
@State private var flights: [Flight] = []
@State private var isLoading = true
var body: some View {
if isLoading {
ProgressView() // Show a loading indicator
} else {
List(flights) { flight in
VStack(alignment: .leading) {
Text(flight.number)
.font(.headline)
Text(flight.departure)
.font(.subheadline)
}
}
}
.onAppear {
isLoading = true // Show loading indicator initially
FirestoreManager.fetchFlights { fetchedFlights, error in
if let fetchedFlights = fetchedFlights {
self.flights = fetchedFlights
}
isLoading = false // Hide loading indicator after data is fetched
}
}
}
}
View 5: Car Rentals
struct CarRentalsView: View {
@State private var carRentals: [CarRental] = []
@State private var isLoading = true
var body: some View {
if isLoading {
ProgressView() // Show a loading indicator
} else {
List(carRentals) { carRental in
VStack(alignment: .leading) {
Text(carRental.company)
.font(.headline)
Text(carRental.location)
.font(.subheadline)
}
}
}
.onAppear {
isLoading = true // Show loading indicator initially
FirestoreManager.fetchCarRentals { fetchedCarRentals, error in
if let fetchedCarRentals = fetchedCarRentals {
self.carRentals = fetchedCarRentals
}
isLoading = false // Hide loading indicator after data is fetched
}
}
}
}
Adding the loading view will give users a visual indication that data is being fetched, improving the overall user experience.
Conclusion
Congratulations! You have successfully created a basic SwiftUI app that fetches data from Firestore and displays it across five different views. This tutorial provides a solid foundation for integrating Firestore into your SwiftUI projects and dynamically presenting content to your users. Feel free to expand on this app by adding more views or enhancing the user interface.
Be sure to check out my other SwiftUI articles: