Практическое занятие по Firebase iOS: FriendlyChat

1. Обзор

Добро пожаловать на практическое занятие по FriendlyChat для iOS! На этом занятии вы научитесь создавать полнофункциональное iOS-приложение для чата в реальном времени, используя Firebase и современные возможности Swift, такие как SwiftUI, Swift Concurrency и фреймворк Observation.

Что вы узнаете

  • Управляйте состоянием с помощью современной платформы для наблюдения ( @Observable ) .
  • Аутентификация пользователей осуществляется с помощью Firebase Authentication .
  • Синхронизируйте сообщения чата в режиме реального времени с помощью Cloud Firestore .
  • Храните и предоставляйте изображения с помощью Cloud Storage for Firebase .
  • Получайте доступ к API Firebase, используя современные возможности параллельного программирования Swift .

Что вам понадобится

  • Последняя версия Xcode
  • Симулятор iOS или физическое тестовое устройство
  • Учетная запись Google для создания и управления вашим проектом Firebase.

2. Получите пример кода.

Клонируйте репозиторий GitHub из командной строки:

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

В репозитории codelab-friendlychat-ios содержится несколько примеров проектов:

  • ios-starter/swift-starter — стартовый проект Xcode, содержащий каркас макета SwiftUI и комментарии TODO , где вы будете реализовывать функции приложения с помощью Firebase.
  • ios/swift — Завершённый проект Xcode с готовым примером приложения.

3. Создайте стартовое приложение.

Соберите и запустите стартовое приложение, чтобы ознакомиться с пользовательским интерфейсом:

  1. В Finder или Терминале перейдите в каталог ios-starter/swift-starter .
  2. Чтобы открыть проект в Xcode, дважды щелкните файл FriendlyChatSwift.xcodeproj .
  3. Обратите внимание, что Xcode автоматически разрешает зависимости Swift Package Manager в фоновом режиме (загружая firebase-ios-sdk версии 12+ с GitHub).
  4. Выберите симулятор iOS (например, iPhone 17 Pro ) и нажмите кнопку «Запустить» (cmd+R).

Через несколько секунд появится экран входа в FriendlyChat. На этом этапе кнопки входа и лента сообщений являются лишь заполнителями. Подключение Firebase к приложению будет выполнено на следующих шагах.

4. Создайте проект Firebase.

Создайте новый проект Firebase.

  1. Войдите в консоль Firebase, используя свою учетную запись Google.
  2. Нажмите кнопку, чтобы создать новый проект, а затем введите название проекта (например, FriendlyChat ).
  3. Нажмите «Продолжить» .
  4. Если появится запрос, ознакомьтесь с условиями использования Firebase и примите их, после чего нажмите «Продолжить» .
  5. (Необязательно) Включите помощь ИИ в консоли Firebase (в Firebase она называется "Gemini").
  6. Для этого практического занятия вам не понадобится Google Analytics, поэтому отключите эту опцию.
  7. Нажмите «Создать проект» , дождитесь завершения подготовки проекта, а затем нажмите «Продолжить» .

Обновите свой тарифный план Firebase.

Для использования Cloud Storage for Firebase ваш проект Firebase должен быть подключен к тарифному плану с оплатой по мере использования (Blaze) , то есть он должен быть связан с учетной записью Cloud Billing .

Чтобы перейти на тарифный план Blaze для вашего проекта, выполните следующие шаги:

  1. В консоли Firebase выберите вариант обновления вашего тарифного плана .
  2. Выберите тарифный план Blaze. Следуйте инструкциям на экране, чтобы связать учетную запись Cloud Billing с вашим проектом.
    Если в рамках этого обновления вам потребовалось создать учетную запись Cloud Billing, возможно, вам нужно будет вернуться к процессу обновления в консоли Firebase, чтобы завершить обновление.

Подключите ваше iOS-приложение

  1. На экране «Обзор проекта» щелкните значок iOS , чтобы запустить процесс настройки приложения.
  2. Введите идентификатор пакета:
    com.google.firebase.codelab.FriendlyChatSwift
    
  3. Нажмите «Зарегистрировать приложение» .

