إضافة خريطة إلى تطبيق iOS باستخدام SwiftUI (Swift)

1. قبل البدء

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

screenshot-iphone-12-black@2x.png

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

  • معرفة أساسية بلغة Swift
  • معرفة أساسية بلغة SwiftUI

الإجراءات التي ستنفذّها

  • فعِّل حزمة تطوير البرامج بالاستناد إلى بيانات "خرائط Google" لتطبيقات iOS واستخدِمها لإضافة "خرائط Google" إلى تطبيق iOS باستخدام SwiftUI.
  • أضِف علامات إلى الخريطة.
  • تمرير الحالة بين عنصر SwiftUI وعنصر GMSMapView

المتطلبات

2. طريقة الإعداد

في خطوة التفعيل التالية، فعِّل حزمة تطوير البرامج بالاستناد إلى بيانات "خرائط Google" لتطبيقات iOS.

إعداد Google Maps Platform

إذا لم يكن لديك حساب على Google Cloud Platform ومشروع مفعَّل فيه نظام الفوترة، يُرجى الاطّلاع على دليل البدء باستخدام Google Maps Platform لإنشاء حساب فوترة ومشروع.

  1. في Cloud Console، انقر على القائمة المنسدلة الخاصة بالمشروع واختَر المشروع الذي تريد استخدامه في هذا الدرس العملي.

  1. فعِّل واجهات برمجة التطبيقات وحِزم تطوير البرامج (SDK) في Google Maps Platform المطلوبة لهذا الدرس العملي في Google Cloud Marketplace. لإجراء ذلك، اتّبِع الخطوات الواردة في هذا الفيديو أو هذه المستندات.
  2. أنشئ مفتاح واجهة برمجة التطبيقات في صفحة بيانات الاعتماد في Cloud Console. يمكنك اتّباع الخطوات الواردة في هذا الفيديو أو هذه المستندات. تتطلّب جميع الطلبات إلى "منصة خرائط Google" مفتاح واجهة برمجة تطبيقات.

3- تنزيل الرمز الأوّلي

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

  1. استنسِخ المستودع إذا كان لديك git مثبَّتًا.
git clone https://github.com/googlecodelabs/maps-ios-swiftui.git

يمكنك بدلاً من ذلك النقر على الزر التالي لتنزيل رمز المصدر.

  1. بعد الحصول على الرمز، انتقِل في نافذة طرفية cd إلى الدليل starter/GoogleMapsSwiftUI.
  2. نفِّذ الأمر carthage update --platform iOS لتنزيل حزمة تطوير البرامج بالاستناد إلى بيانات "خرائط Google" لتطبيقات iOS
  3. أخيرًا، افتح ملف GoogleMapsSwiftUI.xcodeproj في Xcode

4. نظرة عامة على الرمز البرمجي

في مشروع المبتدئين الذي نزّلته، تم توفير الفئات التالية وتنفيذها لك:

  • AppDelegate: UIApplicationDelegate التطبيق سيتم هنا إعداد حزمة تطوير البرامج بالاستناد إلى بيانات "خرائط Google" لتطبيقات iOS.
  • City: بنية تمثّل مدينة (تحتوي على اسم المدينة وإحداثياتها).
  • MapViewController: مجموعة أدوات واجهة مستخدم محدودة النطاق UIViewController تحتوي على "خريطة Google" (GMSMapView)
    • SceneDelegate: UIWindowSceneDelegate الخاص بالتطبيق الذي يتم إنشاء ContentView منه.

بالإضافة إلى ذلك، تحتوي الفئات التالية على عمليات تنفيذ جزئية وستكملها بنفسك بحلول نهاية هذا الدرس التطبيقي:

  • ContentView: هي طريقة العرض الرئيسية في SwiftUI التي تحتوي على تطبيقك.
  • MapViewControllerBridge: فئة تربط بين عرض UIKit وعرض SwiftUI. على وجه التحديد، هذا هو الصف الذي سيتيح الوصول إلى MapViewController في SwiftUI.

