Ćwiczenie dotyczące Firebase na iOS: FriendlyChat

1. Przegląd

Witamy w ćwiczeniu FriendlyChat na iOS. Z tego ćwiczenia dowiesz się, jak utworzyć w pełni funkcjonalną aplikację do czatu w czasie rzeczywistym na iOS przy użyciu Firebase i nowoczesnych funkcji języka Swift, takich jak SwiftUI, Swift Concurrency i framework Observation.

Czego się nauczysz

  • Zarządzanie stanem za pomocą nowoczesnej platformy Observation (@Observable).
  • Uwierzytelnianie użytkowników za pomocą Uwierzytelniania Firebase.
  • Synchronizowanie wiadomości na czacie w czasie rzeczywistym za pomocą Cloud Firestore.
  • Przechowywanie i udostępnianie obrazów za pomocą Cloud Storage dla Firebase.
  • Dostęp do interfejsów API Firebase za pomocą nowoczesnych funkcji Swift Concurrency.

Czego potrzebujesz

  • Najnowsza wersja Xcode
  • Symulator iOS lub fizyczne urządzenie testowe
  • Konto Google do tworzenia projektów w Firebase i zarządzania nimi

2. Pobieranie przykładowego kodu

Sklonuj repozytorium GitHub z wiersza poleceń:

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

Repozytorium codelab-friendlychat-ios zawiera kilka przykładowych projektów:

  • ios-starter/swift-starter – początkowy projekt Xcode zawierający szkielet układu SwiftUI i komentarze TODO, w których zaimplementujesz funkcje aplikacji za pomocą Firebase.
  • ios/swift – ukończony projekt Xcode z gotową przykładową aplikacją.

3. Tworzenie aplikacji startowej

Utwórz i uruchom aplikację startową, aby zapoznać się z interfejsem:

  1. W Finderze lub Terminalu przejdź do katalogu ios-starter/swift-starter.
  2. Kliknij dwukrotnie FriendlyChatSwift.xcodeproj , aby otworzyć projekt w Xcode.
  3. Zwróć uwagę, że Xcode automatycznie rozwiązuje zależności menedżera pakietów Swift w tle (pobierając firebase-ios-sdk w wersji 12 lub nowszej z GitHub).
  4. Wybierz symulator iOS (np. iPhone 17 Pro) i kliknij przycisk Uruchom (cmd+R).

Po kilku sekundach pojawi się ekran logowania FriendlyChat. Na tym etapie przyciski logowania i kanały wiadomości są obiektami zastępczymi. W kolejnych krokach połączysz Firebase z aplikacją.

4. Konfigurowanie projektu w Firebase

Tworzenie nowego projektu w Firebase

  1. Zaloguj się w konsoli Firebase za pomocą konta Google.
  2. Kliknij przycisk, aby utworzyć nowy projekt, a następnie wpisz jego nazwę (np. FriendlyChat).
  3. Kliknij Dalej.
  4. Jeśli pojawi się prośba, zapoznaj się z warunkami korzystania z Firebase i zaakceptuj je, a następnie kliknij Dalej.
  5. (Opcjonalnie) Włącz pomoc AI w konsoli Firebase (nazywaną „Gemini w Firebase”).
  6. W tym ćwiczeniu nie potrzebujesz Google Analytics, więc wyłącz tę opcję.
  7. Kliknij Utwórz projekt, poczekaj na jego utworzenie, a następnie kliknij Dalej.

Uaktualnianie abonamentu Firebase

Aby korzystać z Cloud Storage dla Firebase, Twój projekt w Firebase musi mieć abonament z płatnością według wykorzystania (Blaze), co oznacza, że jest połączony z kontem rozliczeniowym Cloud.

Aby przenieść projekt na abonament Blaze, wykonaj te czynności:

  1. W konsoli Firebase kliknij, aby uaktualnić abonament.
  2. Wybierz abonament Blaze. Postępuj zgodnie z instrukcjami wyświetlanymi na ekranie, aby połączyć konto rozliczeniowe Cloud z projektem.
    Jeśli w ramach tej aktualizacji musisz utworzyć konto rozliczeniowe Cloud, może być konieczne powrócenie do procesu aktualizacji w konsoli Firebase, aby go dokończyć.

Łączenie aplikacji na iOS

  1. Na ekranie „Opis projektu” kliknij ikonę iOS, aby uruchomić proces konfiguracji aplikacji.
  2. Wpisz identyfikator pakietu:
    com.google.firebase.codelab.FriendlyChatSwift
    
  3. Kliknij Zarejestruj aplikację.

