Firebase iOS Codelab Swift

1. 개요

2efe6805ef369641.png

친근한 채팅 Codelab에 오신 것을 환영합니다. 이 Codelab에서는 Firebase 플랫폼을 사용하여 iOS 애플리케이션을 만드는 방법을 알아봅니다. Firebase를 사용하여 채팅 클라이언트를 구현하고 성능을 모니터링합니다.

학습 내용

  • 사용자가 로그인하도록 허용합니다.
  • Firebase 실시간 데이터베이스를 사용하여 데이터를 동기화합니다.
  • Firebase 저장소에 바이너리 파일을 저장합니다.

필요한 사항

  • Xcode
  • CocoaPods
  • iOS 8.0 이상을 실행하는 테스트 기기 또는 시뮬레이터

본 튜토리얼을 어떻게 사용하실 계획인가요?

읽기만 할 계획입니다 읽은 다음 연습 활동을 완료할 계획입니다

귀하의 iOS 앱 빌드 경험을 평가해 주세요.

초급 중급 고급

2. 샘플 코드 가져오기

명령줄에서 GitHub 저장소를 클론합니다.

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

3. 시작 앱 빌드

2f4c98d858c453fe.png

시작 앱을 빌드하려면 다음 안내를 따르세요.

  1. 터미널 창의 샘플 코드 다운로드에서 android_studio_folder.pngios-starter/swift-starter 디렉터리로 이동합니다.
  2. pod install --repo-update 실행
  3. FriendlyChatSwift.xcworkspace 파일을 열어 Xcode에서 프로젝트를 엽니다.
  4. 98205811bbed9d74.pngRun 버튼을 클릭합니다.

몇 초 후 프렌디 채팅 홈 화면이 표시됩니다. UI가 표시됩니다. 하지만 이때는 로그인하거나 메시지를 보내거나 받을 수 없습니다. 다음 단계를 완료할 때까지 앱이 예외와 함께 중단됩니다.

4. Firebase Console 프로젝트 만들기

프로젝트 만들기

Firebase Console에서 프로젝트 추가를 선택합니다.

프로젝트 FriendlyChat를 호출한 다음 프로젝트 만들기를 클릭합니다.

Screenshot from 2015-11-06 14:13:39.png

Firebase 요금제 업그레이드

Firebase용 Cloud Storage를 사용하려면 Firebase 프로젝트가 일부 요금제(Blaze) 요금제를 사용해야 합니다. 즉, Firebase 프로젝트가 Cloud Billing 계정에 연결되어 있어야 합니다.

  • Cloud Billing 계정에는 신용카드와 같은 결제 수단이 필요합니다.
  • Firebase와 Google Cloud를 처음 사용하는 경우 $300 크레딧과 무료 체험판 Cloud Billing 계정을 받을 자격이 되는지 확인하세요.
  • 이 Codelab을 이벤트의 일환으로 진행하는 경우 주최자에게 사용 가능한 Cloud 크레딧이 있는지 문의하세요.

프로젝트를 Blaze 요금제로 업그레이드하려면 다음 단계를 따르세요.

  1. Firebase Console에서 요금제를 업그레이드하도록 선택합니다.
  2. Blaze 요금제를 선택합니다. 화면에 표시된 안내에 따라 Cloud Billing 계정을 프로젝트에 연결합니다.
    이 업그레이드의 일환으로 Cloud Billing 계정을 만들어야 하는 경우 Firebase Console에서 업그레이드 과정으로 돌아가서 업그레이드를 완료해야 할 수도 있습니다.

iOS 앱 연결하기

  1. 새 프로젝트의 프로젝트 개요 화면에서 iOS 앱에 Firebase 추가를 클릭합니다.
  2. 번들 ID를 'com.google.firebase.codelab.FriendlyChatSwift'(으)로 입력합니다.
  3. App Store ID를 '123456'(으)로 입력합니다.
  4. 앱 등록을 클릭합니다.

앱에 GoogleService-Info.plist 파일 추가

두 번째 화면에서 GoogleService-Info.plist 다운로드를 클릭하여 앱에 필요한 모든 Firebase 메타데이터가 포함된 구성 파일을 다운로드합니다. 이 파일을 애플리케이션에 복사하고 FriendlyChatSwift 타겟에 추가합니다.

이제 팝업의 오른쪽 상단에 있는 'x'를 클릭하여 팝업을 닫을 수 있습니다. 3단계와 4단계는 여기에서 수행하므로 건너뜁니다.

19d59efb213ddbdc.png

Firebase 모듈 가져오기

먼저 Firebase 모듈을 가져왔는지 확인합니다.

AppDelegate.swift, FCViewController.swift