5- مقارنة بين SwiftUI وUIKit

تم طرح SwiftUI في الإصدار 13 من نظام التشغيل iOS كإطار عمل بديل لواجهة المستخدم بدلاً من UIKit لتطوير تطبيقات iOS. تقدّم SwiftUI عددًا من المزايا مقارنةً بإطار عمل UIKit السابق. في ما يلي بعض الأمثلة:

  • يتم تعديل طرق العرض تلقائيًا عند تغيُّر الحالة. باستخدام عناصر تُعرف باسم الحالة، سيؤدي أي تغيير في القيمة الأساسية التي تحتوي عليها إلى تعديل واجهة المستخدم تلقائيًا.
  • تتيح المعاينات المباشرة إمكانية التطوير بشكل أسرع. تقلّل المعاينات المباشرة من الحاجة إلى إنشاء الرمز البرمجي ونشره في محاكي لرؤية التغييرات المرئية، إذ يمكن الاطّلاع على معاينة لعرض SwiftUI بسهولة على Xcode.
  • يتم تخزين البيانات الأساسية في Swift. يتم تعريف جميع طرق العرض في SwiftUI باستخدام لغة Swift، لذا لم يعُد من الضروري استخدام Interface Builder.
  • تعمل مع UIKit. تضمن إمكانية التشغيل التفاعلي مع UIKit إمكانية استخدام التطبيقات الحالية لـ SwiftUI بشكل تدريجي مع طرق العرض الحالية. بالإضافة إلى ذلك، يمكن استخدام المكتبات التي لا تتوافق مع SwiftUI بعد، مثل حزمة تطوير البرامج (SDK) لنظام التشغيل iOS في "خرائط Google"، في SwiftUI.

هناك بعض العيوب أيضًا:

  • لا تتوفّر SwiftUI إلا على نظام التشغيل iOS 13 أو الإصدارات الأحدث.
  • لا يمكن فحص بنية العرض في معاينات Xcode.

حالة SwiftUI وتدفّق البيانات

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

ربط UIKit وSwiftUI باستخدام UIViewRepresentable أو UIViewControllerRepresentable

بما أنّ حزمة تطوير البرامج لنظام التشغيل iOS في "خرائط Google" تستند إلى UIKit ولا توفّر طريقة عرض متوافقة مع SwiftUI، يتطلّب استخدامها في SwiftUI الالتزام بأي من UIViewRepresentable أو UIViewControllerRepresentable. تتيح هذه البروتوكولات تضمين UIView وUIViewController تم إنشاؤهما باستخدام UIKit، على التوالي. على الرغم من إمكانية استخدام أي من البروتوكولين لإضافة خريطة Google إلى طريقة عرض SwiftUI، سنتناول في الخطوة التالية كيفية استخدام UIViewControllerRepresentable لتضمين UIViewController يحتوي على خريطة.

6. إضافة خريطة

في هذا القسم، ستضيف "خرائط Google" إلى طريقة عرض SwiftUI.

add-a-map-screenshot@2x.png

إضافة مفتاح واجهة برمجة التطبيقات

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

لتوفير مفتاح واجهة برمجة التطبيقات، افتح الملف AppDelegate.swift وانتقِل إلى الطريقة application(_, didFinishLaunchingWithOptions). يتم تهيئة حزمة SDK باستخدام GMSServices.provideAPIKey() مع السلسلة "YOUR_API_KEY". استبدِل هذه السلسلة بمفتاح واجهة برمجة التطبيقات. سيؤدي إكمال هذه الخطوة إلى إعداد حزمة تطوير البرامج بالاستناد إلى بيانات "خرائط Google" لنظام التشغيل iOS عند تشغيل التطبيق.

إضافة خريطة Google باستخدام MapViewControllerBridge