Dodawanie pliku GoogleService-Info.plist do projektu Xcode

  1. Kliknij Pobierz GoogleService-Info.plist , aby zapisać plik konfiguracyjny.
  2. W Xcode przeciągnij pobrany plik GoogleService-Info.plist do głównej grupy FriendlyChatSwift w nawigatorze projektu.
  3. Gdy pojawi się prośba, upewnij się, że zaznaczone jest pole Skopiuj elementy w razie potrzeby , a wybrany jest cel FriendlyChatSwift , a następnie kliknij Zakończ.

Konfigurowanie Firebase w FriendlyChatSwiftApp.swift

Na koniec zainicjuj Firebase za pomocą konfiguracji projektu, gdy uruchomi się aplikacja SwiftUI. Otwórz plik FriendlyChatSwiftApp.swift i zastąp obiekty zastępcze TODO elementami import FirebaseCore i FirebaseApp.configure():

import SwiftUI
import FirebaseCore

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

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

5. Uwierzytelnianie użytkowników

Konfigurowanie uwierzytelniania w konsoli Firebase

Zanim zaczniesz uwierzytelniać użytkowników, musisz najpierw włączyć wybranego dostawcę uwierzytelniania w konsoli Firebase. W tym samouczku używamy uwierzytelniania za pomocą adresu e-mail i hasła:

  1. W konsoli Firebase otwórz Zabezpieczenia > Uwierzytelnianie, a następnie kliknij Rozpocznij.
  2. Kliknij kartę Metoda logowania.
  3. Kliknij Adres e-mail/hasło, kliknij przełącznik Włącz i kliknij Zapisz.

Zabezpieczanie Cloud Firestore za pomocą reguł uwierzytelniania

Następnie wymagaj, aby użytkownicy byli uwierzytelnieni przed odczytaniem lub zapisaniem wiadomości:

  1. W konsoli Firebase otwórz Bazy danych i pamięć masowa > Baza danych Firestore, a następnie kliknij Utwórz bazę danych.
  2. Wybierz lokalizację i kliknij Dalej.
  3. Kliknij Rozpocznij w trybie testowym i Utwórz.
  4. Kliknij kartę Reguły i zaktualizuj reguły zabezpieczeń do:
    rules_version = '2';
    
    service cloud.firestore {
      match /databases/{database}/documents {
        match /messages/{messageId} {
          allow read, write: if request.auth != null;
        }
      }
    }
    
    Te reguły umożliwiają każdemu uwierzytelnionemu użytkownikowi odczytywanie i zapisywanie wiadomości w bazie danych, co jest odpowiednie do celów edukacyjnych. Więcej informacji o regułach zabezpieczeń znajdziesz w dokumentacji reguł zabezpieczeń.
  5. Kliknij Opublikuj.

Obserwowanie stanu uwierzytelniania w UserViewModel.swift

W SwiftUI obserwowanie stanu uwierzytelniania Firebase w sposób reaktywny zapewnia płynne przełączanie interfejsu między ekranem logowania a interfejsem czatu.

Otwórz plik ViewModels/UserViewModel.swift i zastąp TODO w init() , aby obserwować zmiany stanu uwierzytelniania za pomocą AsyncSequence authStateChanges:

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

Implementowanie logowania się, rejestracji i wylogowywania za pomocą Swift Concurrency

W pliku UserViewModel.swift zastąp pozostałe komentarze TODO nowoczesnymi metodami uwierzytelniania 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)
    }
  }

Testowanie uwierzytelniania użytkowników

  1. W Xcode kliknij przycisk Uruchom.
  2. W symulatorze aplikacji kliknij Zarejestruj się, wpisz nazwę wyświetlaną, adres e-mail i hasło, a następnie kliknij ZAREJESTRUJ SIĘ.
  3. Powinno nastąpić automatyczne uwierzytelnienie i przejście do pustego ekranu wiadomości.

6. Odczytywanie z Cloud Firestore

Dodawanie przykładowych wiadomości w konsoli Firebase

Wypełnij bazę danych przykładowymi wiadomościami:

  1. W konsoli Firebase otwórz Baza danych Firestore i kliknij kartę Dane.
  2. Kliknij Rozpocznij kolekcję.
  3. W polu Identyfikator kolekcji wpisz messages, a następnie kliknij Dalej.
  4. Pozostaw ustawienie Identyfikator dokumentu na automatyczne generowanie (lub kliknij Automatyczny identyfikator).
  5. Dodaj do dokumentu te pola:
    • text (typ: string, wartość: Hello)
    • displayName (typ: string, wartość: anonymous)
  6. Kliknij Zapisz.
  7. Opcjonalnie kliknij Dodaj dokument , aby dodać więcej przykładowych wiadomości.

