1. Обзор

Добро пожаловать на практическое занятие по «Дружелюбному чату». В этом практическом занятии вы научитесь использовать платформу Firebase для создания приложений для iOS. Вы реализуете клиент чата и будете отслеживать его производительность с помощью Firebase.
Чему вы научитесь
- Разрешить пользователям входить в систему.
 - Синхронизируйте данные с помощью базы данных Firebase Realtime.
 - Храните двоичные файлы в Firebase Storage.
 
Что вам понадобится
- Xcode
 - CocoaPods
 - Тестовое устройство с iOS 8.0+ или симулятор
 
Как вы будете использовать это руководство?
Как бы вы оценили свой опыт создания приложений для iOS?
2. Получите пример кода
Клонируйте репозиторий GitHub из командной строки.
$ git clone https://github.com/firebase/codelab-friendlychat-ios
3. Создайте стартовое приложение

Чтобы создать стартовое приложение:
-  В окне терминала перейдите к 
 каталог ios-starter/swift-starterиз вашего примера кода для загрузки -  Запустить 
pod install --repo-update - Откройте файл FriendlyChatSwift.xcworkspace, чтобы открыть проект в Xcode.
 -  Нажмите на 
 Кнопка «Выполнить» . 
Через несколько секунд должен появиться главный экран Friendly Chat. Пользовательский интерфейс должен появиться. Однако на этом этапе вы не сможете войти в систему, отправлять и получать сообщения. Приложение завершит работу с ошибкой, пока вы не выполните следующий шаг.
4. Настройте проект Firebase
Создайте новый проект Firebase
- Войдите в консоль Firebase, используя свою учетную запись Google.
 -  Нажмите кнопку, чтобы создать новый проект, а затем введите название проекта (например, 
FriendlyChat). - Нажмите «Продолжить» .
 - При появлении соответствующего запроса ознакомьтесь с условиями Firebase и примите их, а затем нажмите кнопку «Продолжить» .
 - (Необязательно) Включите помощь ИИ в консоли Firebase (так называемая «Gemini в Firebase»).
 - Для этой лабораторной работы вам не понадобится Google Analytics, поэтому отключите опцию Google Analytics.
 - Нажмите «Создать проект» , дождитесь завершения подготовки проекта, а затем нажмите «Продолжить» .
 
Обновите свой тарифный план Firebase
Чтобы использовать Cloud Storage для Firebase, ваш проект Firebase должен быть включен в тарифный план с оплатой по мере использования (Blaze) , что означает, что он должен быть связан с учетной записью Cloud Billing .
- Для учетной записи Cloud Billing требуется способ оплаты, например кредитная карта.
 - Если вы новичок в Firebase и Google Cloud, проверьте, имеете ли вы право на кредит в размере 300 долларов США и бесплатную пробную учетную запись Cloud Billing .
 - Если вы выполняете эту практическую работу в рамках мероприятия, узнайте у организатора, доступны ли какие-либо облачные кредиты.
 
Чтобы обновить свой проект до плана Blaze, выполните следующие действия:
- В консоли Firebase выберите обновление вашего плана .
 -  Выберите тарифный план Blaze. Следуйте инструкциям на экране, чтобы подключить аккаунт Cloud Billing к своему проекту.
Если вам потребовалось создать учетную запись Cloud Billing в рамках этого обновления, вам может потребоваться вернуться к процессу обновления в консоли Firebase, чтобы завершить обновление. 
Подключите ваше приложение iOS
- На экране обзора нового проекта нажмите «Добавить Firebase в ваше приложение iOS» .
 -  Введите идентификатор пакета, например « 
com.google.firebase.codelab.FriendlyChatSwift». -  Введите идентификатор App Store: « 
123456». - Нажмите «Зарегистрировать приложение» .
 