import Firebase

AppDelegate에서 Firebase 구성

application:didFinishLaunchingWithOptions 함수 내의 FirebaseApp에서 'configure' 메서드를 사용하여 .plist 파일에서 기본 Firebase 서비스를 구성합니다.

AppDelegate.swift

  func application(_ application: UIApplication, didFinishLaunchingWithOptions
      launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
  FirebaseApp.configure()
  GIDSignIn.sharedInstance().delegate = self
  return true
}

5. 사용자 식별

규칙을 사용하여 인증된 사용자로 제한

이제 메일을 읽거나 쓰기 전에 인증을 요구하는 규칙을 추가합니다. 이를 위해 메시지 데이터 객체에 다음 규칙을 추가합니다. Firebase Console의 데이터베이스 섹션에서 실시간 데이터베이스를 선택한 다음 규칙 탭을 클릭합니다. 그런 다음 규칙을 다음과 같이 업데이트합니다.

{
  "rules": {
    "messages": {
      ".read": "auth != null",
      ".write": "auth != null"
    }
  }
}

작동 방식에 관한 자세한 내용('auth' 변수에 관한 문서 포함)은 Firebase 보안 문서를 참고하세요.

Configure Authentication API

애플리케이션에서 사용자를 대신하여 Firebase Authentication API에 액세스하려면 먼저 이 API를 사용 설정해야 합니다.

  1. Firebase Console로 이동하여 프로젝트를 선택합니다.
  2. 인증을 선택합니다.
  3. 로그인 방법 탭을 선택합니다.
  4. Google 스위치를 사용 설정 (파란색)으로 전환합니다.
  5. 표시되는 대화상자에서 Save를 누릅니다.

이 Codelab의 뒷부분에서 'CONFIGURATION_NOT_FOUND' 메시지와 함께 오류가 발생하면 이 단계로 돌아와서 작업을 다시 확인하세요.

Firebase 인증 종속 항목 확인

Podfile 파일에 Firebase 인증 종속 항목이 있는지 확인합니다.

Podfile

pod 'Firebase/Auth'

Google 로그인을 위한 Info.plist를 설정합니다.

XCode 프로젝트에 맞춤 URL 스키마를 추가해야 합니다.

  1. 프로젝트 구성을 엽니다. 왼쪽 트리 보기에서 프로젝트 이름을 더블클릭합니다. TARGETS 섹션에서 앱을 선택한 다음 정보 탭을 선택하고 URL 유형 섹션을 펼칩니다.
  2. + 버튼을 클릭하고 반전된 클라이언트 ID의 URL 스키마를 추가합니다. 이 값을 찾으려면 GoogleService-Info.plist 구성 파일을 열고 REVERSED_CLIENT_ID 키를 찾습니다. 이 키의 값을 복사하여 구성 페이지의 URL 스키마 상자에 붙여넣습니다. 다른 필드는 비워 둡니다.
  3. 완성된 구성은 다음과 같은 형태이며 애플리케이션별 값이 적용됩니다.

1b54d5bd2f4f1448.png

Google 로그인을 위한 clientID 설정

Firebase가 구성되면 clientID를 사용하여 'didFinishLaunchingWithOptions:' 메서드 내에서 Google 로그인을 설정할 수 있습니다.

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 인증에 리스너를 추가하여 로그인에 성공하면 사용자가 앱에 로그인할 수 있도록 합니다. 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)")
    }
  }

로그인한 사용자로 메시지 읽기 테스트

  1. 98205811bbed9d74.pngRun 버튼을 클릭합니다.
  2. 로그인 화면으로 즉시 이동합니다. Google 로그인 버튼을 탭합니다.
  3. 모든 것이 잘 작동하면 메시지 화면으로 이동합니다.

6. 실시간 데이터베이스 활성화

2efe6805ef369641.png

메시지 가져오기

Firebase Console의 프로젝트에서 왼쪽 탐색 메뉴에 있는 데이터베이스 항목을 선택합니다. 데이터베이스의 더보기 메뉴에서 JSON 가져오기를 선택합니다. FRIENDchat 디렉터리에서 initial_messages.json 파일을 찾아 선택한 다음 Import 버튼을 클릭합니다. 이렇게 하면 현재 데이터베이스에 있는 모든 데이터가 대체됩니다. 녹색 + 및 빨간색 x를 사용하여 항목을 추가 및 삭제하여 데이터베이스를 직접 수정할 수도 있습니다.

20ccf4856b715b4c.png

데이터베이스를 가져오면 다음과 같이 표시됩니다.

f3e0367f1c9cd187.png

Firebase 데이터베이스 종속 항목 확인

