Codelab di Firebase per iOS: FriendlyChat

1. Panoramica

Ti diamo il benvenuto nel codelab FriendlyChat per iOS. In questo codelab imparerai a creare un'applicazione di chat in tempo reale per iOS completa di tutte le funzionalità utilizzando Firebase e le moderne funzionalità di Swift come SwiftUI, Swift Concurrency e il framework Observation.

Obiettivi didattici

  • Gestire lo stato con il moderno framework Observation (@Observable).
  • Autenticare gli utenti utilizzando Firebase Authentication.
  • Sincronizzare i messaggi di chat in tempo reale utilizzando Cloud Firestore.
  • Archiviare e pubblicare immagini utilizzando Cloud Storage for Firebase.
  • Accedere alle API Firebase utilizzando le moderne funzionalità di Swift Concurrency.

Che cosa ti serve

  • L'ultima versione di Xcode
  • Un simulatore iOS o un dispositivo di test fisico
  • Un Account Google per creare e gestire il progetto Firebase

2. Recupera il codice campione

Clona il repository GitHub dalla riga di comando:

$ git clone https://github.com/firebase/codelab-friendlychat-ios

Il repository codelab-friendlychat-ios contiene diversi progetti di esempio:

  • ios-starter/swift-starter : il progetto Xcode iniziale contenente lo scheletro del layout SwiftUI e i commenti TODO in cui implementerai le funzionalità dell'app con Firebase.
  • ios/swift : il progetto Xcode completato con l'applicazione di esempio finita.

3. Crea l'app di base

Crea ed esegui l'applicazione iniziale per familiarizzare con l'interfaccia utente:

  1. In Finder o Terminale, vai alla directory ios-starter/swift-starter.
  2. Fai doppio clic su FriendlyChatSwift.xcodeproj per aprire il progetto in Xcode.
  3. Tieni presente che Xcode risolve automaticamente le dipendenze di Swift Package Manager in background (scaricando firebase-ios-sdk v12+ da GitHub).
  4. Seleziona un simulatore iOS (ad esempio, iPhone 17 Pro) e fai clic sul pulsante Esegui (cmd+R).

Dopo alcuni secondi, verrà visualizzata la schermata di accesso di FriendlyChat. A questo punto, i pulsanti di accesso e i feed di messaggi sono segnaposto. Nei passaggi successivi collegherai Firebase all'app.

4. Configura un progetto Firebase

Crea un nuovo progetto Firebase

  1. Accedi alla console Firebase utilizzando il tuo Account Google.
  2. Fai clic sul pulsante per creare un nuovo progetto, quindi inserisci un nome del progetto (ad esempio, FriendlyChat).
  3. Fai clic su Continua.
  4. (Facoltativo) Se ti viene richiesto, esamina e accetta i termini di Firebase, quindi fai clic su Continua.
  5. (Facoltativo) Abilita l'assistenza AI nella console Firebase (denominata "Gemini in Firebase").
  6. Per questo codelab, non hai bisogno di Google Analytics, quindi disattiva l'opzione Google Analytics.
  7. Fai clic su Crea progetto, attendi il provisioning del progetto e poi fai clic su Continua.

Esegui l'upgrade del piano tariffario di Firebase

Per utilizzare Cloud Storage for Firebase, il tuo progetto Firebase deve essere incluso nel piano tariffario con pagamento a consumo (Blaze), il che significa che è collegato a un account di fatturazione Cloud.

Per eseguire l'upgrade del progetto al piano Blaze:

  1. Nella console Firebase, seleziona l'opzione per eseguire l'upgrade del piano.
  2. Seleziona il piano Blaze. Segui le istruzioni sullo schermo per collegare un account di fatturazione Cloud al tuo progetto.
    Se hai dovuto creare un account di fatturazione Cloud nell'ambito di questo upgrade, potresti dover tornare al flusso di upgrade nella console Firebase per completare l'upgrade.

Collega l'app per iOS

  1. Nella schermata Panoramica del progetto, fai clic sull'icona iOS per avviare il flusso di lavoro di configurazione dell'app.
  2. Inserisci l'ID pacchetto:
    com.google.firebase.codelab.FriendlyChatSwift
    
  3. Fai clic su Registra app.