Добавьте файл GoogleService-Info.plist в свое приложение
На втором экране нажмите «Загрузить GoogleService-Info.plist» , чтобы загрузить файл конфигурации, содержащий все необходимые метаданные Firebase для вашего приложения. Скопируйте этот файл в приложение и добавьте его в целевой объект FriendlyChatSwift .
Теперь вы можете нажать «x» в правом верхнем углу всплывающего окна, чтобы закрыть его, пропустив шаги 3 и 4, так как эти шаги вы выполните здесь.
Импорт модуля Firebase
 Для начала убедитесь, что модуль Firebase импортирован. 
AppDelegate.swift , FCViewController.swift
import Firebase
Настройте Firebase в AppDelegate
Используйте метод «configure» в FirebaseApp внутри функции application:didFinishLaunchingWithOptions для настройки базовых служб Firebase из файла .plist.
AppDelegate.swift
  func application(_ application: UIApplication, didFinishLaunchingWithOptions
      launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
  FirebaseApp.configure()
  GIDSignIn.sharedInstance().delegate = self
  return true
}
5. Идентификация пользователей
Используйте правила для ограничения доступа только для аутентифицированных пользователей
Теперь мы добавим правило, требующее аутентификации перед чтением или записью любых сообщений. Для этого добавим следующие правила в объект данных «Сообщения». В разделе «База данных» консоли Firebase выберите «База данных реального времени», затем перейдите на вкладку «Правила». Затем обновите правила так, чтобы они выглядели следующим образом:
{
  "rules": {
    "messages": {
      ".read": "auth != null",
      ".write": "auth != null"
    }
  }
}
Дополнительную информацию о том, как это работает (включая документацию по переменной «auth»), см. в документации по безопасности Firebase.
Настройка API аутентификации
Прежде чем ваше приложение сможет получить доступ к API аутентификации Firebase от имени ваших пользователей, вам необходимо включить его.
- Перейдите в консоль Firebase и выберите свой проект.
 - Выберите аутентификацию
 - Выберите вкладку «Способ входа».
 - Переведите переключатель Google в положение «Включено» (синий)
 - Нажмите « Сохранить» в появившемся диалоговом окне.
 
Если позже в этой кодовой работе вы получите сообщение об ошибке «CONFIGURATION_NOT_FOUND», вернитесь к этому шагу и еще раз проверьте свою работу.
Подтверждение зависимости аутентификации Firebase
 Убедитесь, что зависимости Firebase Auth существуют в файле Podfile .
Подфайл
pod 'Firebase/Auth'
Настройте Info.plist для входа через Google.
Вам потребуется добавить пользовательскую схему URL в ваш проект XCode.
- Откройте конфигурацию проекта: дважды щёлкните по названию проекта в левом дереве. Выберите приложение в разделе «ЦЕЛИ», затем перейдите на вкладку «Информация» и разверните раздел «Типы URL».
 - Нажмите кнопку «+» и добавьте схему URL для вашего обратного идентификатора клиента. Чтобы найти это значение, откройте файл конфигурации GoogleService-Info.plist и найдите ключ REVERSED_CLIENT_ID. Скопируйте значение этого ключа и вставьте его в поле «Схемы URL» на странице конфигурации. Остальные поля оставьте пустыми.
 - После завершения ваша конфигурация должна выглядеть примерно так (но со значениями, специфичными для вашего приложения):
 

Установить clientID для входа в Google
После настройки Firebase мы можем использовать clientID для настройки входа Google внутри метода «didFinishLaunchingWithOptions:».
AppDelegate.swift
  func application(_ application: UIApplication, didFinishLaunchingWithOptions
      launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
  FirebaseApp.configure()
  GIDSignIn.sharedInstance().clientID = FirebaseApp.app()?.options.clientID
  GIDSignIn.sharedInstance().delegate = self
  return true
}
Добавьте обработчик входа
После успешного входа в Google используйте учетную запись для аутентификации в Firebase.
AppDelegate.swift
  func sign(_ signIn: GIDSignIn!, didSignInFor user: GIDGoogleUser!, withError error: Error?) {
    if let error = error {
      print("Error \(error)")
      return
    }
    guard let authentication = user.authentication else { return }
    let credential = GoogleAuthProvider.credential(withIDToken: authentication.idToken,
                                                      accessToken: authentication.accessToken)
    Auth.auth().signIn(with: credential) { (user, error) in
      if let error = error {
        print("Error \(error)")
        return
      }
    }
  }
