1. ภาพรวม
ยินดีต้อนรับสู่ Codelab FriendlyChat สำหรับ iOS ใน Codelab นี้ คุณจะได้เรียนรู้วิธีสร้างแอปพลิเคชันแชท iOS แบบเรียลไทม์ที่มีฟีเจอร์ครบถ้วนโดยใช้ Firebase และฟีเจอร์ Swift สมัยใหม่ เช่น SwiftUI, Swift Concurrency และเฟรมเวิร์ก Observation
สิ่งที่คุณจะได้เรียนรู้
- จัดการสถานะด้วยเฟรมเวิร์ก Observation (
@Observable) สมัยใหม่ - ตรวจสอบสิทธิ์ผู้ใช้โดยใช้การตรวจสอบสิทธิ์ Firebase
- ซิงค์ข้อความแชทแบบเรียลไทม์โดยใช้ Cloud Firestore
- จัดเก็บและแสดงรูปภาพโดยใช้ Cloud Storage for Firebase
- เข้าถึง Firebase API โดยใช้ฟีเจอร์ Swift Concurrency สมัยใหม่
สิ่งที่ต้องมี
- 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ซึ่งคุณจะใช้เพื่อติดตั้งใช้งานฟีเจอร์ของแอปด้วย Firebaseios/swift\- โปรเจ็กต์ Xcode ที่เสร็จสมบูรณ์แล้วพร้อมแอปพลิเคชันตัวอย่างที่เสร็จสมบูรณ์
3. สร้างแอปเริ่มต้น
สร้างและเรียกใช้แอปพลิเคชันเริ่มต้นเพื่อทำความคุ้นเคยกับอินเทอร์เฟซผู้ใช้
- ใน Finder หรือ Terminal ให้ไปที่ไดเรกทอรี
ios-starter/swift-starter - ดับเบิลคลิก
FriendlyChatSwift.xcodeprojเพื่อเปิดโปรเจ็กต์ใน Xcode - สังเกตว่า Xcode จะแก้ปัญหาการขึ้นอยู่กับ Swift Package Manager ในเบื้องหลังโดยอัตโนมัติ (ดาวน์โหลด
firebase-ios-sdkเวอร์ชัน 12 ขึ้นไปจาก GitHub) - เลือกโปรแกรมจำลอง iOS (เช่น iPhone 17 Pro) แล้วคลิกปุ่มเรียกใช้ (cmd+R)
หลังจากผ่านไปสักครู่ หน้าจอเข้าสู่ระบบ FriendlyChat จะปรากฏขึ้น ในตอนนี้ ปุ่มเข้าสู่ระบบและฟีดข้อความเป็นตัวยึดตำแหน่ง คุณจะเชื่อมต่อ Firebase กับแอปในขั้นตอนต่อไป
4. ตั้งค่าโปรเจ็กต์ Firebase
สร้างโปรเจ็กต์ Firebase ใหม่
- ลงชื่อเข้าใช้คอนโซล Firebaseด้วยบัญชี Google
- คลิกปุ่มเพื่อสร้างโปรเจ็กต์ใหม่ แล้วป้อนชื่อโปรเจ็กต์ (เช่น
FriendlyChat)
- คลิกต่อไป
- หากได้รับแจ้ง ให้อ่านและยอมรับข้อกำหนดของ Firebase แล้วคลิกต่อไป
- (ไม่บังคับ) เปิดใช้ความช่วยเหลือจาก AI ในคอนโซล Firebase (เรียกว่า "Gemini ใน Firebase")
- สำหรับ Codelab นี้ คุณไม่ จำเป็นต้องใช้ Google Analytics ดังนั้นปิด ตัวเลือก Google Analytics
- คลิกสร้างโปรเจ็กต์ รอให้ระบบจัดสรรโปรเจ็กต์ แล้วคลิกต่อไป
อัปเกรดแพ็กเกจราคาของ Firebase
หากต้องการใช้ Cloud Storage for Firebase โปรเจ็กต์ Firebase ของคุณต้องอยู่ในแพ็กเกจราคาแบบจ่ายเมื่อใช้ (Blaze) ซึ่งหมายความว่าโปรเจ็กต์ต้องลิงก์กับบัญชีสำหรับการเรียกเก็บเงินใน Cloud
- บัญชีสำหรับการเรียกเก็บเงินใน Cloud ต้องมีวิธีการชำระเงิน เช่น บัตรเครดิต
- หากคุณเพิ่งเริ่มใช้ Firebase และ Google Cloud ให้ตรวจสอบว่าคุณมีสิทธิ์รับเครดิต$300 และบัญชีสำหรับการเรียกเก็บเงินใน Cloud รุ่นทดลองใช้ฟรีหรือไม่
- หากคุณทำ Codelab นี้เป็นส่วนหนึ่งของกิจกรรม ให้สอบถามผู้จัดงานว่ามีเครดิต Cloud ให้หรือไม่
หากต้องการอัปเกรดโปรเจ็กต์เป็นแพ็กเกจ Blaze ให้ทำตามขั้นตอนต่อไปนี้
- ในคอนโซล Firebase ให้เลือกเพื่อ อัปเกรดแพ็กเกจ
- เลือกแพ็กเกจ Blaze ทำตามวิธีการบนหน้าจอเพื่อลิงก์บัญชีสำหรับการเรียกเก็บเงินใน Cloud กับโปรเจ็กต์
หากคุณต้องสร้างบัญชีสำหรับการเรียกเก็บเงินใน Cloud เป็นส่วนหนึ่งของการอัปเกรดนี้ คุณอาจต้องกลับไปที่โฟลว์การอัปเกรดในคอนโซล Firebase เพื่อทำการอัปเกรดให้เสร็จสมบูรณ์
เชื่อมต่อแอป iOS
- จากหน้าจอภาพรวมโปรเจ็กต์ ให้คลิกไอคอน iOS เพื่อเปิดเวิร์กโฟลว์การตั้งค่าแอป
- ป้อนรหัสชุดซอฟต์แวร์
com.google.firebase.codelab.FriendlyChatSwift - คลิกลงทะเบียนแอป
เพิ่ม GoogleService-Info.plist ลงในโปรเจ็กต์ Xcode
- คลิกดาวน์โหลด GoogleService-Info.plist เพื่อบันทึกไฟล์การกำหนดค่า
- ใน Xcode ให้ลากไฟล์
GoogleService-Info.plistที่ดาวน์โหลดลงในกลุ่มหลักFriendlyChatSwiftใน Project Navigator - เมื่อได้รับข้อความแจ้ง ให้ตรวจสอบว่าได้เลือกคัดลอกรายการหากจำเป็น และเลือกเป้าหมาย
FriendlyChatSwiftแล้ว จากนั้นคลิกเสร็จสิ้น
กำหนดค่า Firebase ใน FriendlyChatSwiftApp.swift
สุดท้าย ให้เริ่มต้น Firebase ด้วยการกำหนดค่าโปรเจ็กต์เมื่อแอปพลิเคชัน SwiftUI เปิดขึ้น เปิด 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 ก่อน บทแนะนำนี้ใช้การตรวจสอบสิทธิ์ด้วยอีเมล/รหัสผ่าน
- ในคอนโซล Firebase ให้ไปที่ความปลอดภัย > การตรวจสอบสิทธิ์ แล้วคลิกเริ่มต้นใช้งาน
- เลือกแท็บวิธีการลงชื่อเข้าใช้
- คลิกอีเมล/รหัสผ่าน คลิกปุ่มสลับเปิดใช้ แล้วคลิกบันทึก
รักษาความปลอดภัย Cloud Firestore ด้วยกฎการตรวจสอบสิทธิ์
จากนั้นกำหนดให้ผู้ใช้ต้องได้รับการตรวจสอบสิทธิ์ก่อนที่จะอ่านหรือเขียนข้อความ โดยทำดังนี้
- ในคอนโซล Firebase ให้ไปที่ฐานข้อมูลและพื้นที่เก็บข้อมูล > ฐานข้อมูล 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 แล้วแทนที่ 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)
}
}
ทดสอบการตรวจสอบสิทธิ์ผู้ใช้
- คลิกปุ่มเรียกใช้ ใน Xcode
- ในโปรแกรมจำลองแอป ให้คลิกลงชื่อสมัครใช้ ป้อนชื่อที่แสดง อีเมล และรหัสผ่าน แล้วแตะลงชื่อสมัครใช้
- ระบบควรตรวจสอบสิทธิ์และนำคุณไปยังหน้าจอการรับส่งข้อความที่ว่างเปล่าโดยอัตโนมัติ
6. อ่านจาก Cloud Firestore
เพิ่มข้อความตัวอย่างในคอนโซล Firebase
ป้อนข้อความตัวอย่างลงในฐานข้อมูลโดยทำดังนี้
- ในคอนโซล Firebase ให้ไปที่ฐานข้อมูล Firestore แล้วเลือกแท็บข้อมูล
- คลิกเริ่มคอลเล็กชัน
- ป้อน
messagesสำหรับรหัสคอลเล็กชัน แล้วคลิกถัดไป - ปล่อยให้รหัสเอกสาร ตั้งค่าเป็นสร้างอัตโนมัติ (หรือคลิกรหัสอัตโนมัติ)
- เพิ่มช่องต่อไปนี้ลงในเอกสาร
text(ประเภท:string, ค่า:Hello)displayName(ประเภท:string, ค่า:anonymous)
- คลิกบันทึก
- หรือคลิกเพิ่มเอกสาร เพื่อเพิ่มข้อความตัวอย่างเพิ่มเติม
ซิงค์ข้อความใน FriendlyMessageViewModel.swift
ใน SwiftUI โมเดล @Observable จะขับเคลื่อนการอัปเดต UI แบบประกาศ เปิด ViewModels/FriendlyMessageViewModel.swift แล้วใช้ AsyncSequence ของ snapshots เพื่อใช้ 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)
}
}
}
}
การเชื่อมโยงสถานะฐานข้อมูลกับสถานะ UI โดยตรงผ่าน View Model ช่วยให้โค้ดแอป SwiftUI สามารถจับคู่การเปลี่ยนภาพกับการเคลื่อนไหวได้อย่างชัดเจน ในขั้นตอนถัดไป คุณจะเขียนข้อมูลลงในฐานข้อมูลจากแอปเพื่อสังเกตการเคลื่อนไหวนี้
7. ส่งข้อความ
ใช้ sendMessage ด้วย Swift Concurrency
เมื่อคุณเพิ่มเอกสารลงในคอลเล็กชันโดยใช้ 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)
}
ทดสอบการส่งข้อความ
- คลิกปุ่มเรียกใช้ ใน Xcode
- ลงชื่อเข้าใช้บัญชีของคุณ
- พิมพ์ข้อความในช่องข้อความด้านล่างแล้วแตะไอคอนส่ง
- ดูข้อความปรากฏขึ้นในโปรแกรมจำลองทันที
8. จัดเก็บและรับรูปภาพ
ตั้งค่า Cloud Storage for Firebase
- ในคอนโซล Firebase ให้ไปที่ฐานข้อมูลและพื้นที่เก็บข้อมูล > พื้นที่เก็บข้อมูล แล้วคลิกเริ่มต้นใช้งาน
- เลือกเริ่มในโหมดทดสอบ แล้วคลิกถัดไป
- ยอมรับตำแหน่งพื้นที่เก็บข้อมูลเริ่มต้น แล้วคลิกเสร็จสิ้น
การเลือกรูปภาพแบบเนทีฟด้วย PhotosPicker ของ SwiftUI
SwiftUI สมัยใหม่ (iOS 16 ขึ้นไป) มีการเลือกรูปภาพแบบเนทีฟผ่าน PhotosPicker จาก import PhotosUI โดยไม่จำเป็นต้องใช้บริดจ์ผู้รับมอบสิทธิ์ UIKit หรือ Wrapper 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 ให้แทนที่ 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)")
}
}
การแชร์รูปภาพด้วย Cloud Storage
ใน Views/FriendlyMessageImageView.swift ระบบจะโหลด URL รูปภาพแบบไม่พร้อมกันโดยใช้ Swift Concurrency และ Cloud Storage SDK
- ระบบจะดึง URL ดาวน์โหลด HTTP/HTTPS มาตรฐานแบบไม่พร้อมกันผ่าน
URLSession - ระบบจะดึง URL ของ Google Cloud Storage (เช่น
gs://...) โดยใช้Storage.storage().reference(forURL:)
ทดสอบข้อความรูปภาพ
- คลิกปุ่มเรียกใช้ ใน Xcode
- ลงชื่อเข้าใช้บัญชีของคุณ
- แตะไอคอนรูปภาพ เลือกรูปภาพจากคลังภาพ แล้วดูรูปภาพอัปโหลดและปรากฏในแชท
9. บทสรุป
ขอแสดงความยินดี คุณสร้างแอปพลิเคชันแชท iOS แบบเรียลไทม์โดยใช้ Swift และ Firebase ได้สำเร็จ
สิ่งที่คุณได้เรียนรู้
- การผสานรวม Swift Package Manager (SPM) กับ Xcode สำหรับ
firebase-ios-sdk - เลย์เอาต์แบบประกาศ
PhotosPickerและการเลื่อนอัตโนมัติScrollViewReaderของ SwiftUI - Swift Concurrency (
async/await) ในการตรวจสอบสิทธิ์ Cloud Firestore และ Cloud Storage - การตรวจสอบสิทธิ์ด้วยอีเมล/รหัสผ่านและการตรวจสอบสิทธิ์แบบไม่ระบุตัวตน พร้อมการสังเกตสถานะแบบโต้ตอบ
- การฟังข้อมูลสแนปช็อตแบบเรียลไทม์และการเขียนรหัสอัตโนมัติของ Cloud Firestore
- การอัปโหลดไบนารีและ URL ดาวน์โหลดสาธารณะของ Cloud Storage for Firebase