بعد توفير مفتاح واجهة برمجة التطبيقات لحزمة SDK، تتمثّل الخطوة التالية في عرض الخريطة على التطبيق.

يتضمّن عنصر التحكّم في العرض المقدَّم في رمز البداية، MapViewController، عنصر GMSMapView في طريقة عرضه. ومع ذلك، بما أنّه تم إنشاء وحدة التحكّم في العرض هذه في UIKit، عليك ربط هذه الفئة بـ SwiftUI حتى يمكن استخدامها داخل ContentView. ولإجراء ذلك، يُرجى اتّباع الخطوات التالية:

  1. افتح الملف MapViewControllerBridge في Xcode.

يتوافق هذا الصف مع البروتوكول UIViewControllerRepresentable المطلوب لتضمين UIViewController من UIKit حتى يمكن استخدامه كطريقة عرض في SwiftUI. بعبارة أخرى، يسهّل الالتزام بهذا البروتوكول ربط طريقة عرض UIKit بطريقة عرض SwiftUI. يتطلّب الالتزام بهذا البروتوكول تنفيذ طريقتَين:

  • makeUIViewController(context): تستدعي SwiftUI هذه الطريقة لإنشاء UIViewController الأساسي. هذا هو المكان الذي يمكنك فيه إنشاء مثيل UIViewController وتمرير حالته الأولية إليه.
  • updateUIViewController(_, context): تستدعي SwiftUI هذه الطريقة كلما تغيّرت الحالة. هذا هو المكان الذي يمكنك فيه إجراء أي تعديلات على UIViewController الأساسي للتفاعل مع تغيير الحالة.
  1. إنشاء MapViewController

داخل الدالة makeUIViewController(context)، أنشئ مثيلاً جديدًا من MapViewController وأرجِعه كنتيجة. بعد ذلك، يُفترض أن يظهر MapViewControllerBridge على النحو التالي:

MapViewControllerBridge

import GoogleMaps
import SwiftUI

struct MapViewControllerBridge: UIViewControllerRepresentable {

  func makeUIViewController(context: Context) -> MapViewController {
    return MapViewController()
  }

  func updateUIViewController(_ uiViewController: MapViewController, context: Context) {
  }
}

استخدام MapViewControllerBridge في ContentView

بعد أن ينشئ MapViewControllerBridge مثيلاً من MapViewController، تتمثّل الخطوة التالية في استخدام هذه البنية داخل ContentView لعرض خريطة.

  1. افتح الملف ContentView في Xcode.

يتم إنشاء ContentView في SceneDelegate ويحتوي على عرض التطبيق من المستوى الأعلى. ستتم إضافة الخريطة من داخل هذا الملف.

  1. أنشئ MapViewControllerBridge ضمن الموقع body.

ضمن السمة body لهذا الملف، تمّت إضافة ZStack وتنفيذها نيابةً عنك. يحتوي ZStack على قائمة تفاعلية وقابلة للسحب للمدن ستستخدمها في خطوة لاحقة. في الوقت الحالي، أنشئ ZStack داخل MapViewControllerBridge كطريقة عرض فرعية أولى في ZStack حتى يتم عرض خريطة في التطبيق خلف طريقة عرض قائمة المدن. بعد ذلك، يجب أن يظهر محتوى السمة body ضمن ContentView على النحو التالي:

ContentView

var body: some View {

  let scrollViewHeight: CGFloat = 80

  GeometryReader { geometry in
    ZStack(alignment: .top) {
      // Map
      MapViewControllerBridge()

      // Cities List
      CitiesList(markers: $markers) { (marker) in
        guard self.selectedMarker != marker else { return }
        self.selectedMarker = marker
        self.zoomInCenter = false
        self.expandList = false
      }  handleAction: {
        self.expandList.toggle()
      } // ...
    }
  }
}
  1. الآن، شغِّل التطبيق. من المفترض أن تظهر لك الخريطة على شاشة جهازك مع قائمة قابلة للسحب تضم مدنًا في أسفل الشاشة.

