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

1. نظرة عامة

2efe6805ef369641.png

مرحبًا بك في برنامج Friendly Chat التعليمي. في هذا الدرس العملي، ستتعلّم كيفية استخدام منصة Firebase لإنشاء تطبيقات iOS. ستنفّذ برنامجًا للدردشة وتراقب أدائه باستخدام Firebase.

ما ستتعلمه

  • السماح للمستخدمين بتسجيل الدخول
  • مزامنة البيانات باستخدام "قاعدة بيانات Firebase في الوقت الفعلي"
  • تخزين الملفات الثنائية في 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. (اختياري) فعِّل ميزة "المساعدة المستندة إلى الذكاء الاصطناعي" في وحدة تحكّم Firebase (المعروفة باسم "Gemini في Firebase").
  6. في هذا الدرس العملي، لا تحتاج إلى "إحصاءات Google"، لذا أوقِف خيار "إحصاءات Google".
  7. انقر على إنشاء مشروع، وانتظِر إلى أن يتم توفير مشروعك، ثم انقر على متابعة.

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

لاستخدام Cloud Storage for 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

استخدِم طريقة "الإعداد" في 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 Authentication نيابةً عن المستخدمين، عليك تفعيلها.

  1. انتقِل إلى وحدة تحكّم Firebase واختَر مشروعك.
  2. اختَر المصادقة.
  3. اختَر علامة التبويب طريقة تسجيل الدخول.
  4. انقر على مفتاح التبديل Google لتفعيله (اللون الأزرق).
  5. انقر على حفظ في مربّع الحوار الناتج

إذا ظهرت لك أخطاء لاحقًا في هذا الدرس العملي مع الرسالة "CONFIGURATION_NOT_FOUND"، ارجع إلى هذه الخطوة وتحقّق من عملك.

تأكيد الاعتمادية على Firebase Auth

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

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، اختَر عنصر قاعدة البيانات في شريط التنقّل الأيمن. في القائمة الكاملة لقاعدة البيانات، اختَر استيراد 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 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'

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

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

  1. في اللوحة اليمنى من وحدة تحكّم Firebase، وسِّع إنشاء، ثم اختَر مساحة التخزين.
  2. انقر على البدء.
  3. اختَر موقعًا جغرافيًا لحزمة Storage التلقائية.
    يمكن للحِزم في 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 Storage.

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

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

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

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