Aggiungi GoogleService-Info.plist al progetto Xcode

  1. Fai clic su Scarica GoogleService-Info.plist per salvare il file di configurazione.
  2. In Xcode, trascina il file GoogleService-Info.plist scaricato nel gruppo principale FriendlyChatSwift nel navigatore del progetto.
  3. Quando ti viene richiesto, assicurati che l'opzione Copia elementi se necessario sia selezionata e che la destinazione FriendlyChatSwift sia selezionata, quindi fai clic su Fine.

Configura Firebase in FriendlyChatSwiftApp.swift

Infine, inizializza Firebase con la configurazione del progetto all'avvio dell'applicazione SwiftUI. Apri FriendlyChatSwiftApp.swift e sostituisci i segnaposto TODO con import FirebaseCore e FirebaseApp.configure():

import SwiftUI
import FirebaseCore

@main
struct FriendlyChatSwiftApp: App {
  init() {
    FirebaseApp.configure()
  }

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

5. Autenticazione degli utenti

Configura l'autenticazione nella console Firebase

Prima di poter autenticare gli utenti, devi prima abilitare il provider di autenticazione che preferisci nella console Firebase. Questo tutorial utilizza l'autenticazione email/password:

  1. Nella console Firebase, vai a Sicurezza > Autenticazione e fai clic su Inizia.
  2. Seleziona la scheda Metodo di accesso.
  3. Fai clic su Email/Password, fai clic sul pulsante di attivazione/disattivazione Abilita e fai clic su Salva.

Proteggi Cloud Firestore con le regole di autenticazione

Poi, richiedi agli utenti di autenticarsi prima di leggere o scrivere messaggi:

  1. Nella console Firebase, vai a Database e spazio di archiviazione > Database Firestore e fai clic su Crea database.
  2. Seleziona una località e fai clic su Avanti.
  3. Seleziona Inizia in modalità di test e fai clic su Crea.
  4. Seleziona la scheda Regole e aggiorna le regole di sicurezza a:
    rules_version = '2';
    
    service cloud.firestore {
      match /databases/{database}/documents {
        match /messages/{messageId} {
          allow read, write: if request.auth != null;
        }
      }
    }
    
    Queste regole consentono a qualsiasi utente autenticato di leggere o scrivere messaggi nel database, il che è adatto per scopi di apprendimento. Scopri di più sulle regole di sicurezza nella documentazione relativa.
  5. Fai clic su Pubblica.

Osserva lo stato di autenticazione in UserViewModel.swift

In SwiftUI, l'osservazione reattiva dello stato di Firebase Auth garantisce che l'interfaccia utente passi senza problemi dalla schermata di accesso all'interfaccia di chat.

Apri ViewModels/UserViewModel.swift e sostituisci il TODO in init() per osservare le modifiche dello stato di autenticazione utilizzando AsyncSequence authStateChanges:

  init() {
    authTask = Task {
      for await user in Auth.auth().authStateChanges {
        self.user = user
      }
    }
  }

Implementa l'accesso, la registrazione e la disconnessione con Swift Concurrency

In UserViewModel.swift, sostituisci i commenti TODO rimanenti con i moderni metodi di autenticazione async/await:

  func signIn(email: String, password: String) async {
    errorMessage = nil
    if email.isEmpty || password.isEmpty {
      showError("Please enter both email and password.")
      return
    }
    do {
      try await Auth.auth().signIn(withEmail: email, password: password)
    } catch {
      showError(error.localizedDescription)
    }
  }

  func signUp(email: String, password: String, displayName: String) async {
    errorMessage = nil
    if email.isEmpty || password.isEmpty {
      showError("Please enter both email and password.")
      return
    }
    do {
      let result =
        try await Auth.auth().createUser(withEmail: email, password: password)
      let changeRequest = result.user.createProfileChangeRequest()
      changeRequest.displayName = displayName.isEmpty ? email : displayName
      try await changeRequest.commitChanges()
      self.user = Auth.auth().currentUser
    } catch {
      showError(error.localizedDescription)
    }
  }