7. إضافة علامات إلى الخريطة

في الخطوة السابقة، أضفت خريطة إلى جانب قائمة تفاعلية تعرض قائمة بالمدن. في هذا القسم، ستضيف علامات لكل مدينة في تلك القائمة.

map-with-markers@2x.png

العلامات كحالة

تحدّد ContentView سمة باسم markers وهي قائمة بعناصر GMSMarker تمثّل كل مدينة تم تحديدها في السمة الثابتة cities. لاحظ أنّ هذه السمة مزخرفة بغلاف سمة State في SwiftUI للإشارة إلى أنّه يجب أن تديرها SwiftUI. لذلك، في حال رصد أي تغييرات في هذه السمة، مثل إضافة علامة أو إزالتها، سيتم تعديل طرق العرض التي تستخدم هذه الحالة.

ContentView

  static let cities = [
    City(name: "San Francisco", coordinate: CLLocationCoordinate2D(latitude: 37.7576, longitude: -122.4194)),
    City(name: "Seattle", coordinate: CLLocationCoordinate2D(latitude: 47.6131742, longitude: -122.4824903)),
    City(name: "Singapore", coordinate: CLLocationCoordinate2D(latitude: 1.3440852, longitude: 103.6836164)),
    City(name: "Sydney", coordinate: CLLocationCoordinate2D(latitude: -33.8473552, longitude: 150.6511076)),
    City(name: "Tokyo", coordinate: CLLocationCoordinate2D(latitude: 35.6684411, longitude: 139.6004407))
  ]

  /// State for markers displayed on the map for each city in `cities`
  @State var markers: [GMSMarker] = cities.map {
    let marker = GMSMarker(position: $0.coordinate)
    marker.title = $0.name
    return marker
  }

يُرجى العِلم أنّ ContentView تستخدم السمة markers لعرض قائمة المدن من خلال تمريرها إلى الفئة CitiesList.

CitiesList

struct CitiesList: View {

  @Binding var markers: [GMSMarker]

  var body: some View {
    GeometryReader { geometry in
      VStack(spacing: 0) {
        // ...
        // List of Cities
        List {
          ForEach(0..<self.markers.count) { id in
            let marker = self.markers[id]
            Button(action: {
              buttonAction(marker)
            }) {
              Text(marker.title ?? "")
            }
          }
        }.frame(maxWidth: .infinity)
      }
    }
  }
}

تمرير الحالة إلى MapViewControllerBridge باستخدام @Binding

بالإضافة إلى قائمة المدن التي تعرض بيانات من السمة markers، مرِّر هذه السمة إلى بنية MapViewControllerBridge لكي يمكن استخدامها لعرض تلك العلامات على الخريطة. ولإجراء ذلك:

  1. عرِّف السمة الجديدة markers ضمن MapViewControllerBridge التي تتضمّن التعليق التوضيحي @Binding

MapViewControllerBridge

struct MapViewControllerBridge: : UIViewControllerRepresentable {
  @Binding var markers: [GMSMarker]
  // ...
}
  1. في MapViewControllerBridge، عدِّل طريقة updateUIViewController(_, context) للاستفادة من السمة markers

كما ذكرنا في الخطوة السابقة، سيتم استدعاء updateUIViewController(_, context) من خلال SwiftUI كلما تغيّرت الحالة. ضمن هذه الطريقة، نريد تعديل الخريطة لعرض العلامات في markers. لإجراء ذلك، عليك تعديل السمة map لكل علامة. بعد إكمال هذه الخطوة، من المفترض أن يظهر MapViewControllerBridge على النحو التالي:

import GoogleMaps
import SwiftUI

struct MapViewControllerBridge: UIViewControllerRepresentable {

  @Binding var markers: [GMSMarker]

  func makeUIViewController(context: Context) -> MapViewController {
    return MapViewController()
  }