Добавьте файл GoogleService-Info.plist в свой проект Xcode.

  1. Нажмите кнопку «Скачать GoogleService-Info.plist» , чтобы сохранить файл конфигурации.
  2. В Xcode перетащите загруженный файл GoogleService-Info.plist в основную группу FriendlyChatSwift в навигаторе проекта.
  3. При появлении запроса убедитесь, что установлен флажок «Копировать элементы при необходимости» и выбран целевой объект FriendlyChatSwift , затем нажмите «Готово» .

Настройте Firebase в файле FriendlyChatSwiftApp.swift

Наконец, при запуске приложения SwiftUI инициализируйте Firebase с помощью конфигурации вашего проекта. Откройте файл FriendlyChatSwiftApp.swift и замените заполнители TODO на import FirebaseCore и FirebaseApp.configure() :

import SwiftUI
import FirebaseCore

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

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

5. Аутентификация пользователей

Настройте аутентификацию в консоли Firebase.

Прежде чем аутентифицировать пользователей, необходимо сначала включить выбранный вами поставщик аутентификации в консоли Firebase. В этом руководстве используется аутентификация по электронной почте и паролю:

  1. В консоли Firebase перейдите в раздел Безопасность > Аутентификация , затем нажмите «Начать» .
  2. Выберите вкладку «Способ входа» .
  3. Нажмите «Электронная почта/Пароль» , затем переключатель «Включить » и нажмите «Сохранить» .

Защитите Cloud Firestore с помощью правил аутентификации.

Далее, необходимо требовать от пользователей аутентификации перед чтением или записью сообщений:

  1. В консоли Firebase перейдите в раздел «Базы данных и хранилище» > «База данных Firestore» , затем нажмите «Создать базу данных» .
  2. Выберите местоположение и нажмите «Далее» .
  3. Выберите «Начать в тестовом режиме» и нажмите «Создать» .
  4. Выберите вкладку «Правила» и обновите правила безопасности следующим образом:
    rules_version = '2';
    
    service cloud.firestore {
      match /databases/{database}/documents {
        match /messages/{messageId} {
          allow read, write: if request.auth != null;
        }
      }
    }
    
    Эти правила позволяют любому авторизованному пользователю читать или записывать сообщения в базу данных, что подходит для учебных целей. Подробнее о правилах безопасности см. в документации по правилам безопасности .
  5. Нажмите «Опубликовать» .

Отслеживайте состояние аутентификации в UserViewModel.swift

В SwiftUI реактивное отслеживание состояния Firebase Auth обеспечивает плавное переключение пользовательского интерфейса между экраном входа в систему и интерфейсом чата.

Откройте ViewModels/UserViewModel.swift и замените TODO в init() чтобы отслеживать изменения состояния аутентификации с помощью AsyncSequence authStateChanges :

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

Реализуйте вход, регистрацию и выход из системы с помощью Swift Concurrency.

В UserViewModel.swift замените оставшиеся комментарии TODO на современные методы аутентификации 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)
    }
  }

Проверка аутентификации пользователя

  1. Нажмите кнопку «Запустить» в Xcode.
  2. В симуляторе приложения нажмите «Регистрация» , введите отображаемое имя, адрес электронной почты и пароль, а затем нажмите «ЗАРЕГИСТРИРОВАТЬСЯ» .
  3. Вы должны автоматически пройти аутентификацию и перейти к пустому экрану сообщений!

6. Чтение из Cloud Firestore

Добавьте примеры сообщений в консоль Firebase.

Заполните базу данных примерами сообщений:

  1. В консоли Firebase перейдите в раздел Firestore Database и выберите вкладку Data .
  2. Нажмите « Начать сборку» .
  3. Введите messages для идентификатора коллекции , затем нажмите «Далее» .
  4. Оставьте параметр « Идентификатор документа» установленным на автоматическую генерацию (или нажмите «Автоматическая генерация идентификатора »).
  5. Добавьте в документ следующие поля:
    • text (тип: string , значение: Hello )
    • displayName (type: string , value: anonymous )
  6. Нажмите « Сохранить ».
  7. При желании нажмите «Добавить документ» , чтобы добавить дополнительные примеры сообщений.

