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

تنظيم صفحاتك في مجموعات يمكنك حفظ المحتوى وتصنيفه حسب إعداداتك المفضّلة.

1. قبل البدء

يشرح لك هذا الدرس التطبيقي كيفية استخدام SDK للخرائط لنظام التشغيل iOS مع SwiftUI.

لقطة شاشة-iphone-12-black@2x.png

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

  • المعرفة الأساسية لشركة Swift
  • الإلمام الأساسي بـ SwiftUI

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

  • فعِّل حزمة تطوير البرامج (SDK) لخدمة "خرائط Google" لنظام التشغيل iOS لإضافة "خرائط Google" إلى تطبيق iOS باستخدام SwiftUI.
  • إضافة علامات إلى الخريطة.
  • حالة الحالة من عرض SwiftUI إلى عنصر GMSMapView والعكس صحيح.

الأشياء التي تحتاج إليها

2. الإعداد

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

إعداد "منصة خرائط Google"

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

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

  1. فعِّل واجهات برمجة تطبيقات ومنصة SDK لمنصة "خرائط Google" المطلوبة لهذا الدرس التطبيقي في 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" SDK لنظام التشغيل iOS
  3. أخيرًا، افتح ملف GoogleMapsSwiftUI.xcodeproj في Xcode

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

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

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

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

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

5. استخدام SwiftUI مقابل UIKit

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

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

كما أن هناك بعض السلبيات:

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

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

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

استخدام معرّف UIKit وSwiftUI باستخدام UIViewRepresentable أو UIViewControllerRepresentable

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

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

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

إضافة-a-map-لقطة شاشة@2x.png

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

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

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

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

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

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

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

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

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

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

وحدة تحكم MapViewController

import GoogleMaps
import SwiftUI

struct MapViewControllerBridge: UIViewControllerRepresentable {

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

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

استخدام MapViewControllerB Bridge في ContentView

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

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

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

  1. ويجب إنشاء MapViewControllerBridge ضمن السمة body.

ضمن السمة body لهذا الملف، تم مسبقًا توفير ZStack وتنفيذها. يحتوي ZStack حاليًا على قائمة قابلة للتفاعل والسحب للمدن التي ستستخدمها في خطوة لاحقة. في الوقت الحالي، ضمن ZStack، يمكنك إنشاء MapViewControllerBridge كأول عرض فرعي لـ ZStack بحيث يتم عرض الخريطة في التطبيق خلف قائمة المدن. عند إجراء ذلك، يجب أن يظهر محتوى السمة body ضمن 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-flags@2x.png

علامات تشير إلى أنها الولاية

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

مشاهدة المحتوى

  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.

قائمة المدن

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)
      }
    }
  }
}

تمرير الحالة إلى MapViewControllerBbridge عبر الربط

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

  1. الإعلان عن خاصية markers جديدة في MapViewControllerBridge وإضافة تعليقات توضيحية عليها @Binding

وحدة تحكم MapViewController

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 خاصية ولاية تُسمى selectedMarker ويتم ضبطها على "القيمة" ويتم تعديلها عندما يتم اختيار مدينة في القائمة. تتم معالجة هذه المشكلة من خلال الملف الشخصي CitiesList في buttonAction ضمن ContentView.

مشاهدة المحتوى

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

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

وحدة تحكم MapViewController

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

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

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) على إنشاء سلسلة من الرسوم المتحركة للخريطة باستخدام الدالة GMSMapView's animate(with).

  1. تخطّي ContentView من selectedMarker إلى MapViewControllerBridge

بعد الإعلان عن الربط الجديد لـ MapViewControllerBridge، يُرجى تعديل ContentView لاجتياز selectedMarker حيث يتم إنشاء مثيل MapViewControllerBridge.

مشاهدة المحتوى

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).

وحدة تحكم MapViewController

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.

مشاهدة المحتوى

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

استخدام منسِّقي SwiftUI

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

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

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

وحدة تحكم MapViewController

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

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

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

وحدة تحكم MapViewController

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

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

وحدة تحكم MapViewController

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 من التفاعل فقط مع أحداث تحريك الكاميرا المتعلقة بالإيماءات.

وحدة تحكم MapViewController

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

مشاهدة المحتوى

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 الخاص بك باستخدام حزمة تطوير البرامج (SDK) لخدمة "خرائط Google" لنظام التشغيل iOS.

ما تعلّمته

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

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

ما هي الدروس التطبيقية الأخرى حول الترميز التي تريد الاطّلاع عليها؟

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

هل الدرس التطبيقي حول الترميز الذي تريده غير مدرج أعلاه؟ طلب حلول للمشكلة الجديدة هنا