  func updateUIViewController(_ uiViewController: MapViewController, context: Context) {
    // Update the map for each marker
    markers.forEach { $0.map = uiViewController.map }
  }
}
  1. تمرير السمة markers من ContentView إلى MapViewControllerBridge

بما أنّك أضفت سمة جديدة في MapViewControllerBridge، يتطلّب ذلك الآن تمرير قيمة هذه السمة في أداة التهيئة لـ MapViewControllerBridge. لذلك، إذا حاولت إنشاء التطبيق، ستلاحظ أنّه لن يتم تجميعه. لحلّ هذه المشكلة، عليك تعديل ContentView حيث يتم إنشاء MapViewControllerBridge وإدخال السمة markers على النحو التالي:

struct ContentView: View {
  // ...
  var body: some View {
    // ...
    GeometryReader { geometry in
      ZStack(alignment: .top) {
        // Map
        MapViewControllerBridge(markers: $markers)
        // ...
      }
    }
  }
}

لاحظ أنّه تم استخدام البادئة $ لتمرير markers إلى MapViewControllerBridge لأنّها تتوقّع سمة مرتبطة. ‫$ هي بادئة محجوزة لاستخدامها مع أغلفة خصائص Swift. عند تطبيقها على حالة، ستعرض ربطًا.

  1. يمكنك الآن تشغيل التطبيق لرؤية العلامات المعروضة على الخريطة.

8. التحريك إلى مدينة محدّدة

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

animate-city@2x.png

تحريك الخريطة إلى المدينة المحدّدة

لتحريك الخريطة إلى مدينة محدّدة، اتّبِع الخطوات التالية:

  1. تحديد عملية ربط جديدة في MapViewControllerBridge

يحتوي ContentView على سمة State باسم selectedMarker تمّت تهيئتها إلى nil ويتم تعديلها كلّما تمّ اختيار مدينة من القائمة. يتم التعامل مع ذلك من خلال طريقة العرض CitiesList buttonAction ضمن ContentView.

ContentView

CitiesList(markers: $markers) { (marker) in
  guard self.selectedMarker != marker else { return }
  self.selectedMarker = marker
  // ...
}

عندما تتغيّر قيمة selectedMarker، يجب أن يكون MapViewControllerBridge على دراية بهذا التغيير في الحالة حتى يتمكّن من تحريك الخريطة إلى العلامة المحدّدة. لذلك، حدِّد Binding جديدًا ضمن MapViewControllerBridge من النوع GMSMarker وسمِّ السمة selectedMarker.

MapViewControllerBridge

struct MapViewControllerBridge: UIViewControllerRepresentable {
  @Binding var selectedMarker: GMSMarker?
}
  1. عدِّل MapViewControllerBridge لتحريك الخريطة كلما تغيّر selectedMarker

بعد تعريف Binding جديد، عليك تعديل الدالة updateUIViewController_, context) في MapViewControllerBridge لكي يتم تحريك الخريطة إلى العلامة المحدّدة. يمكنك إجراء ذلك من خلال نسخ الرمز التالي:

struct MapViewControllerBridge: UIViewControllerRepresentable {
  @Binding var selectedMarker: GMSMarker?

  func updateUIViewController(_ uiViewController: MapViewController, context: Context) {
    markers.forEach { $0.map = uiViewController.map }
    selectedMarker?.map = uiViewController.map
    animateToSelectedMarker(viewController: uiViewController)
  }

  private func animateToSelectedMarker(viewController: MapViewController) {
    guard let selectedMarker = selectedMarker else {
      return
    }

    let map = viewController.map
    if map.selectedMarker != selectedMarker {
      map.selectedMarker = selectedMarker
      DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
        map.animate(toZoom: kGMSMinZoomLevel)
        DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
          map.animate(with: GMSCameraUpdate.setTarget(selectedMarker.position))
          DispatchQueue.main.asyncAfter(deadline: .now() + 0.5, execute: {
            map.animate(toZoom: 12)
          })
        }
      }
    }
  }
}