Синхронизация сообщений в FriendlyMessageViewModel.swift

В SwiftUI декларативное обновление пользовательского интерфейса осуществляется с помощью модели @Observable . Откройте ViewModels/FriendlyMessageViewModel.swift и реализуйте startListening() и stopListening() используя snapshots AsyncSequence :

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

Как SwiftUI отображает и автоматически прокручивает сообщения

В Views/ContentView.swift обратите внимание, как ScrollViewReader и LazyVStack отображают всплывающие окна чата и автоматически прокручивают страницу до последнего сообщения при изменении значения 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)
      }
    }
  }
}

Благодаря прямой привязке состояния базы данных к состоянию пользовательского интерфейса через модель представления, SwiftUI позволяет коду приложения четко сопоставлять переходы с анимациями. На следующем шаге вы запишете данные в базу данных из приложения, чтобы наблюдать за этой анимацией в действии.

7. Отправляйте сообщения

Реализуйте функцию sendMessage с использованием параллельного программирования в Swift.

При добавлении документа в коллекцию с помощью addDocument() Cloud Firestore генерирует уникальный идентификатор для каждого сообщения чата.

В ViewModels/FriendlyMessageViewModel.swift замените TODO в sendMessage(text:imageUrl:) на 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)
  }

Проверка отправки сообщений

  1. Нажмите кнопку «Запустить» в Xcode.
  2. Войдите в свой аккаунт.
  3. Введите сообщение в нижнее текстовое поле и нажмите значок «Отправить».
  4. Сообщение мгновенно появится в вашем симуляторе!

8. Хранение и получение изображений

Настройка облачного хранилища для Firebase

  1. В консоли Firebase перейдите в раздел «Базы данных и хранилище» > «Хранилище» , затем нажмите «Начать» .
  2. Выберите «Запустить в тестовом режиме» и нажмите «Далее» .
  3. Примите место хранения по умолчанию и нажмите «Готово» .

Выбор изображений с помощью встроенных функций SwiftUI PhotosPicker

Современный SwiftUI (iOS 16+) обеспечивает нативный выбор фотографий с помощью PhotosPicker из import PhotosUI — никаких мостов делегатов UIKit или оберток UIImagePickerController не требуется!

В Views/FooterView.swift обратите внимание на то, как PhotosPicker привязывает выбранные изображения:

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

Реализовать загрузку изображений.

После выбора фотографии загрузите данные изображения в облачное хранилище и сохраните общедоступную ссылку для скачивания в Cloud Firestore.

В Views/FooterView.swift замените TODO в 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)")
    }
  }

Обмен изображениями через облачное хранилище.

В Views/FriendlyMessageImageView.swift URL-адреса изображений загружаются асинхронно с использованием Swift Concurrency и Cloud Storage SDK:

  • Стандартные URL-адреса для загрузки по протоколу HTTP/HTTPS извлекаются асинхронно с помощью URLSession .
  • URL-адреса Google Cloud Storage (например, gs://... ) извлекаются с помощью Storage.storage().reference(forURL:) .

Тестовые сообщения с изображениями

  1. Нажмите кнопку «Запустить» в Xcode.
  2. Войдите в свой аккаунт.
  3. Нажмите на значок «Фото», выберите изображение из библиотеки, и оно загрузится и появится в чате!

9. Заключение

Поздравляем, вы успешно создали приложение для чата в реальном времени на iOS с использованием Swift и Firebase!

Что вы узнали

  • Интеграция Swift Package Manager (SPM) с Xcode для firebase-ios-sdk .
  • Декларативная компоновка SwiftUI , PhotosPicker и автоматическая прокрутка ScrollViewReader .
  • Параллельное выполнение кода в Swift ( async / await ) для аутентификации, Cloud Firestore и Cloud Storage.
  • Аутентификация по электронной почте/паролю и анонимная аутентификация с реактивным наблюдением за состоянием.
  • Прослушивание моментальных снимков в реальном времени в Cloud Firestore и автоматическая запись идентификаторов.
  • Облачное хранилище для загрузки бинарных файлов Firebase и публичных URL-адресов для скачивания.

Узнать больше