درس تطبيقي حول الترميز في Firebase على نظام التشغيل iOS Swift

1. نظرة عامة

2efe6805ef369641.png

مرحبًا بك في درس Friendly Chat التطبيقي حول الترميز. في هذا الدرس التطبيقي حول الترميز، ستتعلّم كيفية استخدام منصة 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.pngتشغيل.

من المفترض أن تظهر الشاشة الرئيسية لتطبيق Friendly Chat بعد بضع ثوانٍ. يجب أن تظهر واجهة المستخدم. ومع ذلك، لا يمكنك في هذه المرحلة تسجيل الدخول أو إرسال رسائل أو تلقّيها. سيتم إيقاف التطبيق مع ظهور خطأ إلى أن تُكمل الخطوة التالية.

4. إعداد مشروع Firebase

إنشاء مشروع Firebase جديد

  1. سجِّل الدخول إلى وحدة تحكّم Firebase باستخدام حساب Google.
  2. انقر على الزر لإنشاء مشروع جديد، ثم أدخِل اسم المشروع (على سبيل المثال، FriendlyChat).
  3. انقر على متابعة.
  4. إذا طُلب منك ذلك، راجِع بنود Firebase واقبلها، ثم انقر على متابعة.
  5. (اختياري) فعِّل ميزة "المساعدة المستندة إلى الذكاء الاصطناعي" في وحدة تحكّم Firebase (المعروفة باسم "Gemini في Firebase").
  6. في هذا الدرس العملي، لا تحتاج إلى "إحصاءات Google"، لذا أوقِف خيار "إحصاءات Google".
  7. انقر على إنشاء مشروع، وانتظِر إلى أن يتم توفير مشروعك، ثم انقر على متابعة.

ترقية خطة أسعار Firebase

لاستخدام مساحة تخزين سحابية لـ Firebase، يجب أن يكون مشروع Firebase الخاص بك ضمن خطة التسعير "الدفع حسب الاستخدام" (Blaze)، ما يعني أنّه مرتبط بحساب فوترة على Cloud.

لترقية مشروعك إلى خطة Blaze، اتّبِع الخطوات التالية:

  1. في "وحدة تحكّم Firebase"، اختَر ترقية خطتك.
  2. اختَر خطة Blaze. اتّبِع التعليمات الظاهرة على الشاشة لربط حساب فوترة على Cloud بمشروعك.
    إذا كان عليك إنشاء حساب فوترة على Cloud كجزء من عملية الترقية هذه، قد تحتاج إلى الرجوع إلى مسار الترقية في وحدة تحكّم Firebase لإكمال عملية الترقية.

ربط تطبيق iOS

  1. من شاشة "نظرة عامة على المشروع" لمشروعك الجديد، انقر على إضافة Firebase إلى تطبيق iOS.
  2. أدخِل معرّف الحزمة، مثل "com.google.firebase.codelab.FriendlyChatSwift".
  3. أدخِل معرّف App Store على النحو التالي: "123456".
  4. انقر على تسجيل التطبيق.

إضافة ملف 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- تحديد هوية المستخدمين

استخدام القواعد لحصر الوصول على المستخدمين الذين تم إثبات ملكيتهم

سنضيف الآن قاعدة تتطلّب المصادقة قبل قراءة أي رسائل أو كتابتها. لإجراء ذلك، نضيف القواعد التالية إلى عنصر بيانات الرسائل. من قسم "قاعدة البيانات" في وحدة تحكّم Firebase، اختَر "قاعدة بيانات الوقت الفعلي"، ثم انقر على علامة التبويب "القواعد". بعد ذلك، عدِّل القواعد لتصبح على النحو التالي:

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

لمزيد من المعلومات حول طريقة عمل ذلك (بما في ذلك المستندات المتعلقة بالمتغيّر "auth")، يُرجى الاطّلاع على مستندات الأمان في Firebase.

ضبط واجهات برمجة التطبيقات الخاصة بالمصادقة

