إضافة ميزة "تسجيل الدخول باستخدام حساب Google" إلى تطبيق iOS

1. قبل البدء

يرشدك هذا الدرس التطبيقي حول الترميز إلى كيفية إنشاء تطبيق iOS يستخدم ميزة "تسجيل الدخول باستخدام حساب Google" ويعمل في محاكي. يتم توفير عمليات تنفيذ تستخدم كلاً من SwiftUI وUIKit.

‫SwiftUI هو إطار عمل حديث لواجهة المستخدم من Apple لتطوير التطبيقات الجديدة. تتيح هذه الأداة إنشاء واجهات مستخدم لجميع منصات Apple من قاعدة رموز برمجية مشتركة واحدة. يتطلّب الإصدار 13 من نظام التشغيل iOS كحد أدنى.

‫UIKit هو إطار عمل واجهة المستخدم الأصلي والأساسي من Apple لنظام التشغيل iOS. ويوفّر توافقًا مع الإصدارات القديمة من نظام التشغيل iOS. وهذا يجعلها خيارًا جيدًا للتطبيقات المعروفة التي تحتاج إلى التوافق مع مجموعة متنوعة من الأجهزة القديمة.

يمكنك اتّباع مسار إطار العمل الذي يتوافق بشكل أفضل مع احتياجاتك المتعلقة بالتطوير.

المتطلبات الأساسية

  • معرفة أساسية بلغة Swift
  • معرفة أساسية بإطار عمل SwiftUI أو UIKit

أهداف الدورة التعليمية

  • كيفية إنشاء مشروع على Google Cloud
  • كيفية إنشاء برامج تعتمد على بروتوكول OAuth في Google Cloud Console
  • كيفية تنفيذ ميزة "تسجيل الدخول باستخدام حساب Google" لتطبيق iOS
  • كيفية تخصيص زر "تسجيل الدخول باستخدام حساب Google"
  • كيفية فك ترميز رمز التعريف
  • كيفية تفعيل خدمة App Check لتطبيق iOS

المتطلبات

  • إصدار حالي من Xcode
  • جهاز كمبيوتر يعمل بنظام التشغيل macOS يستوفي متطلبات النظام لإصدار Xcode الذي ثبّته

تم إنشاء هذا الدرس التطبيقي حول الترميز باستخدام Xcode 16.3 مع محاكي iOS 18.3. يجب استخدام أحدث إصدار من Xcode للتطوير.

2. إنشاء مشروع Xcode جديد

  1. افتح Xcode وانقر على Create a new Xcode project (إنشاء مشروع Xcode جديد).
  2. اختَر علامة التبويب iOS، ثم اختَر نموذج التطبيق، وانقر على التالي.

صفحة نموذج إنشاء مشروع Xcode

  1. في خيارات المشروع:
    • أدخِل اسم المنتج.
    • اختَر الفريق بشكل اختياري.
    • أدخِل معرّف المؤسسة.
    • دوِّن معرّف الحزمة الذي تم إنشاؤه. ستحتاج إليه لاحقًا.
    • بالنسبة إلى الواجهة، اختَر أحد الخيارَين التاليَين:
      • SwiftUI لتطبيق يستند إلى SwiftUI
      • مخطط القصة لتطبيق يستند إلى UIKit
    • اختَر Swift في اللغة.
    • انقر على التالي واختَر موقعًا جغرافيًا لحفظ مشروعك.

صفحة خيارات مشروع Xcode

3- إنشاء عميل OAuth

للسماح لتطبيقك بالتواصل مع خدمات المصادقة من Google، عليك إنشاء معرّف عميل OAuth. يتطلّب ذلك مشروعًا على Google Cloud. ستساعدك الخطوات التالية في عملية إنشاء مشروع ومعرّف عميل OAuth.

اختيار مشروع Google Cloud أو إنشاؤه

  1. انتقِل إلى Google Cloud Console واختَر مشروعًا أو أنشِئ مشروعًا. في حال اختيار مشروع حالي، ستوجّهك وحدة التحكّم تلقائيًا إلى الخطوة التالية المطلوبة.

صفحة اختيار المشروع في Google Cloud Console

  1. أدخِل اسمًا لمشروعك الجديد على Google Cloud.
  2. انقر على إنشاء.

صفحة اختيار المشروع في Google Cloud Console

إذا سبق لك ضبط شاشة طلب الموافقة للمشروع المحدّد، لن يُطلب منك ضبطها الآن. في هذه الحالة، يمكنك تخطّي هذا القسم والانتقال إلى إنشاء عميل OAuth 2.0.

  1. انقر على إعداد شاشة الموافقة.

صفحة إنشاء عميل OAuth في Google Cloud Console مع شرط إعداد شاشة الموافقة

  1. انقر على البدء في صفحة "وضع العلامة التجارية".

صفحة البدء الخاصة بالعلامة التجارية في Google Cloud Console

  1. في صفحة إعدادات المشروع، املأ الحقول التالية:
    • معلومات التطبيق: أدخِل اسمًا وبريدًا إلكترونيًا لدعم المستخدمين في تطبيقك. سيتم عرض عنوان البريد الإلكتروني هذا بشكل علني ليتمكّن المستخدمون من التواصل معك لطرح أسئلة حول موافقتهم.
    • الجمهور: اختَر خارجي.
    • معلومات الاتصال: أدخِل عنوان بريد إلكتروني لكي تتمكّن Google من التواصل معك بشأن مشروعك.
    • راجِع خدمات Google API: سياسة بيانات المستخدمين.
    • انقر على إنشاء.

صفحة إعدادات العلامة التجارية للعميل في Google Cloud Console

  1. اختَر صفحة العملاء في قائمة التنقّل.
  2. انقر على إنشاء عميل.

صفحة عملاء مشروع Google Cloud

إنشاء عميل OAuth 2.0

  1. اختَر iOS في حقل نوع التطبيق.
  2. أدخِل اسمًا لعميلك.
  3. أدخِل معرّف الحزمة الذي تم إنشاؤه في الخطوة الأخيرة.
  4. أدخِل معرّف الفريق الذي خصّصته Apple لفريقك. هذه الخطوة اختيارية في الوقت الحالي، ولكن يجب توفير معرّف فريق لتفعيل App Check لاحقًا في هذا الدرس العملي.
  5. انقر على إنشاء.

صفحة إدخال تفاصيل عميل OAuth

  1. انسخ معرّف العميل من نافذة الحوار، وستحتاج إليه لاحقًا.
  2. نزِّل ملف plist للرجوع إليه لاحقًا.

مربّع حوار تم إنشاء معرّف عميل OAuth

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

الخطوة التالية هي إعداد مشروع Xcode للعمل مع حزمة تطوير البرامج (SDK) الخاصة بخدمة "تسجيل الدخول باستخدام Google". تتضمّن هذه العملية إضافة حزمة SDK إلى مشروعك كعنصر تابع وتعديل إعدادات مشروعك باستخدام معرّف عميل فريد. يسمح هذا المعرّف لحزمة SDK بالتواصل بأمان مع خدمة المصادقة من Google أثناء عملية تسجيل الدخول.