Podfile 파일의 종속 항목 블록에서 Firebase/Database가 포함되어 있는지 확인합니다.

Podfile

pod 'Firebase/Database'

기존 메시지 동기화하기

새로 추가된 메시지를 앱 UI에 동기화하는 코드를 추가합니다.

이 섹션에 추가하는 코드는 다음과 같습니다.

  • Firebase 데이터베이스를 초기화하고 데이터베이스에 적용된 변경사항을 처리하는 리스너를 추가합니다.
  • 새 메시지가 표시되도록 DataSnapshot를 업데이트합니다.

FCViewController의 'deinit', 'configureDatabase', 'tableView:cellForRow indexPath:' 메서드를 수정하고 아래에 정의된 코드로 바꿉니다.

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
  }

메시지 동기화 테스트

  1. 98205811bbed9d74.pngRun 버튼을 클릭합니다.
  2. 로그인하여 시작하기 버튼을 클릭하여 메시지 창으로 이동합니다.
  3. 'messages' 항목 옆에 있는 녹색 + 기호를 클릭하고 다음과 같은 객체를 추가하여 Firebase Console에 새 메시지를 직접 추가합니다. F9876ffc8b316b14.png
  4. 친근한 채팅 UI에 표시되는지 확인합니다.

7. 메시지 전송

메시지 전송 구현

데이터베이스에 값을 푸시합니다. 푸시 메서드를 사용하여 Firebase 실시간 데이터베이스에 데이터를 추가하면 자동 ID가 추가됩니다. 자동 생성된 ID는 순차적이므로 새 메시지가 올바른 순서로 추가됩니다.

FCViewController의 'sendMessage:' 메서드를 수정하고 아래에 정의된 코드로 바꿉니다.

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

메시지 전송 테스트하기

  1. 98205811bbed9d74.png실행 버튼을 클릭합니다.
  2. 로그인을 클릭하여 메시지 창으로 이동합니다.
  3. 메시지를 입력하고 보내기를 누릅니다. 새 메시지가 앱 UI와 Firebase Console에 표시되어야 합니다.

8. 이미지 저장 및 수신

Firebase 스토리지 종속 항목 확인

Podfile의 종속 항목 블록에서 Firebase/Storage가 포함되어 있는지 확인합니다.

Podfile

pod 'Firebase/Storage'

Firebase용 Cloud Storage 설정

Firebase 프로젝트에서 Firebase용 Cloud Storage를 설정하는 방법은 다음과 같습니다.

  1. Firebase Console의 왼쪽 패널에서 빌드를 펼치고 저장소를 선택합니다.
  2. 시작하기를 클릭합니다.
  3. 기본 Storage 버킷의 위치를 선택하세요.
    US-WEST1, US-CENTRAL1, US-EAST1의 버킷에서 Google Cloud Storage에 '항상 무료' 등급을 활용할 수 있습니다. 다른 모든 위치의 버킷에는 Google Cloud Storage 가격 및 사용량이 적용됩니다.
  4. 테스트 모드에서 시작을 클릭합니다. 보안 규칙에 관한 면책 조항을 읽습니다.
    이 Codelab의 후반부에서 데이터를 보호하는 보안 규칙을 추가합니다. 스토리지 버킷에 대한 보안 규칙을 추가하지 않은 채 앱을 공개적으로 배포하거나 노출하지 마세요.
  5. 만들기를 클릭합니다.

FirebaseStorage 구성

FCViewController.swift

  func configureStorage() {
    storageRef = Storage.storage().reference()
  }

기존 메시지에서 이미지 수신하기

Firebase 저장소에서 이미지를 다운로드하는 코드를 추가합니다.

FCViewController의 'tableView:cellForRowAt indexPath:' 메서드를 수정하고, 아래에 정의된 코드로 바꿉니다.

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을 데이터베이스에 동기화하여 이미지가 메시지 내에 전송되도록 합니다.

FCViewController의 'imagePickerController:didFinishPickingMediaWithInfo:' 메서드를 수정하고 아래에 정의된 코드로 교체합니다.

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

이미지 메시지 전송 및 수신 테스트

  1. 98205811bbed9d74.pngRun 버튼을 클릭합니다.
  2. 로그인을 클릭하여 메시지 창으로 이동합니다.
  3. '사진 추가' 아이콘을 클릭하여 사진을 선택합니다. 사진이 포함된 새 메시지가 앱 UI와 Firebase Console에 표시됩니다.

10. 수고하셨습니다.

Firebase를 사용하여 실시간 채팅 애플리케이션을 쉽게 빌드해 보았습니다.

학습한 내용

  • 실시간 데이터베이스
  • 제휴 로그인
  • 스토리지

자세히 알아보기