Автоматически входить в систему. Затем добавить прослушиватель в Firebase Auth, чтобы пользователь мог войти в приложение после успешного входа. И удалить прослушиватель при deinit.
SignInViewController.swift
  override func viewDidLoad() {
    super.viewDidLoad()
    GIDSignIn.sharedInstance().uiDelegate = self
    GIDSignIn.sharedInstance().signInSilently()
    handle = Auth.auth().addStateDidChangeListener() { (auth, user) in
      if user != nil {
        MeasurementHelper.sendLoginEvent()
        self.performSegue(withIdentifier: Constants.Segues.SignInToFp, sender: nil)
      }
    }
  }
  deinit {
    if let handle = handle {
      Auth.auth().removeStateDidChangeListener(handle)
    }
  }
Выход
Добавьте метод выхода
FCViewController.swift
  @IBAction func signOut(_ sender: UIButton) {
    let firebaseAuth = Auth.auth()
    do {
      try firebaseAuth.signOut()
      dismiss(animated: true, completion: nil)
    } catch let signOutError as NSError {
      print ("Error signing out: \(signOutError.localizedDescription)")
    }
  }
Тестовое чтение сообщений от имени зарегистрированного пользователя
-  Нажмите на 
 Кнопка «Выполнить» . - Вы сразу же будете перенаправлены на экран входа. Нажмите кнопку «Войти через Google».
 - Если все прошло успешно, вы будете перенаправлены на экран сообщений.
 
6. Активируйте базу данных в реальном времени

Импорт сообщений
 В консоли Firebase выберите пункт «База данных» на левой панели навигации. В раскрывающемся меню выберите «Импорт JSON» . Найдите файл initial_messages.json в каталоге friendlychat, выберите его и нажмите кнопку «Импорт» . Это заменит все текущие данные в вашей базе данных. Вы также можете редактировать базу данных напрямую, используя зелёный + и красный крестик для добавления и удаления элементов. 

После импорта ваша база данных должна выглядеть так:

Подтверждение зависимости базы данных Firebase
 В блоке зависимостей файла Podfile убедитесь, что включен Firebase/Database .
Подфайл
pod 'Firebase/Database'
Синхронизировать существующие сообщения
Добавьте код, который синхронизирует вновь добавленные сообщения с пользовательским интерфейсом приложения.
Код, который вы добавите в этом разделе, будет:
- Инициализируйте базу данных Firebase и добавьте прослушиватель для обработки изменений, внесенных в базу данных.
 -  Обновите 
DataSnapshot, чтобы отображались новые сообщения. 
Измените методы «deinit», «configureDatabase» и «tableView:cellForRow indexPath:» вашего FCViewController; замените их кодом, указанным ниже:
FCViewController.swift
  deinit {
    if let refHandle = _refHandle {
      self.ref.child("messages").removeObserver(withHandle: _refHandle)
    }
  }
  func configureDatabase() {
    ref = Database.database().reference()
    // Listen for new messages in the Firebase database
    _refHandle = self.ref.child("messages").observe(.childAdded, with: { [weak self] (snapshot) -> Void in
      guard let strongSelf = self else { return }
      strongSelf.messages.append(snapshot)
      strongSelf.clientTable.insertRows(at: [IndexPath(row: strongSelf.messages.count-1, section: 0)], with: .automatic)
    })
  }
  func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    // Dequeue cell
    let cell = self.clientTable.dequeueReusableCell(withIdentifier: "tableViewCell", for: indexPath)
    // Unpack message from Firebase DataSnapshot
    let messageSnapshot = self.messages[indexPath.row]
    guard let message = messageSnapshot.value as? [String: String] else { return cell }
    let name = message[Constants.MessageFields.name] ?? ""
    let text = message[Constants.MessageFields.text] ?? ""
    cell.textLabel?.text = name + ": " + text
    cell.imageView?.image = UIImage(named: "ic_account_circle")
    if let photoURL = message[Constants.MessageFields.photoURL], let URL = URL(string: photoURL),
        let data = try? Data(contentsOf: URL) {
      cell.imageView?.image = UIImage(data: data)
    }
    return cell
  }