تثبيت التبعيات الخاصة بميزة "تسجيل الدخول باستخدام حساب Google"

  1. افتح مشروع Xcode.
  2. انتقِل إلى ملف (File) > إضافة تبعيات الحزمة (Add Package Dependencies).
  3. في شريط البحث، أدخِل عنوان URL لمستودع "تسجيل الدخول باستخدام Google": https://github.com/google/GoogleSignIn-iOS

العثور على تبعية "تسجيل الدخول باستخدام حساب Google" في Swift Package Manager

  1. انقر على إضافة حزمة.
  2. اختَر هدف التطبيق الرئيسي لحزمة GoogleSignIn.
  3. إذا كنت تستخدم SwiftUI، اختَر هدف التطبيق الرئيسي لحزمة GoogleSignInSwift. إذا كنت تخطّط لاستخدام UIKit، لا تحدّد هدفًا لهذه الحزمة.
  4. انقر على إضافة حزمة.

إضافة تبعية "تسجيل الدخول باستخدام Google" إلى مشروعك

ضبط بيانات اعتماد تطبيقك

  1. في "مستكشف المشروع" (Project Navigator)، انقر على جذر مشروعك.
  2. في منطقة المحرّر الرئيسية، اختَر هدف تطبيقك الرئيسي من قائمة الأهداف.
  3. انقر على علامة التبويب معلومات في أعلى مساحة المحرّر.
  4. مرِّر مؤشر الماوس فوق الصف الأخير في قسم خصائص استهداف iOS المخصّصة وانقر على الزر + الذي يظهر.

إضافة مفتاح استهداف جديد إلى خصائص الاستهداف على iOS

  1. في عمود المفتاح، اكتب GIDClientID.
  2. في عمود القيمة، ألصِق معرّف العميل الذي نسخته من Google Cloud Console.

إضافة GIDClientID إلى هدف التطبيق الرئيسي

  1. افتح ملف plist الذي نزّلته من Google Cloud Console.
  2. انسخ قيمة معرّف العميل المعكوس.

ملف plist في "وحدة تحكّم Google Cloud"

  1. وسِّع أنواع عناوين URL في أسفل علامة التبويب المعلومات.
  2. انقر على الزر +.
  3. أدخِل معرّف العميل المعكوس في المربّع مخططات عناوين URL.

إضافة مفتاح URLSchemes إلى هدف التطبيق الرئيسي

نحن الآن جاهزون لبدء إضافة زر تسجيل الدخول إلى تطبيقنا.

5- إضافة زر تسجيل الدخول

بعد إعداد مشروع Xcode، حان الوقت لبدء إضافة زر "تسجيل الدخول باستخدام حساب Google" إلى التطبيق.

المنطق الأساسي لهذه الخطوة هو استدعاء GIDSignIn.sharedInstance.signIn. تبدأ هذه الطريقة عملية المصادقة، وتمنح التحكّم في واجهة برمجة التطبيقات Sign in with Google SDK لعرض عملية "تسجيل الدخول باستخدام حساب Google" للمستخدم.

SwiftUI

  1. ابحث عن الملف ContentView.swift في "مستكشف المشروع" (Project Navigator) في Xcode.
  2. استبدِل محتوى هذا الملف بالنص التالي:
import GoogleSignIn
import GoogleSignInSwift
import SwiftUI

struct ContentView: View {
  var body: some View {
    VStack {
      GoogleSignInButton(action: handleSignInButton).padding()
    }
  }

  func handleSignInButton() {
    // Find the current window scene.
    guard let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene else {
      print("There is no active window scene")
      return
    }

    // Get the root view controller from the window scene.
    guard
      let rootViewController = windowScene.windows.first(where: { $0.isKeyWindow })?
        .rootViewController
    else {
      print("There is no key window or root view controller")
      return
    }

    // Start the sign-in process.
    GIDSignIn.sharedInstance.signIn(
      withPresenting: rootViewController
    ) { signInResult, error in
      guard let result = signInResult else {
        // Inspect error
        print("Error signing in: \(error?.localizedDescription ?? "No error description")")
        return
      }
      // If sign in succeeded, display the app's main content View.
      print("ID Token: \(result.user.idToken?.tokenString ?? "")")
    }
  }
}

#Preview {
  ContentView()
}

زر "تسجيل الدخول باستخدام حساب Google" في إطار عمل SwiftUI على محاكي iOS

UIKit

  1. ابحث عن الملف ViewController.swift في "مستكشف المشروع" (Project Navigator) في Xcode.
  2. استبدِل محتوى هذا الملف بالنص التالي:
import GoogleSignIn
import UIKit

class ViewController: UIViewController {

  // Create an instance of the Sign in with Google button
  let signInButton = GIDSignInButton()

  override func viewDidLoad() {
    super.viewDidLoad()

    // Add the sign-in button to your view
    view.addSubview(signInButton)

    // Position the button using constraints
    signInButton.translatesAutoresizingMaskIntoConstraints = false
    NSLayoutConstraint.activate([
      signInButton.centerXAnchor.constraint(equalTo: view.centerXAnchor),
      signInButton.centerYAnchor.constraint(equalTo: view.centerYAnchor),
    ])

    // Add a target to the button to call a method when it's pressed
    signInButton.addTarget(self, action: #selector(signInButtonTapped), for: .touchUpInside)
  }

  // This method is called when the sign-in button is pressed.
  @objc func signInButtonTapped() {
    // Start the sign-in process.
    GIDSignIn.sharedInstance.signIn(withPresenting: self) { signInResult, error in
      guard let result = signInResult else {
        // Inspect error
        print("Error signing in: \(error?.localizedDescription ?? "No error description")")
        return
      }

      // If sign in succeeded, print the ID token.
      print("ID Token: \(result.user.idToken?.tokenString ?? "")")
    }
  }
}

زر "تسجيل الدخول باستخدام حساب Google" في إطار عمل UIKit على محاكي iOS

عرض زر تسجيل الدخول

شغِّل تطبيقك في المحاكي. سيظهر لك الزر "تسجيل الدخول باستخدام حساب Google"، ولكنّه لن يعمل بشكل صحيح بعد. وهذا أمر متوقّع، إذ لا يزال عليك تنفيذ الرمز البرمجي للتعامل مع إعادة التوجيه إلى تطبيقك بعد مصادقة المستخدم.

6. تخصيص زر تسجيل الدخول

يمكنك تخصيص زر "تسجيل الدخول باستخدام Google" التلقائي ليتناسب بشكل أفضل مع مظهر تطبيقك. تتيح لك حزمة تطوير البرامج (SDK) الخاصة بخدمة "تسجيل الدخول باستخدام حساب Google" تعديل نظام الألوان ونمط الزر.

SwiftUI

تتم إضافة الزر التلقائي إلى الصفحة باستخدام سطر الرمز التالي:

GoogleSignInButton(action: handleSignInButton)

يتم تخصيص GoogleSignInButton من خلال تمرير المَعلمات إلى أداة التهيئة. سيؤدي الرمز التالي إلى عرض زر تسجيل الدخول في الوضع الداكن.