  func updateDisplayName(_ displayName: String) async {
    guard let currentUser = Auth.auth().currentUser else { return }
    do {
      let changeRequest = currentUser.createProfileChangeRequest()
      changeRequest.displayName = displayName
      try await changeRequest.commitChanges()
      self.user = Auth.auth().currentUser
    } catch {
      showError(error.localizedDescription)
    }
  }

  func signOut() {
    do {
      try Auth.auth().signOut()
    } catch {
      showError(error.localizedDescription)
    }
  }

Testa l'autenticazione utente

  1. Fai clic sul pulsante Esegui in Xcode.
  2. Nel simulatore dell'app, fai clic su Registrati , inserisci un nome visualizzato, un indirizzo email e una password e tocca REGISTRATI.
  3. Dovresti essere autenticato automaticamente e passare alla schermata di messaggistica vuota.

6. Leggi da Cloud Firestore

Aggiungi messaggi di esempio nella console Firebase

Compila il database con messaggi di esempio:

  1. Nella console Firebase, vai a Database Firestore e seleziona la scheda Dati.
  2. Fai clic su Avvia raccolta.
  3. Inserisci messages per l'ID raccolta, quindi fai clic su Avanti.
  4. Lascia l'ID documento impostato su generazione automatica (o fai clic su ID automatico).
  5. Aggiungi i seguenti campi al documento:
    • text (tipo: string, valore: Hello)
    • displayName (tipo: string, valore: anonymous)
  6. Fai clic su Salva.
  7. (Facoltativo) Fai clic su Aggiungi documento per aggiungere altri messaggi di esempio.

Sincronizza i messaggi in FriendlyMessageViewModel.swift

In SwiftUI, un modello @Observable guida gli aggiornamenti dell'interfaccia utente dichiarativa. Apri ViewModels/FriendlyMessageViewModel.swift e implementa startListening() e stopListening() utilizzando AsyncSequence snapshots:

  func startListening() {
    stopListening()
    listenerTask = Task {
      let db = Firestore.firestore()
      do {
        for try await snapshot in db.collection("messages").snapshots {
          self.messages = snapshot.documents.compactMap { document in
            try? document.data(as: FriendlyMessage.self)
          }
        }
      } catch {
        print("Error listening for messages: \(error)")
      }
    }
  }

  func stopListening() {
    listenerTask?.cancel()
    listenerTask = nil
    messages.removeAll()
  }

Come SwiftUI esegue il rendering e lo scorrimento automatico dei messaggi

In Views/ContentView.swift, nota come ScrollViewReader e LazyVStack eseguono il rendering delle bolle di chat e scorrono automaticamente al messaggio più recente ogni volta che messages.count cambia:

ScrollViewReader { scrollViewReader in
  ScrollView {
    LazyVStack(spacing: 12) {
      ForEach(messageViewModel.messages) { message in
        FriendlyMessageView(friendlyMessage: message)
          .id(message.id)
      }
    }
    .padding(.horizontal)
    .onChange(of: messageViewModel.messages.count) { _, count in
      guard count > 0,
            let lastId = messageViewModel.messages.last?.id else { return }
      withAnimation(.easeInOut) {
        scrollViewReader.scrollTo(lastId, anchor: .bottom)
      }
    }
  }
}

Collegando lo stato del database direttamente allo stato dell'interfaccia utente tramite il modello di visualizzazione, SwiftUI consente al codice dell'app di mappare in modo pulito le transizioni alle animazioni. Nel passaggio successivo scriverai i dati nel database dall'app per osservare questa animazione in azione.

7. Invia messaggi

Implementa sendMessage con Swift Concurrency

Quando aggiungi un documento a una raccolta utilizzando addDocument(), Cloud Firestore genera un ID univoco per ogni messaggio chat.

In ViewModels/FriendlyMessageViewModel.swift, sostituisci il TODO in sendMessage(text:imageUrl:) con un metodo async throws:

  func sendMessage(text: String?, imageUrl: String?) async throws {
    guard let currentUser = Auth.auth().currentUser else { return }
    let message = FriendlyMessage(
      text: text,
      displayName: currentUser.displayName ?? currentUser.email ?? "Anonymous",
      imageUrl: imageUrl,
      userId: currentUser.uid
    )
    let db = Firestore.firestore()
    _ = try db.collection("messages").addDocument(from: message)
  }

Prova a inviare messaggi