Синхронизация тестового сообщения
-  Нажмите на 
 Кнопка «Выполнить» . - Нажмите кнопку «Войти, чтобы начать» , чтобы перейти в окно сообщений.
 -  Добавляйте новые сообщения непосредственно в консоли Firebase, нажав на зеленый символ + рядом с записью «messages» и добавив объект, как показано ниже: 

 - Убедитесь, что они отображаются в пользовательском интерфейсе дружественного чата.
 
7. Отправка сообщений
Реализовать отправку сообщения
Передача значений в базу данных. При использовании метода push для добавления данных в базу данных Firebase Realtime Database автоматически добавляется идентификатор. Эти автоматически сгенерированные идентификаторы являются последовательными, что гарантирует правильность порядка добавления новых сообщений.
Измените метод «sendMessage:» вашего FCViewController; замените его кодом, указанным ниже:
FCViewController.swift
  func sendMessage(withData data: [String: String]) {
    var mdata = data
    mdata[Constants.MessageFields.name] = Auth.auth().currentUser?.displayName
    if let photoURL = Auth.auth().currentUser?.photoURL {
      mdata[Constants.MessageFields.photoURL] = photoURL.absoluteString
    }
    // Push data to Firebase Database
    self.ref.child("messages").childByAutoId().setValue(mdata)
  }
Тестовая отправка сообщений
-  Нажмите на 
 Кнопка «Выполнить» . - Нажмите «Войти» , чтобы перейти в окно сообщений.
 - Введите сообщение и нажмите «Отправить». Новое сообщение должно появиться в интерфейсе приложения и в консоли Firebase.
 
8. Хранение и получение изображений
Подтверждение зависимости хранилища Firebase
 В блоке зависимостей Podfile убедитесь, что Firebase/Storage включен.
Подфайл
pod 'Firebase/Storage'
Настройка облачного хранилища для Firebase
Вот как настроить облачное хранилище для Firebase в вашем проекте Firebase:
- На левой панели консоли Firebase разверните Сборка , а затем выберите Хранилище .
 - Нажмите « Начать» .
 -  Выберите местоположение для контейнера хранения по умолчанию.
Бакеты вUS-WEST1,US-CENTRAL1иUS-EAST1могут воспользоваться тарифом «Всегда бесплатно» для Google Cloud Storage. Бакеты во всех остальных регионах следуют тарифам и условиям использования Google Cloud Storage . -  Нажмите «Начать в тестовом режиме» . Ознакомьтесь с отказом от ответственности о правилах безопасности.
Далее в этой лабораторной работе вы добавите правила безопасности для защиты своих данных. Не распространяйте и не публикуйте приложение, не добавив правила безопасности для вашего контейнера хранилища . - Нажмите «Создать» .
 
Настроить FirebaseStorage
FCViewController.swift
  func configureStorage() {
    storageRef = Storage.storage().reference()
  }