قبل أن يتمكّن تطبيقك من الوصول إلى واجهات برمجة التطبيقات الخاصة بخدمة مصادقة Firebase نيابةً عن المستخدمين، عليك تفعيلها.

  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" في صفحة الإعدادات. اترك الحقول الأخرى فارغة.
  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 المصادقة للسماح للمستخدم بالدخول إلى التطبيق بعد تسجيل الدخول بنجاح. ويجب إزالة أداة المعالجة عند إيقاف التطبيق.

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، انقر على العنصر قاعدة البيانات في شريط التنقّل الأيمن. في القائمة الكاملة لقاعدة البيانات، اختَر استيراد JSON. انتقِل إلى ملف initial_messages.json في دليل friendlychat، واختَره، ثم انقر على الزر استيراد. سيؤدي هذا الإجراء إلى استبدال أي بيانات حالية في قاعدة البيانات. يمكنك أيضًا تعديل قاعدة البيانات مباشرةً، باستخدام علامة الجمع الخضراء وعلامة الضرب الحمراء لإضافة عناصر وإزالتها.

20ccf4856b715b4c.png

بعد الاستيراد، من المفترض أن تبدو قاعدة البيانات على النحو التالي:

f3e0367f1c9cd187.png

تأكيد الاعتماد على قاعدة بيانات Firebase

في قسم التبعيات من الملف Podfile، تأكَّد من تضمين Firebase/Database.

Podfile

pod 'Firebase/Database'

مزامنة الرسائل الحالية

أضِف رمزًا برمجيًا يتيح مزامنة الرسائل المُضافة حديثًا مع واجهة مستخدم التطبيق.

سيؤدي الرمز الذي تضيفه في هذا القسم إلى ما يلي:

  • إعداد قاعدة بيانات 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.pngتشغيل.
  2. انقر على الزر تسجيل الدخول لبدء المراسلة للانتقال إلى نافذة الرسائل.
  3. أضِف رسائل جديدة مباشرةً في وحدة تحكّم Firebase من خلال النقر على رمز علامة الجمع الخضراء بجانب الإدخال "الرسائل" وإضافة عنصر مثل ما يلي: f9876ffc8b316b14.png
  4. تأكَّد من ظهورها في واجهة مستخدم Friendly-Chat.

7. إرسال الرسائل

تنفيذ وظيفة "إرسال رسالة"

إرسال القيم إلى قاعدة البيانات عند استخدام طريقة push لإضافة بيانات إلى قاعدة بيانات Firebase في الوقت الفعلي، سيتمّ إضافة معرّف تلقائي. تكون أرقام التعريف التي يتم إنشاؤها تلقائيًا متسلسلة، ما يضمن إضافة الرسائل الجديدة بالترتيب الصحيح.

عدِّل طريقة "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"

في قسم التبعيات في ملف Podfile، تأكَّد من تضمين Firebase/Storage.

Podfile

pod 'Firebase/Storage'

إعداد مساحة تخزين سحابية لـ Firebase

في ما يلي كيفية إعداد مساحة تخزين سحابية لـ Firebase في مشروع Firebase:

  1. في اللوحة اليمنى من وحدة تحكّم Firebase، وسِّع إنشاء، ثم اختَر مساحة التخزين.
  2. انقر على البدء.
  3. اختَر موقعًا جغرافيًا لحزمة التخزين التلقائية.
    يمكن للحِزم في US-WEST1 وUS-CENTRAL1 وUS-EAST1 الاستفادة من الفئة"دائمًا مجانية" في Google Cloud Storage. تخضع الحِزم في جميع المواقع الجغرافية الأخرى لأسعار واستخدام Google Cloud Storage.
  4. انقر على بدء التشغيل في وضع الاختبار. اقرأ بيان إخلاء المسؤولية حول قواعد الأمان.
    في وقت لاحق من هذا الدرس العملي، ستضيف قواعد أمان لحماية بياناتك. لا توزّع تطبيقًا أو تعرضه للجميع بدون إضافة "قواعد الأمان" لحزمة Cloud Storage.
  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 الخاص بمساحة تخزين هذه الصورة مع قاعدة البيانات ليتم إرسال هذه الصورة داخل الرسالة.

عدِّل طريقة "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 لإنشاء تطبيق دردشة في الوقت الفعلي بسهولة.

المواضيع التي تناولناها

  • قاعدة بيانات الوقت الفعلي
  • تسجيل الدخول الموحّد
  • التخزين

مزيد من المعلومات