Firebase iOS Codelab Swift

1. סקירה כללית

2efe6805ef369641.png

ברוכים הבאים לסדנת הקוד Friendly Chat. ב-codelab הזה תלמדו איך להשתמש בפלטפורמת Firebase כדי ליצור אפליקציות ל-iOS. תטמיעו לקוח צ'אט ותעקבו אחרי הביצועים שלו באמצעות Firebase.

מה תלמדו

  • המשתמשים יוכלו להיכנס לחשבון.
  • סנכרון נתונים באמצעות Firebase Realtime Database.
  • אחסון קבצים בינאריים ב-Firebase Storage.

מה צריך

  • 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.pngהפעלה.

אחרי כמה שניות אמור להופיע מסך הבית של Friendly Chat. הממשק אמור להופיע. עם זאת, בשלב הזה אי אפשר להיכנס לחשבון, לשלוח או לקבל הודעות. האפליקציה תבטל את הפעולה עם חריגה עד שתשלימו את השלב הבא.

4. הגדרת פרויקט Firebase

יצירת פרויקט חדש ב-Firebase

  1. נכנסים למסוף Firebase באמצעות חשבון Google.
  2. לוחצים על הלחצן כדי ליצור פרויקט חדש, ואז מזינים שם לפרויקט (לדוגמה, FriendlyChat).
  3. לוחצים על המשך.
  4. אם מוצגת בקשה לעשות זאת, קוראים ומאשרים את התנאים של Firebase, ואז לוחצים על המשך.
  5. (אופציונלי) מפעילים את העזרה מבוססת-AI במסוף Firebase (שנקראת Gemini ב-Firebase).
  6. ב-codelab הזה לא צריך להשתמש ב-Google Analytics, ולכן משביתים את האפשרות Google Analytics.
  7. לוחצים על יצירת פרויקט, מחכים שהפרויקט יוקצה ולוחצים על המשך.

שדרוג תוכנית התמחור של Firebase

כדי להשתמש ב-Cloud Storage for Firebase, הפרויקט ב-Firebase צריך להיות בתוכנית התמחור 'תשלום לפי שימוש' (Blaze), כלומר הוא צריך להיות מקושר לחשבון לחיוב ב-Cloud.

  • בחשבון לחיוב ב-Cloud צריך להגדיר אמצעי תשלום, כמו כרטיס אשראי.
  • אם אתם חדשים ב-Firebase וב-Google Cloud, כדאי לבדוק אם אתם עומדים בדרישות לקבלת קרדיט בסך 300$וחשבון לחיוב ב-Cloud עם תקופת ניסיון בחינם.
  • אם אתם משתתפים בסדנת קוד כחלק מאירוע, כדאי לשאול את מארגן האירוע אם יש קרדיטים ל-Cloud.

כדי לשדרג את הפרויקט לתוכנית Blaze, פועלים לפי השלבים הבאים:

  1. במסוף Firebase, בוחרים באפשרות שדרוג התוכנית.
  2. בוחרים בתוכנית Blaze. פועלים לפי ההוראות במסך כדי לקשר חשבון לחיוב ב-Cloud לפרויקט.
    אם הייתם צריכים ליצור חשבון לחיוב ב-Cloud כחלק מהשדרוג, יכול להיות שתצטרכו לחזור לתהליך השדרוג במסוף Firebase כדי להשלים את השדרוג.

חיבור האפליקציה ל-iOS

  1. במסך Project Overview (סקירת הפרויקט) של הפרויקט החדש, לוחצים על Add Firebase to your iOS app (הוספת Firebase לאפליקציית iOS).
  2. מזינים את מזהה החבילה, בתור com.google.firebase.codelab.FriendlyChatSwift.
  3. מזינים את מזהה האפליקציה ב-App Store בתור 123456.
  4. לוחצים על Register App (רישום האפליקציה).

הוספת קובץ GoogleService-Info.plist לאפליקציה

במסך השני לוחצים על Download GoogleService-Info.plist (הורדה של GoogleService-Info.plist) כדי להוריד קובץ הגדרה שמכיל את כל המטא-נתונים הדרושים של Firebase לאפליקציה. מעתיקים את הקובץ הזה לאפליקציה ומוסיפים אותו ליעד FriendlyChatSwift.