ستنفّذ الدالة animateToSelectedMarker(viewController) سلسلة من الصور المتحركة على الخريطة باستخدام الدالة animate(with) في GMSMapView.

  1. نقل selectedMarker من بطاقة ContentView إلى MapViewControllerBridge

بعد أن يتم الإعلان عن Binding الجديد في MapViewControllerBridge، يمكنك تعديل ContentView لتمرير selectedMarker حيث يتم إنشاء MapViewControllerBridge.

ContentView

struct ContentView: View {
  // ...
  var body: some View {
    // ...
    GeometryReader { geometry in
      ZStack(alignment: .top) {
        // Map
        MapViewControllerBridge(markers: $markers, selectedMarker: $selectedMarker)
        // ...
      }
    }
  }
}

سيؤدي إكمال هذه الخطوة الآن إلى تحريك الخريطة كلّما تمّ اختيار مدينة جديدة في القائمة.

تحريك عرض SwiftUI للتركيز على المدينة

تسهّل SwiftUI عملية تحريك طرق العرض، إذ ستتولّى تنفيذ الرسوم المتحركة لعمليات انتقال الحالة. لتوضيح ذلك، ستضيف المزيد من الرسوم المتحركة من خلال تركيز العرض على المدينة المحدّدة بعد اكتمال الرسوم المتحركة للخريطة. لإجراء ذلك، أكمِل الخطوات التالية:

  1. إضافة إغلاق onAnimationEnded إلى MapViewControllerBridge

بما أنّ الصورة المتحركة في SwiftUI سيتم عرضها بعد تسلسل الصورة المتحركة للخريطة الذي أضفته سابقًا، عليك تعريف إغلاق جديد باسم onAnimationEnded ضمن MapViewControllerBridge واستدعاء هذا الإغلاق بعد تأخير لمدة 0.5 ثانية بعد آخر صورة متحركة للخريطة ضمن طريقة animateToSelectedMarker(viewController).

MapViewControllerBridge

struct MapViewControllerBridge: UIViewControllerRepresentable {
    var onAnimationEnded: () -> ()

    private func animateToSelectedMarker(viewController: MapViewController) {
    guard let selectedMarker = selectedMarker else {
      return
    }

    let map = viewController.map
    if map.selectedMarker != selectedMarker {
      map.selectedMarker = selectedMarker
      DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
        map.animate(toZoom: kGMSMinZoomLevel)
        DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
          map.animate(with: GMSCameraUpdate.setTarget(selectedMarker.position))
          DispatchQueue.main.asyncAfter(deadline: .now() + 0.5, execute: {
            map.animate(toZoom: 12)
            DispatchQueue.main.asyncAfter(deadline: .now() + 0.5, execute: {
              // Invoke onAnimationEnded() once the animation sequence completes
              onAnimationEnded()
            })
          })
        }
      }
    }
  }
}
  1. تنفيذ onAnimationEnded في MapViewControllerBridge

نفِّذ عملية الإغلاق onAnimationEnded حيث يتم إنشاء نسخة من MapViewControllerBridge داخل ContentView. انسَخ الرمز التالي والصِقه، وهو يضيف حالة جديدة باسم zoomInCenter ويعدّل العرض أيضًا باستخدام clipShape ويغيّر قطر الشكل الذي تم اقتصاصه استنادًا إلى قيمة zoomInCenter

ContentView

struct ContentView: View {
  @State var zoomInCenter: Bool = false
  // ...
  var body: some View {
    // ...
    GeometryReader { geometry in
      ZStack(alignment: .top) {
        // Map
        let diameter = zoomInCenter ? geometry.size.width : (geometry.size.height * 2)
        MapViewControllerBridge(markers: $markers, selectedMarker: $selectedMarker, onAnimationEnded: {
          self.zoomInCenter = true
        })
        .clipShape(
           Circle()
             .size(
               width: diameter,
               height: diameter
             )
             .offset(
               CGPoint(
                 x: (geometry.size.width - diameter) / 2,
                 y: (geometry.size.height - diameter) / 2
               )
             )
        )
        .animation(.easeIn)
        .background(Color(red: 254.0/255.0, green: 1, blue: 220.0/255.0))
      }
    }
  }
}
  1. يمكنك الآن تشغيل التطبيق للاطّلاع على الصور المتحركة.

