← Files FirebaseARCHIVED FILE
skills/firebase-firestore/references/standard/ios_setup.md
5.25 KB · Sep 30, 2026 · 23:01 UTC
# Firebase Firestore iOS Setup Guide
# ⛔️ 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 FirebaseFirestoreSwift` in 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()` 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 `@State` in the App root.
- **SAFE PATTERN:** Initialize `Firestore.firestore()` lazily
(`lazy var db = Firestore.firestore()`) OR explicitly initialize the manager
*after* `FirebaseApp.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.
```swift
import FirebaseFirestore
```
Initialize an instance of Cloud Firestore:
```swift
let db = Firestore.firestore()
```
## 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.
```swift
struct User: Codable {
@DocumentID var id: String?
var firstName: String
var lastName: String
var born: Int
}
```
## 3. Writing Data (Modern Concurrency & Codable)
Using `async/await` and `Codable` ensures type safety and avoids callback hell.
```swift
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)")
}
```
## 4. Reading Data (Modern Concurrency & Codable)
```swift
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)")
}
```
## 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.
```swift
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
listenerHandle = Firestore.firestore().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()
}
}
```
Then, in your SwiftUI View, trigger the listener using `.task(id:)`.
```swift
struct MyView: View {
@State private var manager = DataManager()
@Environment(AuthManager.self) var authManager
var body: some View {
List(manager.data, id: \.self) { item in
Text(item)
}
// .task(id:) automatically re-runs if the userId changes.
// The view model handles stopping the old listener and starting the new one.
.task(id: authManager.userId) {
if let userId = authManager.userId {
manager.startListening(for: userId)
} else {
manager.stopListening()
}
}
}
}
```
SHA-256: 0e327d20a725bfe2dc151b08182540b1f4b208e0119b44b85f680085d628a071