Получать изображения в существующих сообщениях
Добавьте код, загружающий изображения из Firebase Storage.
Измените метод «tableView: cellForRowAt indexPath:» вашего FCViewController; замените его кодом, указанным ниже:
FCViewController.swift
  func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    // Dequeue cell
    let cell = self.clientTable .dequeueReusableCell(withIdentifier: "tableViewCell", for: indexPath)
    // Unpack message from Firebase DataSnapshot
    let messageSnapshot: DataSnapshot! = self.messages[indexPath.row]
    guard let message = messageSnapshot.value as? [String:String] else { return cell }
    let name = message[Constants.MessageFields.name] ?? ""
    if let imageURL = message[Constants.MessageFields.imageURL] {
      if imageURL.hasPrefix("gs://") {
        Storage.storage().reference(forURL: imageURL).getData(maxSize: INT64_MAX) {(data, error) in
          if let error = error {
            print("Error downloading: \(error)")
            return
          }
          DispatchQueue.main.async {
            cell.imageView?.image = UIImage.init(data: data!)
            cell.setNeedsLayout()
          }
        }
      } else if let URL = URL(string: imageURL), let data = try? Data(contentsOf: URL) {
        cell.imageView?.image = UIImage.init(data: data)
      }
      cell.textLabel?.text = "sent by: \(name)"
    } else {
      let text = message[Constants.MessageFields.text] ?? ""
      cell.textLabel?.text = name + ": " + text
      cell.imageView?.image = UIImage(named: "ic_account_circle")
      if let photoURL = message[Constants.MessageFields.photoURL], let URL = URL(string: photoURL),
          let data = try? Data(contentsOf: URL) {
        cell.imageView?.image = UIImage(data: data)
      }
    }
    return cell
  }
9. Отправка изображений в сообщениях
Реализовать хранение и отправку изображений
Загрузите изображение от пользователя, затем синхронизируйте URL-адрес хранилища этого изображения с базой данных, чтобы это изображение было отправлено внутри сообщения.
Измените метод "imagePickerController: didFinishPickingMediaWithInfo:" вашего FCViewController; замените его кодом, определенным ниже:
FCViewController.swift
  func imagePickerController(_ picker: UIImagePickerController,
    didFinishPickingMediaWithInfo info: [String : Any]) {
      picker.dismiss(animated: true, completion:nil)
    guard let uid = Auth.auth().currentUser?.uid else { return }
    // if it's a photo from the library, not an image from the camera
    if #available(iOS 8.0, *), let referenceURL = info[UIImagePickerControllerReferenceURL] as? URL {
      let assets = PHAsset.fetchAssets(withALAssetURLs: [referenceURL], options: nil)
      let asset = assets.firstObject
      asset?.requestContentEditingInput(with: nil, completionHandler: { [weak self] (contentEditingInput, info) in
        let imageFile = contentEditingInput?.fullSizeImageURL
        let filePath = "\(uid)/\(Int(Date.timeIntervalSinceReferenceDate * 1000))/\((referenceURL as AnyObject).lastPathComponent!)"
        guard let strongSelf = self else { return }
        strongSelf.storageRef.child(filePath)
          .putFile(from: imageFile!, metadata: nil) { (metadata, error) in
            if let error = error {
              let nsError = error as NSError
              print("Error uploading: \(nsError.localizedDescription)")
              return
            }
            strongSelf.sendMessage(withData: [Constants.MessageFields.imageURL: strongSelf.storageRef.child((metadata?.path)!).description])
          }
      })
    } else {
      guard let image = info[UIImagePickerControllerOriginalImage] as? UIImage else { return }
      let imageData = UIImageJPEGRepresentation(image, 0.8)
      let imagePath = "\(uid)/\(Int(Date.timeIntervalSinceReferenceDate * 1000)).jpg"
      let metadata = StorageMetadata()
      metadata.contentType = "image/jpeg"
      self.storageRef.child(imagePath)
        .putData(imageData!, metadata: metadata) { [weak self] (metadata, error) in
          if let error = error {
            print("Error uploading: \(error)")
            return
          }
          guard let strongSelf = self else { return }
          strongSelf.sendMessage(withData: [Constants.MessageFields.imageURL: strongSelf.storageRef.child((metadata?.path)!).description])
      }
    }
  }
Тестовая отправка и получение сообщений с изображениями
-  Нажмите на 
 Кнопка «Выполнить» . - Нажмите «Войти» , чтобы перейти в окно сообщений.
 - Нажмите значок «Добавить фото», чтобы выбрать фотографию. Новое сообщение с фотографией должно быть видно в интерфейсе приложения и в консоли Firebase.
 
10. Поздравляем!
Вы использовали Firebase для легкого создания приложения чата в реальном времени.
Что мы рассмотрели
- База данных в реальном времени
 - Федеративный вход
 - Хранилище
 