  1. Fai clic sul pulsante Esegui in Xcode.
  2. Accedi al tuo account.
  3. Digita un messaggio nel campo di testo in basso e tocca l'icona Invia.
  4. Guarda il messaggio apparire immediatamente nel simulatore.

8. Archivia e ricevi immagini

Configura Cloud Storage for Firebase

  1. Nella console Firebase, vai a Database e spazio di archiviazione > Storage e fai clic su Inizia.
  2. Seleziona Inizia in modalità di test e fai clic su Avanti.
  3. Accetta la località di archiviazione predefinita e fai clic su Fine.

Selezione di immagini nativa con PhotosPicker di SwiftUI

La versione moderna di SwiftUI (iOS 16+) fornisce la selezione di foto nativa tramite PhotosPicker da import PhotosUI, senza la necessità di bridge delegati UIKit o wrapper UIImagePickerController.

In Views/FooterView.swift, osserva come PhotosPicker associa le immagini selezionate:

PhotosPicker(selection: $selectedItem, matching: .images) {
  Image(systemName: "photo.on.rectangle.angled")
    .font(.system(size: 26))
    .foregroundStyle(.blue)
    .accessibilityLabel("Select photo")
}
.onChange(of: selectedItem) { _, newItem in
  Task {
    if let data = try? await newItem?.loadTransferable(type: Data.self) {
      await uploadAndSendImage(data: data)
    }
  }
}

Implementa il caricamento delle immagini

Quando viene selezionata una foto, carica i dati dell'immagine in Cloud Storage e salva l'URL di download pubblico in Cloud Firestore.

In Views/FooterView.swift, sostituisci il TODO in uploadAndSendImage(data:):

  private func uploadAndSendImage(data: Data) async {
    guard let uid = Auth.auth().currentUser?.uid else { return }
    isUploading = true
    defer {
      isUploading = false
      selectedItem = nil
    }
    do {
      let filename = "\(uid)/\(UUID().uuidString).jpg"
      let storageRef =
        Storage.storage().reference().child("images").child(filename)
      let metadata = StorageMetadata()
      metadata.contentType = "image/jpeg"

      _ = try await storageRef.putDataAsync(data, metadata: metadata)
      let downloadURL = try await storageRef.downloadURL()
      try await viewModel.sendMessage(
        text: nil, imageUrl: downloadURL.absoluteString)
    } catch {
      print("Error uploading image: \(error.localizedDescription)")
    }
  }

Condivisione di immagini con Cloud Storage

In Views/FriendlyMessageImageView.swift, gli URL delle immagini vengono caricati in modo asincrono utilizzando Swift Concurrency e l'SDK Cloud Storage:

  • Gli URL di download HTTP/HTTPS standard vengono recuperati in modo asincrono tramite URLSession.
  • Gli URL di Google Cloud Storage (ad esempio, gs://...) vengono recuperati utilizzando Storage.storage().reference(forURL:).

Prova i messaggi con immagini

  1. Fai clic sul pulsante Esegui in Xcode.
  2. Accedi al tuo account.
  3. Tocca l'icona Foto, scegli un'immagine dalla libreria e guarda il caricamento e la visualizzazione nella chat.

9. Conclusione

Complimenti, hai creato un'applicazione di chat in tempo reale per iOS utilizzando Swift e Firebase.

Che cosa hai imparato

  • Integrazione di Swift Package Manager (SPM) Xcode per firebase-ios-sdk.
  • Layout dichiarativi SwiftUI , PhotosPicker e scorrimento automatico ScrollViewReader.
  • Swift Concurrency (async/await) in Autenticazione, Cloud Firestore e Cloud Storage.
  • Autenticazione email/password e anonima con osservazione reattiva dello stato.
  • Ascolto degli snapshot in tempo reale di Cloud Firestore e scritture automatiche degli ID.
  • Caricamenti binari e URL di download pubblici di Cloud Storage for Firebase.

Scopri di più