Swift and SwiftUI tutorials for Swift Developers

How to use Firebase with SwiftUI

For any iOS Developer, reaching the point where you master creating interfaces is a huge milestone. However, a modern application rarely lives isolated on the device. Sooner or later, you need to store data in the cloud, authenticate users, send notifications, or monitor crashes. This is where the big question arises: which backend do I choose?

Knowing how to use Firebase with SwiftUI has become one of the most in-demand skills in Swift programming. Firebase, Google’s Backend-as-a-Service (BaaS) platform, eliminates the need to write and maintain your own server infrastructure, allowing you to focus on what you do best: creating amazing experiences in SwiftUI for the Apple ecosystem.

In this extensive tutorial, I will guide you step by step on how to integrate, configure, and use Firebase in your Xcode projects, ensuring that your Swift code works flawlessly not only on iOS but also on macOS and watchOS.


1. Why are Firebase and SwiftUI the perfect match?

Historically, integrating external frameworks into Apple development could be a headache. But the evolution of Swift programming and the maturity of modern SDKs have changed the landscape.

  • Real-Time Synchronization: Services like Cloud Firestore fit perfectly with the reactive nature of SwiftUI. When data changes in the cloud, your @Published properties update, and your view is magically redrawn.
  • Swift Package Manager (SPM): Gone are the days of fighting with CocoaPods. Today, integrating Firebase in Xcode is native, fast, and secure.
  • Multiplatform Support: The Firebase SDK is designed to support multiple architectures, making it easy to bring your iOS app to the Mac or Apple Watch while sharing almost all your business logic.

2. Setting the Stage: Configuration in the Firebase Console

Before writing a single line of Swift code, we need to create the “brain” of our app in the cloud.

Step 2.1: Create the Project

  1. Go to the Firebase Console.
  2. Click on “Add project” and give it a name (e.g., “MySwiftUIApp”).
  3. You can enable or disable Google Analytics depending on your needs (for this tutorial, it’s optional).
  4. Once the project is created, click on the iOS icon in the central dashboard to add an Apple app.

Step 2.2: Register the App and Download the Plist

Firebase needs to know exactly which app has permission to connect to its database.

  • Bundle ID: Enter the exact Bundle Identifier of your project in Xcode (for example, com.yourcompany.MySwiftUIApp). This step is critical; if it doesn’t match, nothing will work.
  • Download the GoogleService-Info.plist file.

Drag this downloaded file directly into your project in Xcode (right below your Info.plist file or in the root of the project). Make sure to check the “Copy items if needed” box and that your main Target is selected.


3. Installing the Firebase SDK in Xcode

As an up-to-date iOS Developer, you will use Swift Package Manager (SPM). It is the recommended method by both Apple and Google.

  1. Open your project in Xcode.
  2. Go to File > Add Packages… (or Add Package Dependencies).
  3. In the search bar in the top right corner, paste the official Firebase repository URL: https://github.com/firebase/firebase-ios-sdk
  4. Select the Up to Next Major Version rule and click Add Package.

Xcode will download the source code (this might take a couple of minutes). When it finishes, it will ask you to choose which Firebase modules you want to include. For this tutorial, select FirebaseAnalytics, FirebaseAuth, and FirebaseFirestore.


4. Initializing Firebase in your SwiftUI App

With SwiftUI, the old AppDelegate is no longer the default entry point. We now use the App protocol. For Firebase to work correctly, it must be initialized as soon as the application launches.

Open the main file of your project (usually named [YourAppName]App.swift) and add the initialization. There are several ways to do this, but the most compatible one for multiplatform apps (iOS, macOS, watchOS) is using a delegate adaptor.

To keep the code clean and compatible with modern Swift programming, we will use the delegate adaptor for iOS/macOS, as Firebase recommends this route to handle push notifications and deep links in the future.

import SwiftUI
import FirebaseCore

// We create an AppDelegate to handle initialization
class AppDelegate: NSObject, UIApplicationDelegate {
    func application(_ application: UIApplication,
                     didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
        // Initialize Firebase
        FirebaseApp.configure()
        return true
    }
}

@main
struct MySwiftUIApp: App {
    // We inject the AppDelegate into the SwiftUI lifecycle
    @UIApplicationDelegateAdaptor(AppDelegate.self) var delegate

    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}

Note for watchOS: If you are building for the Apple Watch, UIApplicationDelegate does not exist. Instead, you will need to use WKExtensionDelegate or simply call FirebaseApp.configure() inside an init() method in your @main struct.


5. Cloud Firestore: Real-Time Database

Cloud Firestore is the crown jewel of Firebase. It is a flexible, scalable NoSQL database, perfect for learning how to use Firebase with SwiftUI reactively.

Let’s create a real-world example: a To-Do List application that syncs in real-time across your iPhone and your Mac.

5.1. The Data Model

First, we define our data model in Swift. We will use Codable so that Firestore can automatically transform our objects into NoSQL documents and vice versa.

import Foundation
import FirebaseFirestore

struct Task: Identifiable, Codable {
    @DocumentID var id: String? // Firebase will assign this ID automatically
    var title: String
    var isCompleted: Bool
    var creationDate: Date
}

