درس تطبيقي حول الترميز في 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تشغيل.

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

4. إنشاء مشروع في وحدة تحكّم Firebase

إنشاء مشروع

من وحدة تحكُّم Firebase، اختَر إضافة مشروع.

أدخِل اسم المشروع FriendlyChat، ثم انقر على إنشاء مشروع.

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

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

لاستخدام Cloud Storage لبرنامج Firebase، يجب أن يكون مشروعك على Firebase مُدرَجًا في خطة الأسعار "الدفع حسب الاستخدام" (Blaze)، ما يعني أنّه مرتبط بحساب على "الفوترة في السحابة الإلكترونية".

لترقية مشروعك إلى خطة 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 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 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- تفعيل قاعدة بيانات "الوقت الفعلي"

2efe6805ef369641.png

استيراد الرسائل

في مشروعك في وحدة تحكّم Firebase، اختَر قاعدة البيانات في شريط التنقّل الأيمن. في القائمة المنسدلة لقاعدة البيانات، اختَر استيراد ملف 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. تأكَّد من ظهورها في واجهة مستخدم "المحادثة السهلة".

7- إرسال الرسائل

تنفيذ ميزة "إرسال رسالة"

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

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

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

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