Firebase iOS Codelab Swift

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

2efe6805ef369641.png

ברוכים הבאים ל-codelab של Friendly Chat. בקודלאב הזה תלמדו איך להשתמש בפלטפורמת 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, בוחרים באפשרות Add Project (הוספת פרויקט).

נותנים לפרמטר שם – FriendlyChat – ולוחצים על Create Project.

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

שדרוג של תוכנית התמחור ב-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 כדי להוריד קובץ תצורה שמכיל את כל המטא-נתונים הנדרשים של 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 לאימות

כדי שהאפליקציה תוכל לגשת לממשקי ה-API של אימות Firebase בשם המשתמשים, תצטרכו להפעיל אותה.

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

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

אימות התלות ב-Firebase Auth

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

Podfile

pod 'Firebase/Auth'

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

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

  1. פותחים את הגדרות הפרויקט: לוחצים לחיצה כפולה על שם הפרויקט בתצוגת העץ הימנית. בוחרים את האפליקציה בקטע TARGETS (יעדים), ואז בוחרים בכרטיסייה Info (פרטים) ומרחיבים את הקטע URL Types (סוגי כתובות URL).
  2. לוחצים על הלחצן + ומוסיפים סכימה של כתובת URL למזהה הלקוח ההפוך. כדי למצוא את הערך הזה, פותחים את קובץ התצורה GoogleService-Info.plist ומחפשים את המפתח REVERSED_CLIENT_ID. מעתיקים את הערך של המפתח ומדביקים אותו בתיבה 'סכמות של כתובות 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
}

הוספת הטיפול בהתחברות

אחרי שהתוצאה של הכניסה באמצעות חשבון 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 כדי לאפשר למשתמש להיכנס לאפליקציה אחרי כניסה מוצלחת. ומסירים את ה-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. הפעלת Realtime Database

2efe6805ef369641.png

ייבוא הודעות

בפרויקט במסוף Firebase, בוחרים באפשרות Database (מסד נתונים) בסרגל הניווט הימני. בתפריט האפשרויות הנוספות של מסד הנתונים, בוחרים באפשרות ייבוא JSON. עוברים לקובץ initial_messages.json בספריית friendlychat, בוחרים אותו ולוחצים על הלחצן Import. הפעולה הזו תחליף את כל הנתונים שנמצאים כרגע במסד הנתונים. אפשר גם לערוך את מסד הנתונים ישירות, באמצעות הסמל '+' הירוק והסמל '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. לוחצים על הלחצן Sign in to get started (כניסה לתחילת העבודה) כדי לעבור לחלון ההודעות.
  3. כדי להוסיף הודעות חדשות ישירות במסוף Firebase, לוחצים על סמל הפלוס הירוק לצד הרשומה messages ומוסיפים אובייקט כמו זה: f9876ffc8b316b14.png
  4. מוודאים שהם מופיעים בממשק המשתמש של Friendly-Chat.

7. שליחת הודעות

הטמעת Send Message

דחיפת ערכים למסד הנתונים. כשמשתמשים בשיטת ה-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. לוחצים על התחלה במצב בדיקה. קוראים את כתב הוויתור לגבי כללי האבטחה.
    בהמשך הסדנה תוסיפו כללי אבטחה כדי לאבטח את הנתונים. אין להפיץ או לחשוף אפליקציה באופן ציבורי בלי להוסיף כללי אבטחה לקטגוריית האחסון.
  5. לוחצים על יצירה.

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

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

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

10. כל הכבוד!

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

מה עסקנו בו

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

מידע נוסף