  1. افتح ContentView.swift
  2. عدِّل أداة التهيئة الخاصة بـ GoogleSignInButton لتتضمّن القيم التالية:
GoogleSignInButton(
  scheme: .dark,  // Options: .light, .dark, .auto
  style: .standard,  // Options: .standard, .wide, .icon
  state: .normal,  // Options: .normal, .disabled
  action: handleSignInButton
).padding()

زر "تسجيل الدخول باستخدام حساب Google" في "الوضع الداكن" ضمن إطار عمل SwiftUI على محاكي iOS

لمزيد من المعلومات عن خيارات التخصيص، يُرجى الاطّلاع على مرجع إطار عمل GoogleSignInSwift.

UIKit

يتم إنشاء الزر التلقائي باستخدام أسطر الرمز البرمجي التالية:

// Create an instance of the Sign in with Google button
let signInButton = GIDSignInButton()

// Add the button to your view
view.addSubview(signInButton)

يتم تخصيص GIDSignInButton من خلال ضبط الخصائص على مثيل الزر. سيؤدي الرمز التالي إلى عرض زر تسجيل الدخول في الوضع الداكن.

  1. افتح ViewController.swift.
  2. أضِف أسطر الرمز البرمجي التالية قبل إضافة زر تسجيل الدخول إلى طريقة العرض في الدالة viewDidLoad:
// Set the width and color of the sign-in button
signInButton.style = .standard  // Options: .standard, .wide, .iconOnly
signInButton.colorScheme = .dark  // Options: .dark, .light

زر "تسجيل الدخول باستخدام حساب Google" في وضع "المظهر الداكن" ضمن إطار عمل UIKit على محاكي iOS

لمزيد من المعلومات حول التخصيص، اطّلِع على مرجع إطار عمل GoogleSignIn.

7. التعامل مع عنوان URL لإعادة التوجيه الخاص بالمصادقة

بعد إضافة زر تسجيل الدخول، تتمثّل الخطوة التالية في التعامل مع عملية إعادة التوجيه التي تحدث بعد مصادقة المستخدم. بعد المصادقة، تعرض Google عنوان URL يتضمّن رمز تفويض مؤقتًا. لإكمال عملية تسجيل الدخول، يعترض معالج عنوان URL هذا ويمرّره إلى حزمة تطوير البرامج (SDK) الخاصة بخدمة "تسجيل الدخول باستخدام حساب Google" ليتم استبداله برمز مميّز معرّف موقَّع (JWT).

SwiftUI

  1. افتح الملف الذي يحتوي على بنية App. يتم تسمية هذا الملف استنادًا إلى مشروعك، لذا سيكون الاسم على النحو التالي: YourProjectNameApp.swift.
  2. استبدِل محتوى هذا الملف بالنص التالي:
import GoogleSignIn
import SwiftUI

@main
struct iOS_Sign_in_with_Google_App: App {
  var body: some Scene {
    WindowGroup {
      ContentView()

        .onOpenURL { url in
          GIDSignIn.sharedInstance.handle(url)
        }
    }
  }
}

UIKit

  1. افتح الملف AppDelegate.swift.
  2. أضِف عملية الاستيراد التالية إلى أعلى الملف:
import GoogleSignIn
  1. أضِف دالة معالج المصادقة التالية داخل الفئة AppDelegate. يمكنك وضعها مباشرةً بعد القوس المعقوف الأخير الخاص بالطريقة application(_:didFinishLaunchingWithOptions:):
func application(
  _ app: UIApplication,
  open url: URL,
  options: [UIApplication.OpenURLOptionsKey: Any] = [:]
) -> Bool {
  var handled: Bool

  handled = GIDSignIn.sharedInstance.handle(url)
  if handled {
    return true
  }
  // If not handled by this app, return false.
  return false
}

بعد إجراء هذه التغييرات، من المفترض أن يبدو ملف AppDelegate.swift على النحو التالي:

import GoogleSignIn
import UIKit

@main
class AppDelegate: UIResponder, UIApplicationDelegate {

  func application(
    _ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
  ) -> Bool {
    // Override point for customization after application launch.
    return true
  }

  func application(
    _ app: UIApplication,
    open url: URL,
    options: [UIApplication.OpenURLOptionsKey: Any] = [:]
  ) -> Bool {
    var handled: Bool

    handled = GIDSignIn.sharedInstance.handle(url)
    if handled {
      return true
    }
    // If not handled by this app, return false.
    return false
  }

  // MARK: UISceneSession Lifecycle

  func application(
    _ application: UIApplication,
    configurationForConnecting connectingSceneSession: UISceneSession,
    options: UIScene.ConnectionOptions
  ) -> UISceneConfiguration {
    // Called when a new scene session is being created.
    // Use this method to select a configuration to create the new scene with.
    return UISceneConfiguration(
      name: "Default Configuration",
      sessionRole: connectingSceneSession.role
    )
  }

  func application(
    _ application: UIApplication,
    didDiscardSceneSessions sceneSessions: Set<UISceneSession>
  ) {
    // Called when the user discards a scene session.
    // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions.
    // Use this method to release any resources that were specific to the discarded scenes, as they will not return.
  }
}

اختبار خطوات تسجيل الدخول

يمكنك الآن اختبار عملية تسجيل الدخول الكاملة.

شغِّل تطبيقك وانقر على زر تسجيل الدخول. بعد إثبات هويتك، ستعرض Google شاشة موافقة يمكنك من خلالها منح التطبيق الإذن بالوصول إلى معلوماتك. بعد الموافقة، سيتم إكمال عملية تسجيل الدخول، وستتم إعادتك إلى التطبيق.

عند اكتمال عملية تسجيل الدخول بنجاح، يخزِّن حزمة تطوير البرامج (SDK) الخاصة بميزة "تسجيل الدخول باستخدام حساب Google" بيانات اعتماد المستخدم بشكل آمن في Keychain على الجهاز. ويمكن استخدام بيانات الاعتماد هذه لاحقًا للسماح للمستخدم بالبقاء مسجّلاً الدخول عند تشغيل التطبيق في المرات اللاحقة.

8. إضافة زر تسجيل الخروج

بعد أن أصبح تسجيل الدخول متاحًا، الخطوة التالية هي إضافة زر تسجيل الخروج وتعديل واجهة المستخدم لتعكس حالة تسجيل الدخول الحالية للمستخدم. عند نجاح عملية تسجيل الدخول، توفّر حزمة SDK عنصر GIDGoogleUser. يحتوي هذا العنصر على السمة profile التي تتضمّن معلومات أساسية، مثل اسم المستخدم وعنوان بريده الإلكتروني، والتي ستستخدمها لتخصيص واجهة المستخدم.

SwiftUI

