1. 개요
iOS용 FriendlyChat Codelab에 오신 것을 환영합니다. 이 Codelab에서는 Firebase와 SwiftUI, Swift 동시 실행, 관찰 프레임워크와 같은 최신 Swift 기능을 사용하여 모든 기능을 갖춘 실시간 채팅 iOS 애플리케이션을 빌드하는 방법을 알아봅니다.
학습할 내용
- 최신 관찰 프레임워크 (
@Observable) 로 상태를 관리합니다. - Firebase 인증 을 사용하여 사용자를 인증합니다.
- Cloud Firestore 를 사용하여 채팅 메시지를 실시간으로 동기화합니다.
- Firebase용 Cloud Storage 를 사용하여 이미지를 저장하고 제공합니다.
- 최신 Swift 동시 실행 기능을 사용하여 Firebase API에 액세스합니다.
필요한 항목
- Xcode 최신 버전
- iOS 시뮬레이터 또는 실제 테스트 기기
- Firebase 프로젝트를 만들고 관리할 Google 계정
2. 샘플 코드 가져오기
명령줄에서 GitHub 저장소를 클론합니다.
$ git clone https://github.com/firebase/codelab-friendlychat-ios
codelab-friendlychat-ios 저장소에는 다음과 같은 여러 샘플 프로젝트가 있습니다.
ios-starter/swift-starter: SwiftUI 레이아웃 스켈레톤과 Firebase로 앱 기능을 구현할TODO주석이 포함된 시작 Xcode 프로젝트입니다.ios/swift: 완성된 샘플 애플리케이션이 포함된 완료된 Xcode 프로젝트입니다.
3. 시작 앱 빌드
시작 애플리케이션을 빌드하고 실행하여 사용자 인터페이스에 익숙해집니다.
- Finder 또는 터미널에서
ios-starter/swift-starter디렉터리로 이동합니다. FriendlyChatSwift.xcodeproj를 더블클릭하여 Xcode에서 프로젝트를 엽니다.- Xcode는 백그라운드에서 Swift Package Manager 종속 항목을 자동으로 확인합니다 (GitHub에서
firebase-ios-sdkv12 이상 다운로드). - iOS 시뮬레이터 (예: iPhone 17 Pro)를 선택하고 실행 버튼 (cmd+R)을 클릭합니다.
몇 초 후 FriendlyChat 로그인 화면이 표시됩니다. 이때 로그인 버튼과 메시지 피드는 자리표시자입니다. 다음 단계에서 Firebase를 앱에 연결합니다.
4. Firebase 프로젝트 설정
새 Firebase 프로젝트 만들기
- Google 계정을 사용하여 Firebase Console에 로그인합니다.
- 버튼을 클릭하여 새 프로젝트를 만든 다음 프로젝트 이름 (예:
FriendlyChat)을 입력합니다.
- 계속 을 클릭합니다.
- 메시지가 표시되면 Firebase 약관을 검토하고 이에 동의한 다음 계속을 클릭합니다.
- (선택사항) Firebase Console에서 AI 지원('Firebase의 Gemini'라고 함)을 사용 설정합니다.
- 이 Codelab에서는 Google 애널리틱스가 필요하지 않으므로 Google 애널리틱스 옵션을 사용 중지 합니다.
- 프로젝트 만들기 를 클릭하고 프로젝트가 프로비저닝될 때까지 기다린 후 계속 을 클릭합니다.
Firebase 요금제 업그레이드
Firebase용 Cloud Storage를 사용하려면 Firebase 프로젝트가 사용한 만큼만 지불하는 (Blaze) 요금제를 사용하고 있어야 합니다. 즉, Cloud Billing 계정에 연결되어 있어야 합니다.
- Cloud Billing 계정에는 신용카드와 같은 결제 수단이 필요합니다.
- Firebase와 Google Cloud를 처음 사용하는 경우 $300 크레딧과 무료 체험판 Cloud Billing 계정을 받을 자격이 되는지 확인하세요.
- 이벤트의 일환으로 이 Codelab을 진행하는 경우 주최자에게 사용 가능한 Cloud 크레딧이 있는지 문의하세요.
프로젝트를 Blaze 요금제로 업그레이드하려면 다음 단계를 따르세요.
- Firebase Console에서 요금제를 업그레이드할 요금제를 선택합니다.
- Blaze 요금제를 선택합니다. 화면에 표시된 안내에 따라 Cloud Billing 계정을 프로젝트에 연결합니다.
이 업그레이드의 일환으로 Cloud Billing 계정을 만들어야 하는 경우 업그레이드를 완료하기 위해 Firebase Console의 업그레이드 흐름으로 돌아가야 할 수 있습니다.
iOS 앱 연결
- 프로젝트 개요 화면에서 iOS 아이콘을 클릭하여 앱 설정 워크플로를 시작합니다.
- 번들 ID를 입력합니다.
com.google.firebase.codelab.FriendlyChatSwift - 앱 등록 을 클릭합니다.
Xcode 프로젝트에 GoogleService-Info.plist 추가
- GoogleService-Info.plist 다운로드 를 클릭하여 구성 파일을 저장합니다.
- Xcode에서 다운로드한
GoogleService-Info.plist파일을 프로젝트 탐색기의FriendlyChatSwift기본 그룹으로 드래그합니다. - 메시지가 표시되면 필요한 경우 항목 복사 가 선택되어 있고
FriendlyChatSwift타겟이 선택되어 있는지 확인한 다음 완료 를 클릭합니다.
FriendlyChatSwiftApp.swift에서 Firebase 구성
마지막으로 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 Console에서 인증 구성
사용자를 인증하려면 먼저 Firebase Console에서 원하는 인증 제공업체를 사용 설정해야 합니다. 이 가이드에서는 이메일/비밀번호 인증을 사용합니다.
- Firebase Console에서 보안 > 인증으로 이동한 다음 시작하기를 클릭합니다.
- 로그인 방법 탭을 선택합니다.
- 이메일/비밀번호를 클릭하고 사용 설정 전환 스위치를 클릭한 다음 저장을 클릭합니다.
인증 규칙으로 Cloud Firestore 보호
다음으로 사용자가 메시지를 읽거나 쓰기 전에 인증을 받아야 합니다.
- Firebase Console에서 데이터베이스 및 스토리지 > Firestore 데이터베이스 로 이동한 다음 데이터베이스 만들기 를 클릭합니다.
- 위치를 선택하고 다음 을 클릭합니다.
- 테스트 모드에서 시작 을 선택하고 만들기 를 클릭합니다.
- 규칙 탭을 선택하고 보안 규칙을 다음과 같이 업데이트합니다.
이러한 규칙을 사용하면 인증된 모든 사용자가 데이터베이스에서 메시지를 읽거나 쓸 수 있으므로 학습 목적으로 적합합니다. 보안 규칙에 관한 자세한 내용은 보안 규칙 문서를 참고하세요.rules_version = '2'; service cloud.firestore { match /databases/{database}/documents { match /messages/{messageId} { allow read, write: if request.auth != null; } } } - 게시 를 클릭합니다.
UserViewModel.swift에서 인증 상태 관찰
SwiftUI에서 Firebase 인증 상태를 반응형으로 관찰하면 로그인 화면과 채팅 인터페이스 간에 UI가 원활하게 전환됩니다.
ViewModels/UserViewModel.swift 를 열고 init()의 TODO를 authStateChanges AsyncSequence를 사용하여 인증 상태 변경을 관찰하도록 바꿉니다.
init() {
authTask = Task {
for await user in Auth.auth().authStateChanges {
self.user = user
}
}
}
Swift 동시 실행으로 로그인, 가입, 로그아웃 구현
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)
}
}
사용자 인증 테스트
- Xcode에서 실행 버튼을 클릭합니다.
- 앱 시뮬레이터에서 가입을 클릭하고 표시 이름, 이메일, 비밀번호를 입력한 다음 가입을 탭합니다.
- 자동으로 인증되고 빈 메시지 화면으로 이동됩니다.
6. Cloud Firestore에서 읽기
Firebase Console에서 샘플 메시지 추가
샘플 메시지로 데이터베이스를 채웁니다.
- Firebase Console에서 Firestore 데이터베이스 로 이동하고 데이터 탭을 선택합니다.
- 컬렉션 시작 을 클릭합니다.
- **컬렉션 ID** 에
messages를 입력한 다음 다음을 클릭합니다. - 문서 ID 를 자동 생성으로 설정된 상태로 두거나 자동 ID 를 클릭합니다.
- 문서에 다음 필드를 추가합니다.
text(유형:string, 값:Hello)displayName(유형:string, 값:anonymous)
- 저장 을 클릭합니다.
- 선택적으로 문서 추가 를 클릭하여 샘플 메시지를 더 추가합니다.
FriendlyMessageViewModel.swift에서 메시지 동기화
SwiftUI에서 @Observable 모델은 선언적 UI 업데이트를 구동합니다. ViewModels/FriendlyMessageViewModel.swift 를 열고 snapshots AsyncSequence를 사용하여 startListening() 및 stopListening()을 구현합니다.
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를 사용하면 뷰 모델을 통해 데이터베이스 상태를 UI 상태에 직접 연결하여 앱 코드가 전환을 애니메이션에 깔끔하게 매핑할 수 있습니다. 다음 단계에서는 앱에서 데이터베이스에 데이터를 작성하여 이 애니메이션이 작동하는지 확인합니다.
7. 메시지 보내기
Swift 동시 실행으로 sendMessage 구현
addDocument()를 사용하여 컬렉션에 문서를 추가하면 Cloud Firestore는 각 채팅 메시지에 고유한 ID를 생성합니다.
ViewModels/FriendlyMessageViewModel.swift에서 sendMessage(text:imageUrl:)의 TODO를 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)
}
메시지 전송 테스트
- Xcode에서 실행 버튼을 클릭합니다.
- 계정에 로그인합니다.
- 하단 텍스트 필드에 메시지를 입력하고 보내기 아이콘을 탭합니다.
- 시뮬레이터에 메시지가 즉시 표시되는지 확인합니다.
8. 이미지 저장 및 수신
Firebase용 Cloud Storage 설정
- Firebase Console에서 데이터베이스 및 스토리지 > 스토리지로 이동한 다음 시작하기를 클릭합니다.
- 테스트 모드에서 시작 을 선택하고 다음 을 클릭합니다.
- 기본 스토리지 위치를 그대로 사용하고 완료 를 클릭합니다.
SwiftUI PhotosPicker를 사용한 네이티브 이미지 선택
최신 SwiftUI (iOS 16 이상)는 import PhotosUI의 PhotosPicker 를 통해 네이티브 사진 선택을 제공합니다. 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 Storage에 업로드하고 공개 다운로드 URL을 Cloud Firestore에 저장합니다.
Views/FooterView.swift에서 uploadAndSendImage(data:)의 TODO를 바꿉니다.
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)")
}
}
Cloud Storage를 사용한 이미지 공유
Views/FriendlyMessageImageView.swift에서 이미지 URL은 Swift 동시 실행 및 Cloud Storage SDK를 사용하여 비동기식으로 로드됩니다.
- 표준 HTTP/HTTPS 다운로드 URL은
URLSession을 통해 비동기식으로 검색됩니다. - Google Cloud Storage URL (예:
gs://...)은Storage.storage().reference(forURL:)을 사용하여 가져옵니다.
이미지 메시지 테스트
- Xcode에서 실행 버튼을 클릭합니다.
- 계정에 로그인합니다.
- 사진 아이콘을 탭하고 라이브러리에서 이미지를 선택한 다음 채팅에 업로드되어 표시되는지 확인합니다.
9. 결론
수고하셨습니다. Swift와 Firebase를 사용하여 실시간 iOS 채팅 애플리케이션을 빌드했습니다.
학습한 내용
firebase-ios-sdk용 Swift Package Manager (SPM) Xcode 통합- SwiftUI 선언적 레이아웃,
PhotosPicker,ScrollViewReader자동 스크롤 - 인증, Cloud Firestore, Cloud Storage 전반의 Swift 동시 실행 (
async/await) - 반응형 상태 관찰을 통한 이메일/비밀번호 및 익명 인증
- Cloud Firestore 실시간 스냅샷 수신 대기 및 자동 ID 쓰기
- Firebase용 Cloud Storage 바이너리 업로드 및 공개 다운로드 URL