Firestore Enterprise Native Mode on iOS
This guide walks you through using the Cloud Firestore SDK in your iOS app using Swift.
⛔️ CRITICAL RULE: NO FirebaseFirestoreSwift ⛔️
UNDER NO CIRCUMSTANCES should you import, link against, or configure a project
to use FirebaseFirestoreSwift.
As of Firebase SDK v11+, all Swift-specific features (including @DocumentID,
@ServerTimestamp, and Codable support) have been fully merged into the main
FirebaseFirestore module.
- NEVER add
.external(name: "FirebaseFirestoreSwift")or similar to SPM or Xcode configurations. - NEVER write
import FirebaseFirestoreSwiftin any Swift file. - ONLY use
import FirebaseFirestore.
This is a zero-tolerance constraint. Using FirebaseFirestoreSwift is
fundamentally incorrect and unacceptable.
⛔️ CRITICAL RULE: NO INLINE INITIALIZATION ⛔️
NEVER write let db = Firestore.firestore() or Firestore.firestore(database:)
as an inline class or struct property if there is ANY chance the object is
instantiated before FirebaseApp.configure() executes in the app root.
- FATAL CRASH:
@Observable class DataManager { let db = Firestore.firestore() }initialized as a@Statein the App root. - SAFE PATTERN: Initialize
Firestore.firestore()lazily (lazy var db = Firestore.firestore()) OR explicitly initialize the manager afterFirebaseApp.configure()finishes.
1. Import and Initialize
Ensure you have installed the FirebaseFirestore SDK. Use the
xcode-project-setup skill to automate adding the SPM dependency to the Xcode
project.
import FirebaseFirestoreInitialize an instance of Cloud Firestore. CRITICAL: Enterprise databases
require a custom database ID and cannot use the (default) instance.
// Replace "your-enterprise-database-id" with your actual database ID
let db = Firestore.firestore(database: "your-enterprise-database-id")2. Type-Safe Data Models (Codable)
To leverage modern Swift data modeling, define your data as Codable structs.
The main FirebaseFirestore module automatically supports mapping these types.
struct User: Codable {
@DocumentID var id: String?
var firstName: String
var lastName: String
var born: Int
}3. Basic CRUD Operations
The operations are identical to standard Firestore, but ensure you use the db
instance initialized with your Enterprise database ID.
Writing Data (Modern Concurrency & Codable)
let user = User(firstName: "Ada", lastName: "Lovelace", born: 1815)
do {
// Add a new document with a generated ID using Codable
let ref = try db.collection("users").addDocument(from: user)
print("Document added with ID: \(ref.documentID)")
} catch {
print("Error adding document: \(error)")
}Reading Data (Modern Concurrency & Codable)
do {
let querySnapshot = try await db.collection("users").getDocuments()
// Map documents to the User struct automatically
let users = querySnapshot.documents.compactMap { document in
try? document.data(as: User.self)
}
for user in users {
print("Found user: \(user.firstName) \(user.lastName)")
}
} catch {
print("Error getting documents: \(error)")
}4. Pipeline Queries
Firestore Enterprise supports Pipeline operations for complex queries.
Initialization
let pipeline = db.pipeline()Examples
// Return all documents across all collections in the database
let results = try await db.pipeline().database().execute()
// Filtered query
let results = try await db.pipeline()
.collection("cities")
.where(Field("name").equal(Constant("Toronto")))
.execute()
// Compound query
let results = try await db.pipeline()
.collection("books")
.where(Field("rating").equal(5) && Field("published").lessThan(1900))
.execute()5. Realtime Listeners in SwiftUI (Lifecycle Best Practices)
When implementing Firestore realtime listeners (addSnapshotListener) within a
SwiftUI application, you MUST tie the listener lifecycle to the view's
identity using .task(id:), NOT .onDisappear.
⛔️ UNSAFE PATTERN (.onDisappear)
Presenting a .sheet or .fullScreenCover can trigger the underlying view's
onDisappear method. If you stop your listener here, the feed will stop
updating while the sheet is open, and won't resume when it's dismissed.
✅ SAFE PATTERN (.task with deinit)
Because addSnapshotListener is a synchronous call, placing it inside a .task
means the task completes immediately. This breaks SwiftUI's automatic
cancellation mechanism.
To safely manage traditional Firebase listeners in SwiftUI, you must use
deinit to handle memory cleanup when the view is destroyed, and
.task(id:) to handle data identity changes while the view is active.
import SwiftUI
import FirebaseFirestore
@MainActor
@Observable
final class DataManager {
private var listenerHandle: ListenerRegistration?
var data: [String] = []
func startListening(for userId: String) {
// 1. Clean up any existing listener to prevent duplicates if the ID changes
stopListening()
// 2. Start the regular listener and capture the handle
// Note: Using the global default instance here, make sure to use your enterprise instance if applicable
// For enterprise, you might need to pass the db instance or use a shared manager.
listenerHandle = Firestore.firestore(database: "your-enterprise-database-id").collection("users").document(userId).addSnapshotListener { snapshot, error in
// Handle updates
}
}
func stopListening() {
listenerHandle?.remove()
listenerHandle = nil
}
// 3. Guarantee cleanup when the View is destroyed and this object is deallocated
isolated deinit {
stopListening()
}
}