עכשיו אפשר ללחוץ על ה-X בפינה השמאלית העליונה של החלון הקופץ כדי לסגור אותו – בלי לבצע את שלבים 3 ו-4 – כי תבצעו אותם כאן.

19d59efb213ddbdc.png

ייבוא מודול 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. זיהוי משתמשים

שימוש בכללים להגבלת הגישה למשתמשים מאומתים

עכשיו נוסיף כלל שידרוש אימות לפני קריאה או כתיבה של הודעות. לשם כך, אנחנו מוסיפים את הכללים הבאים לאובייקט הנתונים של ההודעות. בקטע Database (מסד נתונים) במסוף Firebase, בוחרים באפשרות Realtime Database (מסד נתונים בזמן אמת) ואז לוחצים על הכרטיסייה Rules (כללים). לאחר מכן מעדכנים את הכללים כך שייראו כך:

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

למידע נוסף על אופן הפעולה (כולל מסמכים על המשתנה auth), אפשר לעיין במסמכי האבטחה של Firebase.

הגדרת ממשקי API לאימות

כדי שהאפליקציה תוכל לגשת אל Firebase Authentication APIs בשם המשתמשים, צריך להפעיל אותה.

  1. עוברים אל מסוף Firebase ובוחרים את הפרויקט.
  2. בוחרים באפשרות אימות.
  3. בוחרים בכרטיסייה שיטת הכניסה.
  4. מעבירים את המתג Google למצב מופעל (כחול).
  5. לוחצים על שמירה בתיבת הדו-שיח שמופיעה.

אם בהמשך תופיע שגיאה עם ההודעה CONFIGURATION_NOT_FOUND, צריך לחזור לשלב הזה ולבדוק שוב את העבודה.

אישור התלות ב-Firebase Auth

מוודאים שקיימות תלויות של Firebase Auth בקובץ Podfile.

Podfile

pod 'Firebase/Auth'

הגדרת הקובץ Info.plist לכניסה באמצעות חשבון Google

תצטרכו להוסיף סכימת כתובות URL מותאמת אישית לפרויקט XCode.

  1. פותחים את הגדרות הפרויקט: לוחצים לחיצה כפולה על שם הפרויקט בתצוגת העץ בצד ימין. בוחרים את האפליקציה בקטע 'יעדים', ואז בוחרים בכרטיסייה 'מידע' ומרחיבים את הקטע 'סוגי כתובות URL'.
  2. לוחצים על הלחצן + ומוסיפים סכימת כתובות URL למזהה הלקוח ההפוך. כדי למצוא את הערך הזה, פותחים את קובץ ההגדרות GoogleService-Info.plist ומחפשים את המפתח REVERSED_CLIENT_ID. מעתיקים את הערך של המפתח ומדביקים אותו בתיבה URL Schemes (סכמות של כתובות URL) בדף ההגדרות. משאירים את שאר השדות ריקים.
  3. בסיום התהליך, ההגדרה אמורה להיראות בערך כך (אבל עם הערכים הספציפיים לאפליקציה שלכם):

1b54d5bd2f4f1448.png

הגדרת 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
}

הוספת handler לכניסה

אחרי שהכניסה באמצעות חשבון 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 כדי לאפשר למשתמש להיכנס לאפליקציה אחרי כניסה מוצלחת. ומסירים את ה-listener ב-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.pngהפעלה.
  2. תועברו באופן מיידי למסך הכניסה. מקישים על לחצן הכניסה לחשבון Google.
  3. אם הכול פעל כמו שצריך, תועברו למסך ההודעות.

6. הפעלת מסד נתונים בזמן אמת

2efe6805ef369641.png

ייבוא הודעות

בפרויקט במסוף Firebase, בוחרים באפשרות Database (מסד נתונים) בסרגל הניווט הימני. בתפריט האפשרויות הנוספות של מסד הנתונים, בוחרים באפשרות ייבוא JSON. עוברים אל קובץ initial_messages.json בספרייה friendlychat, בוחרים אותו ולוחצים על הלחצן ייבוא. הפעולה הזו תחליף את כל הנתונים שנמצאים כרגע במסד הנתונים. אפשר גם לערוך את מסד הנתונים ישירות, באמצעות סימן ה-'+' הירוק וסימן ה-'x' האדום כדי להוסיף פריטים ולהסיר פריטים.