  1. افتح ملف ContentView.swift.
  2. أضِف متغيّر حالة في أعلى بنية ContentView. سيحتوي هذا المتغيّر على معلومات المستخدم بعد تسجيل الدخول. بما أنّها متغيّر @State، ستعدّل SwiftUI واجهة المستخدم تلقائيًا كلما تغيّرت قيمتها:
struct ContentView: View {
  @State private var user: GIDGoogleUser?
}
  1. استبدِل body الحالي في بنية ContentView بما يلي: VStack. سيتحقّق هذا الرمز مما إذا كان متغيّر الحالة user يتضمّن مستخدمًا. في حال توفّرها، ستظهر رسالة ترحيب وزر تسجيل الخروج. إذا لم يكن كذلك، سيظهر زر "تسجيل الدخول باستخدام حساب Google" الأصلي:
var body: some View {
  VStack {
    // Check if the user is signed in.
    if let user = user {
      // If signed in, show a welcome message and the sign-out button.
      Text("Hello, \(user.profile?.givenName ?? "User")!")
        .font(.title)
        .padding()

      Button("Sign Out", action: signOut)
        .buttonStyle(.borderedProminent)

    } else {
      // If not signed in, show the "Sign in with Google" button.
      GoogleSignInButton(
        scheme: .dark,  // Options: .light, .dark, .auto
        style: .standard,  // Options: .standard, .wide, .icon
        state: .normal,  // Options: .normal, .disabled
        action: handleSignInButton
      ).padding()
    }
  }
}
  1. عدِّل كتلة الإكمال handleSignInButton لتعيين signInResult.user إلى المتغيّر الجديد user. إليك ما يؤدي إلى تبديل واجهة المستخدم إلى العرض الخاص بالمستخدمين الذين سجّلوا الدخول:
func handleSignInButton() {
  // Find the current window scene.
  guard let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene else {
    print("There is no active window scene")
    return
  }

  // Get the root view controller from the window scene.
  guard
    let rootViewController = windowScene.windows.first(where: { $0.isKeyWindow })?
      .rootViewController
  else {
    print("There is no key window or root view controller")
    return
  }

  // Start the sign-in process.
  GIDSignIn.sharedInstance.signIn(
    withPresenting: rootViewController
  ) { signInResult, error in
    guard let result = signInResult else {
      // Inspect error
      print("Error signing in: \(error?.localizedDescription ?? "No error description")")
      return
    }

    DispatchQueue.main.async {
      self.user = result.user
    }

    // If sign in succeeded, display the app's main content View.
    print("ID Token: \(result.user.idToken?.tokenString ?? "")")
  }
}
  1. أضِف دالة signOut جديدة إلى أسفل بنية ContentView ليتم استدعاؤها من خلال زر تسجيل الخروج:
func signOut() {
  GIDSignIn.sharedInstance.signOut()
  // After signing out, set the `user` state variable to `nil`.
  self.user = nil
}

افتح التطبيق وسجِّل الدخول. من المفترض أن تتغيّر واجهة المستخدم بعد إتمام عملية المصادقة بنجاح.

حالة تسجيل الدخول في إطار عمل SwiftUI على محاكي iOS

بعد إجراء هذه التغييرات، من المفترض أن يبدو ملف ContentView.swift على النحو التالي:

import GoogleSignIn
import GoogleSignInSwift
import SwiftUI

struct ContentView: View {

  @State private var user: GIDGoogleUser?

  var body: some View {
    VStack {
      // Check if the user is signed in.
      if let user = user {
        // If signed in, show a welcome message and the sign-out button.
        Text("Hello, \(user.profile?.givenName ?? "User")!")
          .font(.title)
          .padding()

        Button("Sign Out", action: signOut)
          .buttonStyle(.borderedProminent)

      } else {
        // If not signed in, show the "Sign in with Google" button.
        GoogleSignInButton(
          scheme: .dark,  // Options: .light, .dark, .auto
          style: .standard,  // Options: .standard, .wide, .icon
          state: .normal,  // Options: .normal, .disabled
          action: handleSignInButton
        ).padding()
      }
    }
  }

  func handleSignInButton() {
    // Find the current window scene.
    guard let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene else {
      print("There is no active window scene")
      return
    }

    // Get the root view controller from the window scene.
    guard
      let rootViewController = windowScene.windows.first(where: { $0.isKeyWindow })?
        .rootViewController
    else {
      print("There is no key window or root view controller")
      return
    }

    // Start the sign-in process.
    GIDSignIn.sharedInstance.signIn(
      withPresenting: rootViewController
    ) { signInResult, error in
      guard let result = signInResult else {
        // Inspect error
        print("Error signing in: \(error?.localizedDescription ?? "No error description")")
        return
      }

      DispatchQueue.main.async {
        self.user = result.user
      }

      // If sign in succeeded, display the app's main content View.
      print("ID Token: \(result.user.idToken?.tokenString ?? "")")
    }
  }

  func signOut() {
    GIDSignIn.sharedInstance.signOut()
    // After signing out, set the `user` state variable to `nil`.
    self.user = nil
  }
}

#Preview {
  ContentView()
}

UIKit

  1. افتح ViewController.swift.
  2. في أعلى ViewController، أسفل المكان الذي حدّدت فيه signInButton مباشرةً، أضِف زر تسجيل الخروج وتصنيف الترحيب:
let signOutButton = UIButton(type: .system)
let welcomeLabel = UILabel()
  1. أضِف الدالة التالية إلى أسفل ViewController. ستعرض هذه الدالة واجهة مستخدم مختلفة للمستخدم استنادًا إلى حالة تسجيل الدخول:
private func updateUI(for user: GIDGoogleUser?) {
  if let user = user {
    // User is signed in.
    signInButton.isHidden = true
    signOutButton.isHidden = false
    welcomeLabel.isHidden = false
    welcomeLabel.text = "Hello, \(user.profile?.givenName ?? "User")!"
  } else {
    // User is signed out.
    signInButton.isHidden = false
    signOutButton.isHidden = true
    welcomeLabel.isHidden = true
  }
}
  1. في أسفل الدالة viewDidLoad، أضِف الرمز التالي لإضافة تصنيف الترحيب وزر تسجيل الخروج إلى العرض:
// --- Set up the Welcome Label ---
welcomeLabel.translatesAutoresizingMaskIntoConstraints = false
welcomeLabel.textAlignment = .center
welcomeLabel.font = .systemFont(ofSize: 24, weight: .bold)
view.addSubview(welcomeLabel)

NSLayoutConstraint.activate([
  welcomeLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor),
  welcomeLabel.bottomAnchor.constraint(equalTo: signInButton.topAnchor, constant: -20),
])

// --- Set up the Sign-Out Button ---
signOutButton.translatesAutoresizingMaskIntoConstraints = false
signOutButton.setTitle("Sign Out", for: .normal)
view.addSubview(signOutButton)

NSLayoutConstraint.activate([
  signOutButton.centerXAnchor.constraint(equalTo: view.centerXAnchor),
  signOutButton.topAnchor.constraint(equalTo: signInButton.bottomAnchor, constant: 20),
])