9- إرسال حدث إلى SwiftUI

في هذه الخطوة، ستستمع إلى الأحداث المنبعثة من GMSMapView، ثم سترسل هذا الحدث إلى SwiftUI. على وجه التحديد، عليك ضبط مفوّض لعرض الخريطة والاستماع إلى أحداث تحرّك الكاميرا، حتى أنّه عند التركيز على مدينة معيّنة وتحرّك كاميرا الخريطة من خلال إيماءة، سيتم إلغاء التركيز على عرض الخريطة لتتمكّن من رؤية المزيد من الخريطة.

Use SwiftUI Coordinators

GMSMapView تُصدر أحداثًا، مثل تغييرات موضع الكاميرا أو عند النقر على علامة. آلية الاستماع إلى هذه الأحداث هي من خلال البروتوكول GMSMapViewDelegate. تقدّم SwiftUI مفهوم "المنسّق" الذي يُستخدَم تحديدًا للعمل كمفوّض لوحدات التحكّم في العرض في UIKit. لذا، في عالم SwiftUI، يجب أن يكون Coordinator مسؤولاً عن الالتزام ببروتوكول GMSMapViewDelegate. لإجراء ذلك، أكمل الخطوات التالية:

  1. إنشاء منسّق باسم MapViewCoordinator ضمن MapViewControllerBridge

أنشئ فئة متداخلة داخل الفئة MapViewControllerBridge وسمِّها MapViewCoordinator. يجب أن يتوافق هذا الصف مع GMSMapViewDelegate وأن يعرّف MapViewControllerBridge كسمة.

MapViewControllerBridge

struct MapViewControllerBridge: UIViewControllerRepresentable {
  // ...
  final class MapViewCoordinator: NSObject, GMSMapViewDelegate {
    var mapViewControllerBridge: MapViewControllerBridge

    init(_ mapViewControllerBridge: MapViewControllerBridge) {
      self.mapViewControllerBridge = mapViewControllerBridge
    }
  }
}
  1. تنفيذ makeCoordinator() في MapViewControllerBridge

بعد ذلك، نفِّذ طريقة makeCoordinator() ضمن MapViewControllerBridge وأرجِع مثيلاً من MapViewCoodinator الذي أنشأته في الخطوة السابقة.

MapViewControllerBridge

struct MapViewControllerBridge: UIViewControllerRepresentable {
  // ...
  func makeCoordinator() -> MapViewCoordinator {
    return MapViewCoordinator(self)
  }
}
  1. ضبط MapViewCoordinator كعنصر التحكّم في عرض الخريطة

بعد إنشاء أداة التنسيق المخصّصة، تتمثّل الخطوة التالية في ضبط أداة التنسيق كمفوّض لعرض الخريطة في وحدة التحكّم في العرض. لإجراء ذلك، عدِّل عملية تهيئة وحدة التحكّم في العرض في makeUIViewController(context). يمكن الوصول إلى أداة التنسيق التي تم إنشاؤها من الخطوة السابقة من عنصر Context.

MapViewControllerBridge

struct MapViewControllerBridge: UIViewControllerRepresentable {
  // ...
  func makeUIViewController(context: Context) -> MapViewController {
    let uiViewController = MapViewController()
    uiViewController.map.delegate = context.coordinator
    return uiViewController
  }
}
  1. إضافة عملية إغلاق إلى MapViewControllerBridge حتى يمكن نشر حدث تحريك الكاميرا