5.2. The ViewModel (Business Logic)

Like a good iOS Developer, we will separate the logic from the view using the MVVM (Model-View-ViewModel) pattern.

import Foundation
import FirebaseFirestore
import Combine

class TasksViewModel: ObservableObject {
    @Published var tasks: [Task] = []
    private var db = Firestore.firestore()
    private var listenerRegistration: ListenerRegistration?
    
    // Start listening for real-time data
    func subscribeToTasks() {
        // We access the "tasks" collection ordered by date
        listenerRegistration = db.collection("tasks")
            .order(by: "creationDate", descending: true)
            .addSnapshotListener { [weak self] (querySnapshot, error) in
                guard let documents = querySnapshot?.documents else {
                    print("Error fetching documents: \(String(describing: error))")
                    return
                }
                
                // We map the Firestore documents to our Swift structure
                self?.tasks = documents.compactMap { document -> Task? in
                    try? document.data(as: Task.self)
                }
            }
    }
    
    // Stop listening to save resources
    func unsubscribe() {
        listenerRegistration?.remove()
    }
    
    // Add a new task to Firebase
    func addTask(title: String) {
        let newTask = Task(title: title, isCompleted: false, creationDate: Date())
        
        do {
            let _ = try db.collection("tasks").addDocument(from: newTask)
        } catch {
            print("Error saving the task: \(error.localizedDescription)")
        }
    }
    
    // Update the status of a task
    func toggleCompleted(task: Task) {
        guard let id = task.id else { return }
        
        db.collection("tasks").document(id).updateData([
            "isCompleted": !task.isCompleted
        ]) { error in
            if let error = error {
                print("Error updating task: \(error.localizedDescription)")
            }
        }
    }
}

5.3. The Interface in SwiftUI

Now, we connect our ViewModel with a SwiftUI view. Watch how the UI will automatically update thanks to @StateObject and @Published whenever someone adds a task from any device.

import SwiftUI

struct ContentView: View {
    @StateObject private var viewModel = TasksViewModel()
    @State private var newTaskTitle: String = ""
    
    var body: some View {
        NavigationView {
            VStack {
                // Form to add tasks
                HStack {
                    TextField("New task...", text: $newTaskTitle)
                        .textFieldStyle(RoundedBorderTextFieldStyle())
                    
                    Button(action: {
                        guard !newTaskTitle.isEmpty else { return }
                        viewModel.addTask(title: newTaskTitle)
                        newTaskTitle = "" // Clear the field
                    }) {
                        Image(systemName: "plus.circle.fill")
                            .font(.title2)
                    }
                }
                .padding()
                
                // Task list
                List(viewModel.tasks) { task in
                    HStack {
                        Text(task.title)
                            .strikethrough(task.isCompleted, color: .gray)
                            .foregroundColor(task.isCompleted ? .gray : .primary)
                        
                        Spacer()
                        
                        Image(systemName: task.isCompleted ? "checkmark.circle.fill" : "circle")
                            .foregroundColor(task.isCompleted ? .green : .gray)
                            .onTapGesture {
                                viewModel.toggleCompleted(task: task)
                            }
                    }
                }
            }
            .navigationTitle("My Tasks")
            .onAppear {
                viewModel.subscribeToTasks()
            }
            .onDisappear {
                viewModel.unsubscribe()
            }
        }
    }
}

6. Multiplatform Considerations (iOS, macOS, watchOS)

The Swift programming code we just wrote for the data layer and the model is 100% universal. You can create a macOS or watchOS target in Xcode and reuse the TasksViewModel.swift file without changing a single comma.

However, a good iOS Developer knows that the user experience (UX) differs:

  • macOS: Navigation based on NavigationView (or NavigationSplitView in iOS 16+) works wonderfully, but make sure to adjust margins (paddings) so it doesn’t feel like an oversized iPhone app on the Mac screen.
  • watchOS: Avoid long text fields (TextField) whenever possible. On watchOS, you might prefer tasks to only be markable as completed, delegating the creation of new tasks to the companion iPhone app, or using Voice Dictation. Also, remember that watchOS apps go into the background quickly; manage Firestore’s listenerRegistration carefully so as not to drain the Apple Watch’s battery.

Conclusion

Learning how to use Firebase with SwiftUI unlocks a completely new level in your career as an iOS Developer. In less than half an hour, we have set up a project in Xcode, integrated a professional backend SDK via Swift Package Manager, and created a to-do list application with real-time database synchronization.

Modern Swift programming and SwiftUI‘s declarative API make connecting to Firebase’s asynchronous data flows feel like magic. Whether you are building a quick prototype, an MVP for a startup, or a full enterprise application across iOS, macOS, and watchOS, the combination of these technologies is one of the most powerful tools at your disposal today.

If you have any questions about this article, please contact me and I will be happy to help you 🙂. You can contact me on my X profile or on my Instagram profile.

Leave a Reply

Your email address will not be published. Required fields are marked *

Previous Article

How to Add App Icon in Xcode

Next Article

How to install Xcode Command Line Tools

Related Posts