signOutButton.addTarget(self, action: #selector(signOutButtonTapped), for: .touchUpInside)

// --- Set Initial UI State ---
updateUI(for: nil)
  1. عدِّل الدالة signInButtonTapped لاستدعاء الطريقة UpdateUI عند تسجيل الدخول بنجاح:
@objc func signInButtonTapped() {
  // Start the sign-in process.
  GIDSignIn.sharedInstance.signIn(withPresenting: self) { signInResult, error in
    guard let result = signInResult else {
      // Inspect error
      print("Error signing in: \(error?.localizedDescription ?? "No error description")")
      return
    }

    // If sign in succeeded, print the ID token.
    print("ID Token: \(result.user.idToken?.tokenString ?? "")")

    DispatchQueue.main.async {
      self.updateUI(for: result.user)
    }
  }
}
  1. أخيرًا، أضِف الدالة signOutButtonTapped إلى ViewController للتعامل مع عملية تسجيل الخروج:
@objc func signOutButtonTapped() {
  GIDSignIn.sharedInstance.signOut()
  // Update the UI for the signed-out state.
  updateUI(for: nil)
}

افتح التطبيق وسجِّل الدخول. من المفترض أن تتغيّر واجهة المستخدم بعد إتمام عملية المصادقة بنجاح.

حالة تسجيل الدخول إلى إطار عمل UIKit على محاكي iOS

بعد إجراء هذه التغييرات، من المفترض أن يبدو ملف ViewController.swift كما يلي:

import GoogleSignIn
import UIKit

class ViewController: UIViewController {

  // Create an instance of the Sign in with Google button
  let signInButton = GIDSignInButton()
  let signOutButton = UIButton(type: .system)
  let welcomeLabel = UILabel()

  override func viewDidLoad() {
    super.viewDidLoad()

    // Set the width and color of the sign-in button
    signInButton.style = .standard  // Options: .standard, .wide, .iconOnly
    signInButton.colorScheme = .dark  // Options: .dark, .light

    // Add the sign-in button to your view
    view.addSubview(signInButton)

    // Position the button using constraints
    signInButton.translatesAutoresizingMaskIntoConstraints = false
    NSLayoutConstraint.activate([
      signInButton.centerXAnchor.constraint(equalTo: view.centerXAnchor),
      signInButton.centerYAnchor.constraint(equalTo: view.centerYAnchor),
    ])

    // Add a target to the button to call a method when it's pressed
    signInButton.addTarget(self, action: #selector(signInButtonTapped), for: .touchUpInside)

    // --- Set up the Welcome Label ---
    welcomeLabel.translatesAutoresizingMaskIntoConstraints = false
    welcomeLabel.textAlignment = .center
    welcomeLabel.font = .systemFont(ofSize: 24, weight: .bold)
    view.addSubview(welcomeLabel)

    NSLayoutConstraint.activate([
      welcomeLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor),
      welcomeLabel.bottomAnchor.constraint(equalTo: signInButton.topAnchor, constant: -20),
    ])

    // --- Set up the Sign-Out Button ---
    signOutButton.translatesAutoresizingMaskIntoConstraints = false
    signOutButton.setTitle("Sign Out", for: .normal)
    view.addSubview(signOutButton)

    NSLayoutConstraint.activate([
      signOutButton.centerXAnchor.constraint(equalTo: view.centerXAnchor),
      signOutButton.topAnchor.constraint(equalTo: signInButton.bottomAnchor, constant: 20),
    ])

    signOutButton.addTarget(self, action: #selector(signOutButtonTapped), for: .touchUpInside)

    // --- Set Initial UI State ---
    updateUI(for: nil)
  }

  // This method is called when the sign-in button is pressed.
  @objc func signInButtonTapped() {
    // Start the sign-in process.
    GIDSignIn.sharedInstance.signIn(withPresenting: self) { signInResult, error in
      guard let result = signInResult else {
        // Inspect error
        print("Error signing in: \(error?.localizedDescription ?? "No error description")")
        return
      }

      // If sign in succeeded, print the ID token.
      print("ID Token: \(result.user.idToken?.tokenString ?? "")")

      DispatchQueue.main.async {
        self.updateUI(for: result.user)
      }
    }
  }

  private func updateUI(for user: GIDGoogleUser?) {
    if let user = user {
      // User is signed in.
      signInButton.isHidden = true
      signOutButton.isHidden = false
      welcomeLabel.isHidden = false
      welcomeLabel.text = "Hello, \(user.profile?.givenName ?? "User")!"
    } else {
      // User is signed out.
      signInButton.isHidden = false
      signOutButton.isHidden = true
      welcomeLabel.isHidden = true
    }
  }

  @objc func signOutButtonTapped() {
    GIDSignIn.sharedInstance.signOut()
    // Update the UI for the signed-out state.
    updateUI(for: nil)
  }
}

9- استعادة حالة تسجيل دخول المستخدم

لتحسين تجربة المستخدمين العائدين، تتمثّل الخطوة التالية في استعادة حالة تسجيل الدخول عند تشغيل التطبيق. تستخدم ميزة "الاتصال" (Calling) restorePreviousSignIn بيانات الاعتماد المحفوظة في Keychain لإعادة تسجيل دخول المستخدم بدون الحاجة إلى إكمال خطوات تسجيل الدخول في كل مرة.

SwiftUI

  1. افتح الملف ContentView.swift.
  2. أضِف الرمز التالي مباشرةً بعد VStack داخل المتغيّر body:
.onAppear {
  // On appear, try to restore a previous sign-in.
  GIDSignIn.sharedInstance.restorePreviousSignIn { user, error in
    // This closure is called when the restoration is complete.
    if let user = user {
      // If a user was restored, update the `user` state variable.
      DispatchQueue.main.async {
        self.user = user
      }

      // Print the ID token to the console when restored.
      print("Restored ID Token: \(user.idToken?.tokenString ?? "")")
    }
  }
}

يجب أن يبدو ملف ContentView.swift على النحو التالي:

import GoogleSignIn
import GoogleSignInSwift
import SwiftUI

struct ContentView: View {

  @State private var user: GIDGoogleUser?

  var body: some View {
    VStack {
      // Check if the user is signed in.
      if let user = user {
        // If signed in, show a welcome message and the sign-out button.
        Text("Hello, \(user.profile?.givenName ?? "User")!")
          .font(.title)
          .padding()

        Button("Sign Out", action: signOut)
          .buttonStyle(.borderedProminent)

      } else {
        // If not signed in, show the "Sign in with Google" button.
        GoogleSignInButton(
          scheme: .dark,  // Options: .light, .dark, .auto
          style: .standard,  // Options: .standard, .wide, .icon
          state: .normal,  // Options: .normal, .disabled
          action: handleSignInButton
        ).padding()
      }
    }

    .onAppear {
      // On appear, try to restore a previous sign-in.
      GIDSignIn.sharedInstance.restorePreviousSignIn { user, error in
        // This closure is called when the restoration is complete.
        if let user = user {
          // If a user was restored, update the `user` state variable.
          DispatchQueue.main.async {
            self.user = user
          }

          // Print the ID token to the console when restored.
          print("Restored ID Token: \(user.idToken?.tokenString ?? "")")
        }
      }
    }
  }

  func handleSignInButton() {
    // Find the current window scene.
    guard let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene else {
      print("There is no active window scene")
      return
    }

    // Get the root view controller from the window scene.
    guard
      let rootViewController = windowScene.windows.first(where: { $0.isKeyWindow })?
        .rootViewController
    else {
      print("There is no key window or root view controller")
      return
    }

    // Start the sign-in process.
    GIDSignIn.sharedInstance.signIn(
      withPresenting: rootViewController
    ) { signInResult, error in
      guard let result = signInResult else {
        // Inspect error
        print("Error signing in: \(error?.localizedDescription ?? "No error description")")
        return
      }

      DispatchQueue.main.async {
        self.user = result.user
      }

      // If sign in succeeded, display the app's main content View.
      print("ID Token: \(result.user.idToken?.tokenString ?? "")")
    }
  }

  func signOut() {
    GIDSignIn.sharedInstance.signOut()
    // After signing out, set the `user` state variable to `nil`.
    self.user = nil
  }
}

#Preview {
  ContentView()
}

UIKit

  1. افتح ViewController.swift.
  2. أضِف استدعاء restorePreviousSignIn التالي إلى نهاية طريقة viewDidLoad:
// Attempt to restore a previous sign-in session
GIDSignIn.sharedInstance.restorePreviousSignIn { user, error in
  if let user = user {
    print("Successfully restored sign-in for user: \(user.profile?.givenName ?? "Unknown")")

    // Print the ID token when a session is restored.
    print("Restored ID Token: \(user.idToken?.tokenString ?? "")")

    // On success, update the UI for the signed-in state on the main thread.
    DispatchQueue.main.async {
      self.updateUI(for: user)
    }
  }
}

يجب أن يبدو ملف ViewController.swift على النحو التالي:

import GoogleSignIn
import UIKit

class ViewController: UIViewController {

  // Create an instance of the Sign in with Google button
  let signInButton = GIDSignInButton()
  let signOutButton = UIButton(type: .system)
  let welcomeLabel = UILabel()

  override func viewDidLoad() {
    super.viewDidLoad()

    // Set the width and color of the sign-in button
    signInButton.style = .standard  // Options: .standard, .wide, .iconOnly
    signInButton.colorScheme = .dark  // Options: .dark, .light

    // Add the sign-in button to your view
    view.addSubview(signInButton)

    // Position the button using constraints
    signInButton.translatesAutoresizingMaskIntoConstraints = false
    NSLayoutConstraint.activate([
      signInButton.centerXAnchor.constraint(equalTo: view.centerXAnchor),
      signInButton.centerYAnchor.constraint(equalTo: view.centerYAnchor),
    ])

    // Add a target to the button to call a method when it's pressed
    signInButton.addTarget(self, action: #selector(signInButtonTapped), for: .touchUpInside)

    // --- Set up the Welcome Label ---
    welcomeLabel.translatesAutoresizingMaskIntoConstraints = false
    welcomeLabel.textAlignment = .center
    welcomeLabel.font = .systemFont(ofSize: 24, weight: .bold)
    view.addSubview(welcomeLabel)

    NSLayoutConstraint.activate([
      welcomeLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor),
      welcomeLabel.bottomAnchor.constraint(equalTo: signInButton.topAnchor, constant: -20),
    ])

    // --- Set up the Sign-Out Button ---
    signOutButton.translatesAutoresizingMaskIntoConstraints = false
    signOutButton.setTitle("Sign Out", for: .normal)
    view.addSubview(signOutButton)

    NSLayoutConstraint.activate([
      signOutButton.centerXAnchor.constraint(equalTo: view.centerXAnchor),
      signOutButton.topAnchor.constraint(equalTo: signInButton.bottomAnchor, constant: 20),
    ])

    signOutButton.addTarget(self, action: #selector(signOutButtonTapped), for: .touchUpInside)

    // --- Set Initial UI State ---
    updateUI(for: nil)

    // Attempt to restore a previous sign-in session
    GIDSignIn.sharedInstance.restorePreviousSignIn { user, error in
      if let user = user {
        print("Successfully restored sign-in for user: \(user.profile?.givenName ?? "Unknown")")

        // Print the ID token when a session is restored.
        print("Restored ID Token: \(user.idToken?.tokenString ?? "")")

        // On success, update the UI for the signed-in state on the main thread.
        DispatchQueue.main.async {
          self.updateUI(for: user)
        }
      }
    }
  }

  // This method is called when the sign-in button is pressed.
  @objc func signInButtonTapped() {
    // Start the sign-in process.
    GIDSignIn.sharedInstance.signIn(withPresenting: self) { signInResult, error in
      guard let result = signInResult else {
        // Inspect error
        print("Error signing in: \(error?.localizedDescription ?? "No error description")")
        return
      }

      // If sign in succeeded, print the ID token.
      print("ID Token: \(result.user.idToken?.tokenString ?? "")")

      DispatchQueue.main.async {
        self.updateUI(for: result.user)
      }
    }
  }

  private func updateUI(for user: GIDGoogleUser?) {
    if let user = user {
      // User is signed in.
      signInButton.isHidden = true
      signOutButton.isHidden = false
      welcomeLabel.isHidden = false
      welcomeLabel.text = "Hello, \(user.profile?.givenName ?? "User")!"
    } else {
      // User is signed out.
      signInButton.isHidden = false
      signOutButton.isHidden = true
      welcomeLabel.isHidden = true
    }
  }

  @objc func signOutButtonTapped() {
    GIDSignIn.sharedInstance.signOut()
    // Update the UI for the signed-out state.
    updateUI(for: nil)
  }
}

اختبار ميزة "تسجيل الدخول بدون كلمة مرور"

بعد تسجيل الدخول، أغلِق التطبيق تمامًا وأعِد تشغيله. من المفترض أن يتم تسجيل دخولك تلقائيًا بدون الحاجة إلى النقر على الزر.

10. فهم الرمز المميز لتعريف الهوية

على الرغم من أنّ الكائن GIDGoogleUser مناسب لتخصيص واجهة المستخدم باستخدام اسم المستخدم وبريده الإلكتروني، فإنّ أهم جزء من البيانات التي يتم عرضها من حزمة تطوير البرامج (SDK) هو رمز التعريف.

يستخدم برنامج التدريب العملي هذا أداة على الإنترنت لفحص محتوى رمز JWT المميّز. في تطبيق متاح للجميع، يجب إرسال رمز التعريف المميز هذا إلى خادم الخلفية. يجب أن يتحقّق الخادم من سلامة الرمز المميّز للمعرّف واستخدام JWT لتنفيذ إجراء أكثر أهمية، مثل إنشاء حساب جديد على منصة الخلفية أو إنشاء جلسة جديدة للمستخدم.

الوصول إلى رمز JWT المميّز وفك ترميزه

  1. افتح تطبيقك.
  2. افتح وحدة تحكّم Xcode. من المفترض أن يظهر لك رمز مميّز مطبوع. سيبدو على النحو التالي: eyJhbGciOiJSUzI1Ni ... Hecz6Wm4Q.
  3. انسخ الرمز المميّز لتعريف الهوية واستخدِم أداة على الإنترنت، مثل jwt.io، لفك ترميز رمز JWT.

سيبدو رمز JWT الذي تم فك تشفيره على النحو التالي:

{
  "alg": "RS256",
  "kid": "c8ab71530972bba20b49f78a09c9852c43ff9118",
  "typ": "JWT"
}
{
  "iss": "https://accounts.google.com",
  "azp": "171291171076-rrbkcjrp5jbte92ai9gub115ertscphi.apps.googleusercontent.com",
  "aud": "171291171076-rrbkcjrp5jbte92ai9gub115ertscphi.apps.googleusercontent.com",
  "sub": "10769150350006150715113082367",
  "email": "example@example.com",
  "email_verified": true,
  "at_hash": "JyCYDmHtzhjkb0-qJhKsMg",
  "name": "Kimya",
  "picture": "https://lh3.googleusercontent.com/a/ACg8ocIyy4VoR31t_n0biPVcScBHwZOCRaKVDb_MoaMYep65fyqoAw=s96-c",
  "given_name": "Kimya",
  "iat": 1758645896,
  "exp": 1758649496
}

حقول الرموز المميّزة البارزة

يحتوي رمز التعريف المميّز الذي تم فك ترميزه على حقول بأغراض مختلفة. بعضها سهل الفهم، مثل الاسم والبريد الإلكتروني، بينما يستخدم خادم الخلفية البعض الآخر للتحقّق من الهوية.

من المهم بشكل خاص فهم الحقل التالي:

  • sub: الحقل sub هو معرّف فريد ودائم لحساب المستخدم على Google. يمكن للمستخدم تغيير عنوان البريد الإلكتروني الأساسي أو الاسم، ولكن لن يتغير معرّف sub أبدًا. وهذا يجعل الحقل sub القيمة المثالية لاستخدامها كمفتاح أساسي لحسابات المستخدمين في الخلفية.

يتضمّن المقال الحصول على معلومات المستخدم من رمز التعريف المميز مزيدًا من المعلومات حول معنى جميع حقول الرمز المميز.

11. تأمين تطبيقك باستخدام خدمة App Check

ننصحك بشدة بتفعيل App Check لضمان أنّ تطبيقك فقط يمكنه الوصول إلى نقاط نهاية OAuth 2.0 من Google نيابةً عن مشروعك. تعمل خدمة App Check من خلال التحقّق من أنّ الطلبات المُرسَلة إلى خدمات الخلفية مصدرها تطبيقك الأصلي على جهاز حقيقي لم يتم التلاعب به.

يوضّح هذا القسم كيفية دمج خدمة App Check في تطبيقك وإعدادها لتصحيح الأخطاء في المحاكي ولإنشاء إصدار نهائي يعمل على جهاز حقيقي.

إعداد وحدة التحكّم

يتطلّب دمج App Check في تطبيقك إعدادًا لمرة واحدة في وحدتَي تحكّم Google Cloud وFirebase. يتضمّن ذلك تفعيل App Check لعميل OAuth على نظام التشغيل iOS في Google Cloud Console، وإنشاء مفتاح واجهة برمجة تطبيقات لاستخدامه مع موفّر تصحيح الأخطاء في App Check، وربط مشروعك على Google Cloud بخدمة Firebase.

تفعيل App Check في Google Cloud Console

  1. انتقِل إلى قائمة العملاء المرتبطين بمشروعك على Google Cloud.
  2. اختَر معرّف عميل OAuth 2.0 الذي أنشأته لتطبيق iOS.
  3. فعِّل App Check ضمن هوية Google على iOS

صفحة تعديل عميل OAuth مع زر تفعيل App Check

  1. انقر على حفظ.

إنشاء مفتاح واجهة برمجة تطبيقات

  1. انتقِل إلى صفحة مكتبة واجهات برمجة التطبيقات لمشروعك على Google Cloud.
  2. أدخِل Firebase App Check API في شريط البحث.

صفحة مكتبة واجهات برمجة التطبيقات في Google Cloud Console

  1. اختَر Firebase App Check API وفعِّله.
  2. انتقِل إلى واجهات برمجة التطبيقات والخدمات وانقر على بيانات الاعتماد في قائمة التنقّل.
  3. انقر على إنشاء بيانات اعتماد في أعلى الصفحة.

صفحة بيانات اعتماد واجهة برمجة التطبيقات في Google Cloud Console

  1. خصِّص اسمًا لمفتاح واجهة برمجة التطبيقات هذا.
  2. اختَر تطبيقات iOS ضِمن قيود التطبيقات.
  3. أضِف معرّف الحزمة لتطبيقك كتطبيق معتمَد.
  4. انقر على تقييد المفتاح ضمن قيود واجهة برمجة التطبيقات.
  5. اختَر Firebase App Check API من القائمة المنسدلة.
  6. انقر على إنشاء.

صفحة إنشاء مفتاح واجهة برمجة التطبيقات في Google Cloud Console

  1. انسخ مفتاح واجهة برمجة التطبيقات الذي تم إنشاؤه. ستحتاج إليه في خطوة لاحقة.

إضافة Firebase إلى مشروعك على Google Cloud

  1. انتقِل إلى وحدة تحكّم Firebase.
  2. انقر على البدء من خلال إعداد مشروع على Firebase.
  3. انقر على إضافة Firebase إلى مشروع Google Cloud.

إضافة Firebase إلى مشروع حالي على Google Cloud

  1. اختَر مشروعًا على Google Cloud من القائمة المنسدلة وواصِل عملية الاشتراك.
  2. انقر على إضافة Firebase.
  3. بعد أن يصبح مشروعك على Firebase جاهزًا، انقر على متابعة لفتح المشروع.

دمج الرمز البرمجي من جهة العميل

بعد إعداد مشروع Google Cloud لخدمة App Check، حان الوقت لكتابة الرمز البرمجي من جهة العميل لتفعيل الخدمة. يختلف مقدّم خدمة التصديق المستخدَم في بيئتَي الإنتاج وتصحيح الأخطاء. يستخدم تطبيق نهائي على جهاز حقيقي خدمة App Attest المضمّنة من Apple لإثبات صحته. ومع ذلك، بما أنّ محاكي iOS لا يمكنه تقديم هذا النوع من الشهادات، تتطلّب بيئة تصحيح الأخطاء موفّرًا خاصًا لتصحيح الأخطاء يتمّ تمرير مفتاح واجهة برمجة التطبيقات إليه.

يتعامل الرمز التالي مع كلتا الحالتين باستخدام توجيه للمترجم البرمجي لاختيار الموفّر الصحيح تلقائيًا في وقت الإنشاء.

SwiftUI

  1. افتح ملف التطبيق الرئيسي.
  2. حدِّد فئة AppDelegate التالية بعد عمليات الاستيراد وقبل السمة @main:
class AppDelegate: NSObject, UIApplicationDelegate {
  func application(
    _ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil
  ) -> Bool {

    #if targetEnvironment(simulator)
      // Configure for debugging on a simulator.
      // TODO: Replace "YOUR_API_KEY" with the key from your Google Cloud project.
      let apiKey = "YOUR_API_KEY"
      GIDSignIn.sharedInstance.configureDebugProvider(withAPIKey: apiKey) { error in
        if let error {
          print("Error configuring GIDSignIn debug provider: \(error)")
        }
      }
    #else
      // Configure GIDSignIn for App Check on a real device.
      GIDSignIn.sharedInstance.configure { error in
        if let error {
          print("Error configuring GIDSignIn for App Check: \(error)")
        } else {
          print("GIDSignIn configured for App Check.")
        }
      }
    #endif

    return true
  }
}
  1. استبدِل "YOUR_API_KEY" في الرمز المقدَّم بمفتاح واجهة برمجة التطبيقات الذي نسخته من Google Cloud Console.
  2. أضِف السطر التالي داخل بنية App، قبل المتغيّر body مباشرةً. تسجّل هذه السمة فئة AppDelegate في دورة حياة التطبيق، ما يسمح لها بالاستجابة لعمليات تشغيل التطبيق وأحداث النظام الأخرى:
@UIApplicationDelegateAdaptor(AppDelegate.self) var delegate

يجب أن يظهر ملف التطبيق الرئيسي على النحو التالي:

import GoogleSignIn
import SwiftUI

class AppDelegate: NSObject, UIApplicationDelegate {
  func application(
    _ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil
  ) -> Bool {

    #if targetEnvironment(simulator)
      // Configure for debugging on a simulator.
      // TODO: Replace "YOUR_API_KEY" with the key from your Google Cloud project.
      let apiKey = "YOUR_API_KEY"
      GIDSignIn.sharedInstance.configureDebugProvider(withAPIKey: apiKey) { error in
        if let error {
          print("Error configuring GIDSignIn debug provider: \(error)")
        }
      }
    #else
      // Configure GIDSignIn for App Check on a real device.
      GIDSignIn.sharedInstance.configure { error in
        if let error {
          print("Error configuring GIDSignIn for App Check: \(error)")
        } else {
          print("GIDSignIn configured for App Check.")
        }
      }
    #endif

    return true
  }
}

@main
struct iOS_Sign_in_with_Google_App: App {

  @UIApplicationDelegateAdaptor(AppDelegate.self) var delegate

  var body: some Scene {
    WindowGroup {
      ContentView()

        .onOpenURL { url in
          GIDSignIn.sharedInstance.handle(url)
        }
    }
  }
}

UIKit

  1. افتح الملف AppDelegate.swift.
  2. عدِّل طريقة application(_:didFinishLaunchingWithOptions:) لتتضمّن عملية إعداد App Check:
func application(
  _ application: UIApplication,
  didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {

  #if targetEnvironment(simulator)
    // Configure for debugging on a simulator.
    // TODO: Replace "YOUR_API_KEY" with the key from your Google Cloud project.
    let apiKey = "YOUR_API_KEY"
    GIDSignIn.sharedInstance.configureDebugProvider(withAPIKey: apiKey) { error in
      if let error {
        print("Error configuring GIDSignIn debug provider: \(error)")
      }
    }
  #else
    // Configure GIDSignIn for App Check on a real device.
    GIDSignIn.sharedInstance.configure { error in
      if let error {
        print("Error configuring GIDSignIn for App Check: \(error)")
      }
    }
  #endif

  return true
}
  1. استبدِل "YOUR_API_KEY" في الرمز المقدَّم بمفتاح واجهة برمجة التطبيقات الذي نسخته من Google Cloud Console.

يجب أن يبدو ملف AppDelegate.swift على النحو التالي:

import GoogleSignIn
import UIKit

@main
class AppDelegate: UIResponder, UIApplicationDelegate {

  func application(
    _ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
  ) -> Bool {

    #if targetEnvironment(simulator)
      // Configure for debugging on a simulator.
      // TODO: Replace "YOUR_API_KEY" with the key from your Google Cloud project.
      let apiKey = "YOUR_API_KEY"
      GIDSignIn.sharedInstance.configureDebugProvider(withAPIKey: apiKey) { error in
        if let error {
          print("Error configuring GIDSignIn debug provider: \(error)")
        }
      }
    #else
      // Configure GIDSignIn for App Check on a real device.
      GIDSignIn.sharedInstance.configure { error in
        if let error {
          print("Error configuring GIDSignIn for App Check: \(error)")
        }
      }
    #endif

    return true
  }

  func application(
    _ app: UIApplication,
    open url: URL,
    options: [UIApplication.OpenURLOptionsKey: Any] = [:]
  ) -> Bool {
    var handled: Bool

    handled = GIDSignIn.sharedInstance.handle(url)
    if handled {
      return true
    }
    // If not handled by this app, return false.
    return false
  }

  // MARK: UISceneSession Lifecycle

  func application(
    _ application: UIApplication,
    configurationForConnecting connectingSceneSession: UISceneSession,
    options: UIScene.ConnectionOptions
  ) -> UISceneConfiguration {
    // Called when a new scene session is being created.
    // Use this method to select a configuration to create the new scene with.
    return UISceneConfiguration(
      name: "Default Configuration",
      sessionRole: connectingSceneSession.role
    )
  }

  func application(
    _ application: UIApplication,
    didDiscardSceneSessions sceneSessions: Set<UISceneSession>
  ) {
    // Called when the user discards a scene session.
    // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions.
    // Use this method to release any resources that were specific to the discarded scenes, as they will not return.
  }
}

اختبار خدمة App Check على المحاكي

  1. في شريط قائمة Xcode، انتقِل إلى المنتج (Product) > المخطّط (Scheme) > تعديل المخطّط (Edit Scheme).
  2. انقر على تشغيل في قائمة التنقّل.
  3. انقر على علامة التبويب الوسيطات (Arguments).
  4. في قسم المَعلمات التي تم ضبطها عند الإطلاق (Arguments Passed on Launch)، انقر على + وأضِف -FIRDebugEnabled. تتيح وسيطة الإطلاق هذه تسجيل تصحيح الأخطاء في Firebase.
  5. اختَر إغلاق.

صفحة أداة تعديل وسيطات Xcode

  1. شغِّل تطبيقك على المحاكي.
  2. انسخ الرمز المميز لتصحيح أخطاء App Check الذي تتم طباعته في وحدة تحكّم Xcode.

رمز تصحيح App Check في وحدة تحكّم Xcode

  1. انتقِل إلى مشروعك في وحدة تحكّم Firebase.
  2. وسِّع قسم إنشاء في قائمة التنقّل.
  3. اختَر App Check.
  4. انقر على علامة التبويب التطبيقات.
  5. مرِّر مؤشر الماوس فوق تطبيقك وانقر على رمز قائمة الخيارات الإضافية.

إعدادات خدمة &quot;فحص التطبيقات من Firebase&quot;

  1. انقر على إدارة رموز تصحيح الأخطاء.
  2. انقر على إضافة رمز تصحيح الأخطاء.
  3. أدخِل اسمًا لرمز التصحيح وألصِق رمز التصحيح الذي نسخته سابقًا في الحقل "القيمة".
  4. انقر على حفظ لتسجيل الرمز المميّز.

إدارة الرموز المميزة لتصحيح الأخطاء في خدمة &quot;فحص التطبيقات&quot; من Firebase

  1. ارجِع إلى المحاكي وسجِّل الدخول.

قد يستغرق ظهور المقاييس في وحدة التحكّم عدة دقائق. بعد ذلك، يمكنك التأكّد من أنّ خدمة App Check تعمل من خلال البحث عن زيادة في طلبات التحقّق في أحد المكانَين التاليَين:

  • في قسم App Check ضِمن علامة التبويب "واجهات برمجة التطبيقات" في وحدة تحكّم Firebase

مقاييس خدمة &quot;فحص التطبيقات من Firebase&quot;

  • في صفحة التعديل الخاصة بعميل OAuth في Google Cloud Console

مقاييس Google Cloud Console App Check

بعد مراقبة مقاييس App Check في تطبيقك والتأكّد من التحقّق من الطلبات الصالحة، عليك تفعيل فرض استخدام App Check. وبعد فرضها، ترفض خدمة App Check جميع الطلبات التي لم يتم التحقّق منها، ما يضمن إمكانية وصول الزيارات من تطبيقك الأصلي فقط إلى نقاط نهاية OAuth 2.0 من Google نيابةً عن مشروعك.

12. مراجع إضافية

تهانينا!

لقد أعددت برنامج iOS لبروتوكول OAuth 2.0، وأضفت زر "تسجيل الدخول باستخدام Google" إلى تطبيق iOS، وتعرّفت على كيفية تخصيص مظهر الزر، وفكّ ترميز رمز تعريف JWT، وفعّلت خدمة App Check لتطبيقك.

قد تساعدك الروابط التالية في الخطوات التالية:

الأسئلة الشائعة