Synchronizowanie wiadomości w FriendlyMessageViewModel.swift

W SwiftUI model @Observable steruje deklaratywnymi aktualizacjami interfejsu. Otwórz plik ViewModels/FriendlyMessageViewModel.swift i zaimplementuj startListening() oraz stopListening() za pomocą 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()
  }

Jak SwiftUI renderuje i automatycznie przewija wiadomości

W pliku Views/ContentView.swift zobacz, jak ScrollViewReader i LazyVStack renderują dymki czatu i automatycznie przewijają do najnowszej wiadomości, gdy tylko zmieni się wartość messages.count:

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)
      }
    }
  }
}

Dzięki powiązaniu stanu bazy danych bezpośrednio ze stanem interfejsu za pomocą modelu widoku SwiftUI umożliwia kodowi aplikacji czyste mapowanie przejść na animacje. W następnym kroku zapiszesz dane w bazie danych z aplikacji, aby zobaczyć tę animację w działaniu.

7. Wysyłanie wiadomości

Implementowanie sendMessage za pomocą Swift Concurrency

Gdy dodasz dokument do kolekcji za pomocą addDocument(), Cloud Firestore wygeneruje unikalny identyfikator każdej wiadomości na czacie.

W pliku ViewModels/FriendlyMessageViewModel.swift zastąp TODO w sendMessage(text:imageUrl:) metodą 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)
  }

Testowanie wysyłania wiadomości

  1. W Xcode kliknij przycisk Uruchom.
  2. Zaloguj się na konto.
  3. Wpisz wiadomość w dolnym polu tekstowym i kliknij ikonę Wyślij.
  4. Zobacz, jak wiadomość natychmiast pojawia się w symulatorze.

8. Przechowywanie i odbieranie obrazów

Konfigurowanie Cloud Storage dla Firebase

  1. W konsoli Firebase otwórz Bazy danych i pamięć masowa > Pamięć masowa, a następnie kliknij Rozpocznij.
  2. Kliknij Rozpocznij w trybie testowym i Dalej.
  3. Zaakceptuj domyślną lokalizację pamięci masowej i kliknij Gotowe.

Natywne wybieranie obrazów za pomocą SwiftUI PhotosPicker

Nowoczesny SwiftUI (iOS 16 lub nowszy) umożliwia natywne wybieranie zdjęć za pomocą PhotosPicker z import PhotosUI – nie są wymagane żadne mostki delegatów UIKit ani otoki UIImagePickerController.

W pliku Views/FooterView.swift zobacz, jak PhotosPicker wiąże wybrane obrazy:

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)
    }
  }
}

Implementowanie przesyłania obrazów

Gdy wybierzesz zdjęcie, prześlij dane obrazu do Cloud Storage i zapisz publiczny adres URL pobierania w Cloud Firestore.

W pliku Views/FooterView.swift zastąp TODO w 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)")
    }
  }

Udostępnianie obrazów za pomocą Cloud Storage

W pliku Views/FriendlyMessageImageView.swift adresy URL obrazów są wczytywane asynchronicznie za pomocą Swift Concurrency i pakietu SDK Cloud Storage:

  • Standardowe adresy URL pobierania HTTP/HTTPS są pobierane asynchronicznie za pomocą URLSession.
  • Adresy URL Google Cloud Storage (np. gs://...) są pobierane za pomocą Storage.storage().reference(forURL:).

Testowanie wiadomości z obrazami

  1. W Xcode kliknij przycisk Uruchom.
  2. Zaloguj się na konto.
  3. Kliknij ikonę Zdjęcie, wybierz obraz z biblioteki i zobacz, jak jest przesyłany i pojawia się na czacie.

9. Podsumowanie

Udało Ci się utworzyć aplikację do czatu w czasie rzeczywistym na iOS przy użyciu Swift i Firebase.

Czego się nauczysz

  • Integracja menedżera pakietów Swift (SPM) z Xcode na potrzeby firebase-ios-sdk.
  • Deklaratywne układy SwiftUI , PhotosPicker i automatyczne przewijanie ScrollViewReader.
  • Swift Concurrency (async/await) w uwierzytelnianiu, Cloud Firestore i Cloud Storage.
  • Uwierzytelnianie za pomocą adresu e-mail i hasła oraz uwierzytelnianie anonimowe z reaktywną obserwacją stanu.
  • Cloud Firestore – nasłuchiwanie migawek w czasie rzeczywistym i automatyczne zapisywanie identyfikatorów.
  • Cloud Storage dla Firebase – przesyłanie plików binarnych i publiczne adresy URL pobierania.

Więcej informacji