20ccf4856b715b4c.png

אחרי הייבוא, מסד הנתונים אמור להיראות כך:

f3e0367f1c9cd187.png

אישור התלות במסד הנתונים של Firebase

בבלוק התלויות של הקובץ Podfile, מוודאים שהספרייה Firebase/Database נכללת.

Podfile

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
  }

בדיקת סנכרון הודעות

  1. לוחצים על הלחצן 98205811bbed9d74.pngהפעלה.
  2. לוחצים על הלחצן כניסה כדי להתחיל כדי לעבור לחלון ההודעות.
  3. מוסיפים הודעות חדשות ישירות במסוף Firebase. לשם כך, לוחצים על סמל הפלוס הירוק לצד הרשומה 'הודעות' ומוסיפים אובייקט כמו זה שבהמשך: f9876ffc8b316b14.png
  4. מוודאים שהם מופיעים בממשק המשתמש של Friendly-Chat.

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

בדיקת שליחת הודעות

  1. לוחצים על הלחצן 98205811bbed9d74.pngהפעלה.
  2. לוחצים על כניסה כדי לעבור לחלון ההודעות.
  3. מקלידים את ההודעה ולוחצים על סמל השליחה. ההודעה החדשה צריכה להיות גלויה בממשק המשתמש של האפליקציה ובמסוף Firebase.

8. אחסון וקבלה של תמונות

אישור התלות ב-Firebase Storage

בבלוק התלויות של Podfile, מוודאים ש-Firebase/Storage כלול.

Podfile

pod 'Firebase/Storage'

הגדרת Cloud Storage for Firebase

כך מגדירים את Cloud Storage for Firebase בפרויקט Firebase:

  1. בחלונית הימנית במסוף Firebase, מרחיבים את Build (פיתוח) ובוחרים באפשרות Storage (אחסון).
  2. לוחצים על תחילת העבודה.
  3. בוחרים מיקום לקטגוריית האחסון שמוגדרת כברירת מחדל.
    קטגוריות ב-US-WEST1, ב-US-CENTRAL1 וב-US-EAST1 יכולות ליהנות מהמסלול תמיד בחינם של Google Cloud Storage. התמחור והשימוש בקטגוריות בכל המיקומים האחרים מפורטים בתמחור ובשימוש ב-Google Cloud Storage.
  4. לוחצים על התחלה במצב בדיקה. קוראים את כתב הוויתור בנוגע לכללי האבטחה.
    בהמשך ה-codelab, תוסיפו כללי אבטחה כדי לאבטח את הנתונים. אל תפיצו או תחשפו אפליקציה לציבור בלי להוסיף כללי אבטחה לדלי שלכם ב-Storage.
  5. לוחצים על יצירה.

הגדרת FirebaseStorage

FCViewController.swift

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

קבלת תמונות בהודעות קיימות

מוסיפים קוד שמוריד תמונות מ-Firebase Storage.

משנים את השיטה FCViewController's "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 של האחסון של התמונה עם מסד הנתונים כדי שהתמונה תישלח בתוך ההודעה.

משנים את השיטה 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])
      }
    }
  }

בדיקה של שליחה וקבלה של הודעות עם תמונות

  1. לוחצים על הלחצן 98205811bbed9d74.pngהפעלה.
  2. לוחצים על כניסה כדי לעבור לחלון ההודעות.
  3. לוחצים על סמל הוספת התמונה כדי לבחור תמונה. ההודעה החדשה עם התמונה אמורה להופיע בממשק המשתמש של האפליקציה ובמסוף Firebase.

10. כל הכבוד!

השתמשתם ב-Firebase כדי ליצור בקלות אפליקציית צ'אט בזמן אמת.

מה למדנו

  • מסד נתונים בזמן אמת
  • כניסה מאוחדת
  • אחסון

מידע נוסף