بما أنّ الهدف هو تعديل طريقة العرض باستخدام حركات الكاميرا، عليك تعريف خاصية إغلاق جديدة تقبل قيمة منطقية ضمن MapViewControllerBridge باسم mapViewWillMove واستدعاء عملية الإغلاق هذه في طريقة التفويض mapView(_, willMove) ضمن MapViewCoordinator. مرِّر قيمة gesture إلى الإغلاق حتى يتمكّن عرض SwiftUI من الاستجابة فقط لأحداث تحريك الكاميرا المرتبطة بالإيماءات.

MapViewControllerBridge

struct MapViewControllerBridge: UIViewControllerRepresentable {
  var mapViewWillMove: (Bool) -> ()
  //...

  final class MapViewCoordinator: NSObject, GMSMapViewDelegate {
    // ...
    func mapView(_ mapView: GMSMapView, willMove gesture: Bool) {
      self.mapViewControllerBridge.mapViewWillMove(gesture)
    }
  }
}
  1. عدِّل ContentView لتمرير قيمة mapWillMove

مع إعلان الإغلاق الجديد في ‎MapViewControllerBridge، عدِّل ContentView لتمرير قيمة لهذا الإغلاق الجديد. ضمن عملية الإغلاق هذه، بدِّل "الحالة" zoomInCenter إلى false إذا كان حدث النقل مرتبطًا بإيماءة. سيؤدي ذلك إلى عرض الخريطة بملء الشاشة مرة أخرى عند تحريكها بإيماءة.

ContentView

struct ContentView: View {
  @State var zoomInCenter: Bool = false
  // ...
  var body: some View {
    // ...
    GeometryReader { geometry in
      ZStack(alignment: .top) {
        // Map
        let diameter = zoomInCenter ? geometry.size.width : (geometry.size.height * 2)
        MapViewControllerBridge(markers: $markers, selectedMarker: $selectedMarker, onAnimationEnded: {
          self.zoomInCenter = true
        }, mapViewWillMove: { (isGesture) in
          guard isGesture else { return }
          self.zoomInCenter = false
        })
        // ...
      }
    }
  }
}
  1. يمكنك الآن تشغيل التطبيق للاطّلاع على التغييرات الجديدة.

10. تهانينا

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

ما تعلّمته

  • الاختلافات بين SwiftUI وUIKit
  • كيفية الربط بين SwiftUI وUIKit باستخدام UIViewControllerRepresentable
  • كيفية إجراء تغييرات على طريقة عرض الخريطة باستخدام State وBinding
  • كيفية إرسال حدث من عرض الخريطة إلى SwiftUI باستخدام Coordinator

ما هي الخطوات التالية؟

  • حزمة تطوير البرامج بالاستناد إلى بيانات "خرائط Google" لتطبيقات iOS
    • المستندات الرسمية الخاصة بحزمة تطوير البرامج بالاستناد إلى بيانات "خرائط Google" لتطبيقات iOS
  • Places SDK for iOS: العثور على أنشطة تجارية محلية ونقاط اهتمام من حولك
  • maps-sdk-for-ios-samples
    • عيّنة للتعليمات البرمجية على GitHub توضّح جميع الميزات ضِمن حزمة تطوير البرامج بالاستناد إلى بيانات "خرائط Google" لأجهزة iOS
  • SwiftUI: مستندات Apple الرسمية حول SwiftUI
  • يُرجى مساعدتنا في إنشاء المحتوى الذي تراه الأكثر فائدة من خلال الإجابة عن الاستطلاع التالي:

ما هي جلسات الترميز الأخرى التي تريد المشاركة فيها؟

العرض المرئي للبيانات على الخرائط مزيد من المعلومات عن تخصيص نمط خرائطي إنشاء تفاعلات ثلاثية الأبعاد في الخرائط

هل يتعذّر عليك العثور على الدرس العملي الذي يهمّك أكثر؟ يمكنك طلب ذلك من خلال تقديم مشكلة جديدة هنا.