Building SwiftUI Apps with Firestore Integration

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

  1. Open Xcode and create a new SwiftUI project.
  2. Choose a suitable name for your project and select SwiftUI as the User Interface option.
  3. 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

  1. Define a struct for each of the five views, such as View1, View2, View3, View4, and View5.
  2. Implement the body property for each view, displaying the relevant data obtained from Firestore.

Step 4: Fetching Data from Firestore

  1. Create a new Swift file, e.g., FirestoreManager.swift, to handle Firestore interactions.
  2. Import Firebase and Firestore at the top of the file.
  3. 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)
       }
   }
  1. 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
       }
   }
  1. In the onAppear modifier for each view, call the fetchData method from FirestoreManager 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:

0 0 votes
Article Rating
Subscribe
Notify of
guest
0 Comments
Inline Feedbacks
View all comments