إنشاء أول خريطة ثلاثية الأبعاد باستخدام SwiftUI

1. قبل البدء

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

تطبيق يعرض خريطة ثلاثية الأبعاد لمدينة سان فرانسيسكو

ستتعرّف على:

  • كيفية التحكّم في الكاميرا لعرض المواقع الجغرافية والتنقّل حول الخريطة
  • كيفية إضافة العلامات والنماذج
  • كيفية رسم الخطوط والمضلّعات
  • كيفية التعامل مع نقرات المستخدمين على علامات الأماكن

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

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

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

  • إعداد Xcode واستخدام Swift Package Manager لإدخال حزمة تطوير البرامج (SDK)
  • ضبط تطبيقك لاستخدام مفتاح واجهة برمجة التطبيقات
  • إضافة خريطة ثلاثية الأبعاد أساسية إلى تطبيقك
  • التحكّم في الكاميرا للانتقال إلى مواقع جغرافية معيّنة والتنقّل حولها
  • إضافة علامات وخطوط ومضلّعات ونماذج إلى خريطتك

المتطلبات

  • الإصدار 15 من Xcode أو إصدار أحدث

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

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

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

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

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

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

تفعيل حزمة تطوير البرامج بالاستناد إلى بيانات "خرائط Google" الثلاثية الأبعاد لأجهزة iOS

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

انقر على "تفعيل" لتفعيل واجهة برمجة التطبيقات في المشروع الذي اخترته.

تفعيل حزمة تطوير البرامج بالاستناد إلى بيانات "خرائط Google" الثلاثية الأبعاد في Google Console

3- إنشاء تطبيق SwiftUI أساسي

ملاحظة: يمكنك العثور على رمز الحل لكل خطوة في مستودع تطبيق نموذج دورة codelab على GitHub .

أنشئ تطبيقًا جديدًا في Xcode.

يمكن العثور على الرمز البرمجي لهذه الخطوة في المجلد GoogleMaps3DDemo على GitHub.

افتح Xcode وأنشئ تطبيقًا جديدًا. حدِّد SwiftUI.

اضبط اسم تطبيقك على GoogleMaps3DDemo واضبط اسم الحزمة على com.example.GoogleMaps3DDemo.

استيراد مكتبة GoogleMaps3D إلى مشروعك

أضِف حزمة تطوير البرامج (SDK) إلى مشروعك باستخدام أداة Swift Package Manager.

في مشروع Xcode أو مساحة العمل، انتقِل إلى ملف > إضافة تبعيات الحزمة. أدخِل https://github.com/googlemaps/ios-maps-3d-sdk كعنوان URL، واضغط على مفتاح Enter لسحب الحزمة، ثم انقر على "إضافة حزمة".

من نافذة "اختيار منتجات الحزمة"، تأكَّد من أنّه سيتمّ إضافة GoogleMaps3D إلى الاستهداف الرئيسي المحدّد. بعد الانتهاء، انقر على "إضافة حزمة".

للتحقّق من عملية التثبيت، انتقِل إلى اللوحة "العامة" لهدفك. في "الإطارات الأساسية" و"المكتبات" و"المحتوى المضمّن"، من المفترض أن تظهر الحِزم المثبَّتة. يمكنك أيضًا الاطّلاع على قسم "تبعيات الحزمة" في "مستكشف المشاريع" للتحقّق من الحزمة وإصدارها.

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

يمكنك تضمين مفتاح واجهة برمجة التطبيقات في التطبيق بشكلٍ ثابت، ولكن هذه ليست ممارسة جيدة. تتيح لك إضافة ملفّ ضبط الاحتفاظ بسرية مفتاح واجهة برمجة التطبيقات وتجنُّب التحقّق منه في أداة التحكّم في المصدر.

أنشئ ملف إعدادات جديدًا في المجلد الجذر للمشروع.

في Xcode، تأكَّد من أنّك تطّلع على نافذة "مستكشف المشاريع". انقر بزر الماوس الأيمن على جذر المشروع واختَر "ملف جديد من نموذج". انتقِل للأسفل إلى أن يظهر لك "ملف إعدادات الضبط". اختَر هذا الخيار وانقر على "التالي". أدخِل اسم الملف Config.xcconfig وتأكَّد من اختيار المجلد الجذر للمشروع. انقر على "إنشاء" لإنشاء الملف.

في المحرِّر، أضِف سطرًا إلى ملف الإعدادات على النحو التالي: MAPS_API_KEY = YOUR_API_KEY

استبدِل YOUR_API_KEY بمفتاح واجهة برمجة التطبيقات.

أضِف هذا الإعداد إلى Info.plist.

لإجراء ذلك، اختَر جذر المشروع وانقر على علامة التبويب "معلومات".

أضِف سمة جديدة باسم MAPS_API_KEY وقيمة $(MAPS_API_KEY).

يحتوي نموذج رمز التطبيق على ملف Info.plist يحدِّد هذه السمة.

إضافة خريطة

افتح الملف الذي يُسمى GoogleMaps3DDemoApp.swift. هذه هي نقطة الدخول وشريط التنقّل الرئيسي في تطبيقك.

يتصل بـ ContentView() الذي يعرض رسالة "مرحبًا".

افتح ContentView.swift في المحرِّر.

أضِف بيان import لـ GoogleMaps3D.

احذف الرمز البرمجي داخل مجموعة الرموز var body: some View {}. وضِّح Map() جديدًا داخل body.

الحد الأدنى للإعدادات التي تحتاجها لبدء تشغيل Map هو MapMode. تتوفّر لهذا الحقل قيمتان محتملتان:

  • .hybrid - صور الأقمار الصناعية التي تتضمّن الطرق والعلامات
  • .satellite - صور الأقمار الصناعية فقط

اختَر ".hybrid".

من المفترض أن يظهر ملف ContentView.swift على النحو التالي.

import GoogleMaps3D
import SwiftUI

@main
struct ContentView: View {
    var body: some View {
      Map(mode: .hybrid)
    }
}

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

يجب ضبط مفتاح واجهة برمجة التطبيقات قبل بدء تشغيل "الخريطة".

يمكنك إجراء ذلك من خلال ضبط Map.apiKey في معالِج حدث init() لأي View يحتوي على خريطة. يمكنك أيضًا ضبطه في GoogleMaps3DDemoApp.swift قبل الاتصال بـ ContentView().

في GoogleMaps3DDemoApp.swift، اضبط Map.apiKey في معالِج الحدث onAppear للعنصر WindowGroup.

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

استخدِم Bundle.main.infoDictionary للوصول إلى إعداد MAPS_API_KEY الذي أنشأته في ملف الإعداد.

import GoogleMaps3D
import SwiftUI

@main
struct GoogleMaps3DDemoApp: App {
  var body: some Scene {
    WindowGroup {
      ContentView()
    }
    .onAppear {
      guard let infoDictionary: [String: Any] = Bundle.main.infoDictionary else {
        fatalError("Info.plist not found")
      }
      guard let apiKey: String = infoDictionary["MAPS_API_KEY"] as? String else {
        fatalError("MAPS_API_KEY not set in Info.plist")
      }
      Map.apiKey = apiKey
    }
  }
}

أنشئ تطبيقك وشغِّله للتأكّد من تحميله بشكل صحيح. من المفترض أن تظهر لك خريطة للكرة الأرضية.

خريطة ثلاثية الأبعاد تعرض الأرض

4. استخدام كاميرا للتحكّم في عرض الخريطة

إنشاء عنصر حالة الكاميرا

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

عرض خريطة ثلاثية الأبعاد لمدينة سان فرانسيسكو

إنشاء فئة Helpers لتخزين إعدادات الكاميرا

أضِف ملفًا جديدًا فارغًا باسم MapHelpers.swift. في ملفك الجديد، استورِد GoogleMaps3D وأضِف إضافة إلى فئة Camera. أضِف متغيّرًا باسم sanFrancisco. اضبط هذا المتغيّر ككائن Camera جديد. حدِّد مكان الكاميرا في latitude: 37.39, longitude: -122.08.

import GoogleMaps3D

extension Camera {
 public static var sanFrancisco: Camera = .init(latitude: 37.39, longitude: -122.08)
}

إضافة عرض جديد إلى تطبيقك

أنشئ ملفًا جديدًا باسم CameraDemo.swift. أضِف المخطط الأساسي لعرض SwiftUI جديد إلى الملف.

أضِف متغيّر @State باسم camera من النوع Camera. اضبطها على كاميرا sanFrancisco التي حدّدتها للتو.

يتيح لك استخدام @State ربط الخريطة بحالة الكاميرا واستخدامها كمصدر معلومات.

@State var camera: Camera = .sanFrancisco

غيِّر طلب استدعاء الدالة Map() لتضمين سمة camera. استخدِم عملية ربط حالة الكاميرا $camera لبدء تشغيل الخاصية camera في عنصر @State للكاميرا (.sanFrancisco).

import SwiftUI
import GoogleMaps3D

struct CameraDemo: View {
  @State var camera: Camera = .sanFrancisco
  var body: some View {
    VStack {
      Map(camera: $camera, mode: .hybrid)
    }
  }
}

إضافة واجهة مستخدم أساسية للتنقّل إلى تطبيقك

أضِف NavigationView إلى نقطة دخول التطبيق الرئيسية، GoogleMaps3DDemoApp.swift.

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

عدِّل GoogleMaps3DDemoApp.swift لإضافة NavigationView جديدة.

أضِف List يحتوي على بيانَي NavigationLink.

من المفترض أن يؤدي النقر على NavigationLink الأول إلى فتح ContentView() مع وصف Text Basic Map.

من المفترض أن يفتح الرمز NavigationLink الثاني CameraDemo().

...
      NavigationView {
        List {
          NavigationLink(destination: ContentView()) {
            Text("Basic Map")
          }
          NavigationLink(destination: CameraDemo()) {
            Text("Camera Demo")
          }
        }
      }
...

إضافة معاينة Xcode

المعاينات هي ميزة قوية في Xcode تتيح لك الاطّلاع على تطبيقك والتفاعل معه أثناء إجراء تغييرات عليه.

لإضافة معاينة، افتح CameraDemo.swift. أضِف مجموعة رموز #Preview {} خارج struct.

#Preview {
 CameraDemo()
}

افتح لوحة المعاينة أو أعِد تحميلها في Xcode. من المفترض أن تظهر مدينة سان فرانسيسكو على الخريطة.

إعداد طرق عرض مخصّصة ثلاثية الأبعاد

يمكنك تحديد مَعلمات إضافية للتحكّم في الكاميرا:

  • heading: الاتجاه بالدرجات من الشمال لتوجيه الكاميرا نحوه
  • tilt: زاوية الإمالة بالدرجات، حيث تكون القيمة 0 فوق الرأس مباشرةً والقيمة 90 للنظر أفقيًا
  • roll: زاوية الدوران حول المستوى العمودي للكاميرا، بالدرجات
  • range: المسافة بالمتر بين الكاميرا وموقع خط العرض وخط الطول
  • altitude: ارتفاع الكاميرا فوق مستوى سطح البحر

في حال عدم تقديم أيّ من هذه المَعلمات الإضافية، سيتمّ استخدام القيم التلقائية.

لعرض المزيد من البيانات الثلاثية الأبعاد في عرض الكاميرا، اضبط المَعلمات الأولية لعرض عرض أقرب ومائل.

عدِّل Camera الذي حدّدته في MapHelpers.swift لتضمين قيم altitude وheading وtilt وroll وrange.

public static var sanFrancisco: Camera = .init(
  latitude: 37.7845812,
  longitude: -122.3660241,
  altitude: 585,
  heading: 288.0,
  tilt: 75.0,
  roll: 0.0,
  range: 100)

أنشئ التطبيق وشغِّله للاطّلاع على العرض الثلاثي الأبعاد الجديد واستكشافه.

5- الصور المتحركة الأساسية للكاميرا

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

خريطة ثلاثية الأبعاد لمدينة سياتل

الانتقال إلى موقع جغرافي

ستستخدم طريقة Map.flyCameraTo() لتحريك الكاميرا من الموقع الجغرافي الأولي إلى موقع جديد.

تأخذ طريقة flyCameraTo() عددًا من المَعلمات:

  • Camera يمثّل الموقع الجغرافي النهائي
  • duration: مدة عرض الصورة المتحركة، بالثواني
  • trigger: عنصر قابل للتتبّع يؤدي إلى تشغيل الصورة المتحركة عند تغيير حالته.
  • completion: الرمز الذي سيتم تنفيذه عند اكتمال الحركة

تحديد موقع جغرافي للسفر إليه

افتح ملف MapHelpers.swift.

حدِّد عنصر كاميرا جديدًا لعرض مدينة دبي.

public static var seattle: Camera = .init(latitude:
47.6210296,longitude: -122.3496903, heading: 149.0, tilt: 77.0, roll: 0.0, range: 4000)

أضِف زرًا لتشغيل الصورة المتحركة.

افتح CameraDemo.swift. عرِّف متغيّرًا منطقيًا جديدًا داخل struct.

اضبط اسمها على animate مع قيمة أولية هي false.

@State private var animate: Bool = false

أضِف Button أسفل VStack. سيؤدي Button إلى بدء الصورة المتحركة للخريطة.

امنح Button بعض Text المناسبة، مثل "بدء الطيران".

import SwiftUI
import GoogleMaps3D

struct CameraDemo: View {
  @State var camera:Camera = .sanFrancisco
  @State private var animate: Bool = false

  var body: some View {
    VStack{
      Map(camera: $camera, mode: .hybrid)
      Button("Start Flying") {
      }
    }
  }
}

في إغلاق الزر، أضِف رمزًا لتبديل حالة المتغيّر animate.

      Button("Start Flying") {
        animate.toggle()
      }

ابدأ الصورة المتحركة.

أضِف الرمز البرمجي لتشغيل الحركة flyCameraTo() عند تغيير حالة المتغيّر animate.

  var body: some View {
    VStack{
      Map(camera: $camera, mode: .hybrid)
        .flyCameraTo(
          .seattle,
          duration: 5,
          trigger: animate,
          completion: {  }
        )
      Button("Start Flying") {
        animate.toggle()
      }
    }
  }

التنقّل حول موقع جغرافي

يمكن الطيران حول موقع جغرافي باستخدام طريقة Map.flyCameraAround(). تستخدِم هذه الطريقة عدة مَعلمات:

  • Camera لتحديد الموقع الجغرافي وطريقة العرض
  • duration ثانية
  • rounds: عدد مرات تكرار الصورة المتحركة
  • trigger: عنصر قابل للتتبّع سيؤدي إلى تشغيل الحركة
  • callback: الرمز الذي سيتم تنفيذه عند تشغيل الصورة المتحركة

حدِّد متغيّر @State جديدًا باسم flyAround، مع قيمة أولية هي false.

بعد إجراء ذلك، أضِف طلبًا إلى flyCameraAround() مباشرةً بعد طلب الطريقة flyCameraTo().

يجب أن تكون مدة الطيران حول العنصر طويلة نسبيًا لكي يتغيّر العرض بسلاسة.

احرص على بدء الحركة flyCameraAround() من خلال تغيير حالة عنصر التفعيل عند اكتمال flyCameraTo().

من المفترض أن تظهر علامتك على النحو التالي:

import SwiftUI
import GoogleMaps3D

struct CameraDemo: View {
  @State var camera:Camera = .sanFrancisco
  @State private var animate: Bool = false
  @State private var flyAround: Bool = false

  var body: some View {
    VStack{
      Map(camera: $camera, mode: .hybrid)
        .flyCameraTo(
          .seattle,
          duration: 5,
          trigger: animate,
          completion: { flyAround = true }
        )
        .flyCameraAround(
          .seattle,
          duration: 15,
          rounds: 0.5,
          trigger: flyAround,
          callback: {  }
        )
      Button("Start Flying") {
        animate.toggle()
      }
    }
  }
}

#Preview {
  CameraDemo()
}

يمكنك معاينة التطبيق أو تشغيله لمعرفة أنّ الكاميرا تطير حول الوجهة بعد اكتمال الصورة المتحركة flyCameraTo().

6. أضِف علامة إلى خريطتك.

في هذه الخطوة، ستتعرّف على كيفية رسم علامة على الخريطة.

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

خريطة ثلاثية الأبعاد تعرض علامة على الخريطة

أنشئ عرض SwiftUI جديدًا لعرض "العلامة" التجريبي.

أضِف ملف Swift جديدًا إلى مشروعك. يُرجى الاتصال برقم MarkerDemo.swift.

أضِف مخطّط SwiftUI View وأدخِل الخريطة كما فعلت في CameraDemo.

import SwiftUI
import GoogleMaps3D

struct MarkerDemo: View {
  @State var camera: Camera = .sanFrancisco
  var body: some View {
    VStack {
      Map(camera: $camera, mode: .hybrid)
    }
  }
}

بدء عنصر Marker

أدخِل متغيّر علامة جديدًا باسم mapMarker. في أعلى مجموعة رموز struct في MarkerDemo.swift.

ضَع التعريف في السطر تحت بيان camera. يعمل نموذج الرمز البرمجي هذا على إعداد جميع المواقع المتاحة.

  @State var mapMarker: Marker = .init(
    position: .init(
      latitude: 37.8044862,
      longitude: -122.4301493,
      altitude: 0.0),
    altitudeMode: .absolute,
    collisionBehavior: .required,
    extruded: false,
    drawsWhenOccluded: true,
    sizePreserved: true,
    zIndex: 0,
    label: "Test"
  )

أضِف العلامة إلى خريطتك.

لرسم العلامة، أضِفها إلى إغلاق يتم استدعاؤه عند إنشاء الخريطة.

struct MarkerDemo: View {
  @State var camera: Camera = .sanFrancisco
  var body: some View {
    VStack {
      Map(camera: $camera, mode: .hybrid) {
        mapMarker
      }
    }
  }
}

أضِف NavigationLink جديدًا إلى GoogleMaps3DDemoApp.swift مع وجهة MarkerDemo() وText يصفها على أنّها "عرض توضيحي للعلامة".

...
      NavigationView {
        List {
          NavigationLink(destination: Map()) {
            Text("Basic Map")
          }
          NavigationLink(destination: CameraDemo()) {
            Text("Camera Demo")
          }
          NavigationLink(destination: MarkerDemo()) {
            Text("Marker Demo")
          }
        }
      }
...

معاينة تطبيقك وتشغيله

أعِد تحميل المعاينة أو شغِّل تطبيقك للاطّلاع على العلامة.

العلامات المُشكّلة بالانبثاق

يمكن وضع العلامات فوق الأرض أو الشبكة الثلاثية الأبعاد باستخدام altitude وaltitudeMode.

انسخ بيان mapMarker في MarkerDemo.swift إلى متغيّر Marker جديد باسم extrudedMarker.

اضبط قيمة غير صفرية لسمة altitude، وتكون القيمة 50 كافية.

غيِّر altitudeMode إلى .relativeToMesh، واضبط extruded على true. استخدِم latitude وlongitude من مقتطف الرمز البرمجي هنا لوضع العلامة على قمة ناطحة سحاب.

  @State var extrudedMarker: Marker = .init(
    position: .init(
      latitude: 37.78980534,
      longitude:  -122.3969349,
      altitude: 50.0),
    altitudeMode: .relativeToMesh,
    collisionBehavior: .required,
    extruded: true,
    drawsWhenOccluded: true,
    sizePreserved: true,
    zIndex: 0,
    label: "Extruded"
  )

تشغيل التطبيق أو معاينته مرة أخرى من المفترض أن يظهر العلامة على سطح مبنى ثلاثي الأبعاد.

7. أضِف نموذجًا إلى خريطتك.

يمكن إضافة Model بالطريقة نفسها التي تُستخدَم لإضافة Marker. ستحتاج إلى ملف نموذج يمكن الوصول إليه من خلال عنوان URL أو إضافته كملف محلي في مشروعك. في هذه الخطوة، سنستخدم ملفًا محليًا يمكنك تنزيله من مستودع GitHub الخاص بهذا الدليل التعليمي.

خريطة ثلاثية الأبعاد لمدينة سان فرانسيسكو مع نموذج منطاد هوائي

إضافة ملف نموذج إلى مشروعك

أنشئ مجلدًا جديدًا في مشروع Xcode باسم Models.

نزِّل النموذج من مستودع نماذج التطبيقات على GitHub. أضِفه إلى مشروعك من خلال سحبه إلى المجلد الجديد في عرض مشروع Xcode.

تأكَّد من ضبط الاستهداف ليكون الاستهداف الرئيسي لتطبيقك.

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

أضِف النموذج إلى تطبيقك.

أنشئ ملف SwiftUI جديدًا باسم ModelDemo.swift.

أضِف عبارات import لكلّ من SwiftUI وGoogleMaps3D كما في الخطوات السابقة.

أدرِج Map داخل VStack في body.

import SwiftUI
import GoogleMaps3D

struct ModelDemo: View {
  @State var camera: Camera = .sanFrancisco
  
  var body: some View {
    VStack {
      Map(camera: $camera, mode: .hybrid) {
        
      }
    }
  }
}

احصل على مسار النموذج من الحِزمة. أضِف الرمز الخاص بهذا الإجراء خارج struct.

private let fileUrl = Bundle.main.url(forResource: "balloon", withExtension: "glb")

يمكنك تحديد متغيّر لنموذجك داخل البنية.

أدخِل قيمة تلقائية في حال عدم تقديم fileUrl.

  @State var balloonModel: Model = .init(
    position: .init(
      latitude: 37.791376,
      longitude: -122.397571,
      altitude: 300.0),
    url: URL(fileURLWithPath: fileUrl?.relativePath ?? ""),
    altitudeMode: .absolute,
    scale: .init(x: 5, y: 5, z: 5),
    orientation: .init(heading: 0, tilt: 0, roll: 0)
  )

3- استخدِم النموذج مع خريطتك.

كما هو الحال مع إضافة Marker، ما عليك سوى تقديم مرجع Model في بيان Map.

  var body: some View {
    VStack {
      Map(camera: $camera, mode: .hybrid) {
        balloonModel
      }
    }
  }

معاينة تطبيقك وتشغيله

أضِف NavigationLink جديدًا إلى GoogleMaps3DDemoApp.swift، مع تحديد وجهة ModelDemo() وText "نموذج العرض".

...
          NavigationLink(destination: ModelDemo()) {
            Text("Model Demo")
          }
...

أعِد تحميل المعاينة أو شغِّل تطبيقك للاطّلاع على النموذج.

8. ارسم خطًا ومضلّعًا على الخريطة.

في هذه الخطوة، ستتعرّف على كيفية إضافة خطوط وأشكال مضلّعات إلى خريطتك الثلاثية الأبعاد.

ولتبسيط الأمر، يمكنك تحديد الأشكال على أنّها مصفوفات من عناصر LatLngAltitude. في التطبيق الفعلي، قد يتم تحميل البيانات من ملف أو طلب بيانات من واجهة برمجة التطبيقات أو قاعدة بيانات.

خريطة ثلاثية الأبعاد لمدينة سان فرانسيسكو تعرض مضلّعَين وخطًا متعدّد الخطوط

أنشئ بعض عناصر الأشكال لإدارة بيانات الأشكال.

أضِف تعريفًا جديدًا لـ Camera إلى MapHelpers.swift يعرض وسط مدينة سان فرانسيسكو.

  public static var downtownSanFrancisco: Camera = .init(latitude: 37.7905, longitude: -122.3989, heading: 25, tilt: 71, range: 2500) 

أضِف ملفًا جديدًا إلى مشروعك باسم ShapesDemo.swift. أضِف struct باسم ShapesDemo ينفِّذ بروتوكول View، وأضِف body إليه.

struct ShapesDemo: View {
  @State var camera: Camera = .downtownSanFrancisco

  var body: some View {
    VStack {
      Map(camera: $camera, mode: .hybrid) {

      }
    }
  }
}

فئتا البيانات التي ستستخدمهما لإدارة بيانات الأشكال هما Polyline وPolygon. افتح ShapesDemo.swift وأضِفه إلى struct على النحو التالي.

var polyline: Polyline = .init(coordinates: [
    LatLngAltitude(latitude: 37.80515638571346, longitude: -122.4032569467164, altitude: 0),
    LatLngAltitude(latitude: 37.80337073509504, longitude: -122.4012878349353, altitude: 0),
    LatLngAltitude(latitude: 37.79925208843463, longitude: -122.3976697250461, altitude: 0),
    LatLngAltitude(latitude: 37.7989102378512, longitude: -122.3983408725656, altitude: 0),
    LatLngAltitude(latitude: 37.79887832784348, longitude: -122.3987094864192, altitude: 0),
    LatLngAltitude(latitude: 37.79786443410338, longitude: -122.4066878788802, altitude: 0),
    LatLngAltitude(latitude: 37.79549248916587, longitude: -122.4032992702785, altitude: 0),
    LatLngAltitude(latitude: 37.78861484290265, longitude: -122.4019489189814, altitude: 0),
    LatLngAltitude(latitude: 37.78618687561075, longitude: -122.398969592545, altitude: 0),
    LatLngAltitude(latitude: 37.7892310309145, longitude: -122.3951458683092, altitude: 0),
    LatLngAltitude(latitude: 37.7916358762409, longitude: -122.3981969390652, altitude: 0)
  ])
  .stroke(GoogleMaps3D.Polyline.StrokeStyle(
    strokeColor: UIColor(red: 0.09803921568627451, green: 0.403921568627451, blue: 0.8235294117647058, alpha: 1),
    strokeWidth: 10.0,
    outerColor: .white,
    outerWidth: 0.2
    ))
  .contour(GoogleMaps3D.Polyline.ContourStyle(isGeodesic: true))

  var originPolygon: Polygon = .init(outerCoordinates: [
    LatLngAltitude(latitude: 37.79165766856578, longitude:  -122.3983762901255, altitude: 300),
    LatLngAltitude(latitude: 37.7915324439261, longitude:  -122.3982171091383, altitude: 300),
    LatLngAltitude(latitude: 37.79166617650914, longitude:  -122.3980478493319, altitude: 300),
    LatLngAltitude(latitude: 37.79178986470217, longitude:  -122.3982041104199, altitude: 300),
    LatLngAltitude(latitude: 37.79165766856578, longitude:  -122.3983762901255, altitude: 300 )
  ],
  altitudeMode: .relativeToGround)
  .style(GoogleMaps3D.Polygon.StyleOptions(fillColor:.green, extruded: true) )

  var destinationPolygon: Polygon = .init(outerCoordinates: [
      LatLngAltitude(latitude: 37.80515661739527, longitude:  -122.4034307490334, altitude: 300),
      LatLngAltitude(latitude: 37.80503794515428, longitude:  -122.4032633416024, altitude: 300),
      LatLngAltitude(latitude: 37.80517850164195, longitude:  -122.4031056058006, altitude: 300),
      LatLngAltitude(latitude: 37.80529346901115, longitude:  -122.4032622466595, altitude: 300),
      LatLngAltitude(latitude: 37.80515661739527, longitude:  -122.4034307490334, altitude: 300 )
  ],
  altitudeMode: .relativeToGround)
  .style(GoogleMaps3D.Polygon.StyleOptions(fillColor:.red, extruded: true) )

لاحِظ مَعلمات الإعداد المستخدَمة.

  • يُستخدَم altitudeMode: .relativeToGround لتوسيع المضلّعات إلى ارتفاع معيّن فوق سطح الأرض.
  • يتم استخدام altitudeMode: .clampToGround لجعل الخطوط المتعددة تتبع شكل سطح الأرض.
  • يتم ضبط الأنماط على عناصر Polygon من خلال ربط طلب إجراء بـ styleOptions() بعد استدعاء .init()

إضافة الأشكال إلى الخريطة

تمامًا كما في الخطوات السابقة، يمكن إضافة الأشكال مباشرةً إلى إغلاق Map. أنشئ Map داخل VStack.

...
  var body: some View {
    VStack {
      Map(camera: $camera, mode: .hybrid) {
        polyline
        originPolygon
        destinationPolygon
      }
    }
  }
...

معاينة تطبيقك وتشغيله

أضِف رمز المعاينة وتحقّق من تطبيقك في لوحة المعاينة في Xcode.

#Preview {
  ShapesDemo()
}

لتشغيل تطبيقك، أضِف NavigationLink جديدًا إلى GoogleMaps3DDemoApp.swift يفتح عرض المعاينة الجديد.

...
          NavigationLink(destination: ShapesDemo()) {
            Text("Shapes Demo")
          }
...

شغِّل تطبيقك واستكشِف الأشكال التي أضفتها.

9- التعامل مع أحداث النقر على محدِّدات المواقع

في هذه الخطوة، ستتعرّف على كيفية الردّ على نقرات المستخدم على علامات الأماكن.

خريطة تعرض نافذة منبثقة تتضمّن معرّف مكان

ملاحظة: للاطّلاع على محددات الأماكن على الخريطة، عليك ضبط MapMode على .hybrid.

يتطلب التعامل مع النقرة تنفيذ طريقة Map.onPlaceTap.

يقدّم الحدث onPlaceTap عنصر PlaceTapInfo يمكنك من خلاله الحصول على رقم تعريف المكان لعلامة المكان التي تم النقر عليها.

يمكنك استخدام معرّف المكان للاطّلاع على مزيد من التفاصيل باستخدام حزمة تطوير البرامج (SDK) للأماكن أو Places API.

إضافة عرض Swift جديد

أضِف الرمز البرمجي التالي إلى ملف Swift جديد باسم PlaceTapDemo.swift.

import GoogleMaps3D
import SwiftUI

struct PlaceTapDemo: View {
  @State var camera: Camera = .sanFrancisco
  @State var isPresented = false
  @State var tapInfo: PlaceTapInfo?

  var body: some View {
    Map(camera: $camera, mode: .hybrid)
      .onPlaceTap { tapInfo in
        self.tapInfo = tapInfo
        isPresented.toggle()
      }
      .alert(
        "Place tapped - \(tapInfo?.placeId ?? "nil")",
        isPresented: $isPresented,
        actions: { Button("OK") {} }
      )
  }
}
#Preview {
  PlaceTapDemo()
}

معاينة تطبيقك وتشغيله

افتح لوحة المعاينة لمعاينة التطبيق.

لتشغيل التطبيق، أضِف NavigationLink جديدًا إلى GoogleMaps3DDemoApp.swift.

...
          NavigationLink(destination: PlaceTapDemo()) {
            Text("Place Tap Demo")
          }
...

10. (اختياري) اتّخاذ مزيد من الإجراءات

الصور المتحركة المتقدّمة في الكاميرا

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

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

عرض الخريطة الثلاثي الأبعاد للاقتراب من إنسبروك

حمِّل ملفًا يحتوي على تسلسل للمواقع الجغرافية.

نزِّل flightpath.json من مستودع GitHub لتطبيقات النماذج.

أنشئ مجلدًا جديدًا في مشروع Xcode باسم JSON.

اسحب flightpath.json إلى مجلد JSON في Xcode.

اضبط الاستهداف ليكون الاستهداف الرئيسي لتطبيقك. تأكَّد من أنّ إعدادات "موارد حِزم النسخ" في مشروعك تتضمّن هذا الملف.

أنشئ ملفين جديدَين بتنسيق Swift في تطبيقك باسم FlightPathData.swift وFlightDataLoader.swift.

انسخ الرمز البرمجي التالي إلى تطبيقك. ينشئ هذا الرمز البرمجي بنى وفئَات تقرأ ملفًا محليًا باسم "flighpath.json" وتُفكّ تشفيره بتنسيق JSON.

تمثّل بنية FlightPathData وFlightPathLocation بنية البيانات في ملف JSON كعناصر Swift.

تقرأ فئة FlightDataLoader البيانات من الملف وتُفكّ تشفيرها. ويعتمد هذا البروتوكول ObservableObject للسماح لتطبيقك برصد التغييرات في بياناته.

يتم عرض البيانات التي تم تحليلها من خلال موقع منشور.

FlightPaths.swift

import GoogleMaps3D

struct FlightPathData: Decodable {
  let flight: [FlightPathLocation]
}

struct FlightPathLocation: Decodable {
  let timestamp: Int64
  let latitude: Double
  let longitude: Double
  let altitude: Double
  let bearing: Double
  let speed: Double
}

FlightDataLoader.swift

import Foundation

public class FlightDataLoader : ObservableObject {

  @Published var flightPathData: FlightPathData = FlightPathData(flight:[])
  @Published var isLoaded: Bool = false

  public init() {
    load("flightpath.json")
  }
  
  public func load(_ path: String) {
    if let url = Bundle.main.url(forResource: path, withExtension: nil){
      if let data = try? Data(contentsOf: url){
        let jsondecoder = JSONDecoder()
        do{
          let result = try jsondecoder.decode(FlightPathData.self, from: data)
          flightPathData = result
          isLoaded = true
        }
        catch {
          print("Error trying to load or parse the JSON file.")
        }
      }
    }
  }
}

تحريك الكاميرا في كل موقع جغرافي

لنقل الكاميرا بين تسلسل من الخطوات، استخدِم KeyframeAnimator.

سيتم إنشاء كل Keyframe كCubicKeyframe، وبالتالي يتم عرض التغييرات في حالة الكاميرا بشكل متحرك بسلاسة.

سيؤدي استخدام flyCameraTo() إلى "تبديل" العرض بين كل موقع جغرافي.

أولاً، عليك تحديد كاميرا باسم "innsbruck" في MapHelpers.swift.

public static var innsbruck: Camera = .init(
  latitude: 47.263,
  longitude: 11.3704,
  altitude: 640.08,
  heading: 237,
  tilt: 80.0,
  roll: 0.0,
  range: 200)

الآن، يمكنك إعداد "عرض" جديد في ملف جديد باسم FlyAlongRoute.swift.

استورِد SwiftUI وGoogleMaps3D. أضِف Map وButton داخل VStack. اضبط Button لتبديل حالة المتغيّر المنطقي animation.

يمكنك تحديد عنصر State لـ FlightDataLoader، ما سيؤدي إلى تحميل ملف JSON عند إعداده.

import GoogleMaps3D
import SwiftUI

struct FlyAlongRoute: View {
  @State private var camera: Camera = .innsbruck
  @State private var flyToDuration: TimeInterval = 5
  @State var animation: Bool = true

  @StateObject var flightData: FlightDataLoader = FlightDataLoader()

  var body: some View {
    VStack {
      Map(camera: $camera, mode: .hybrid)
      Button("Fly Along Route"){
        animation.toggle()
      }
    }
  }
}

إنشاء الإطارات الرئيسية

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

أضِف دالتَين إلى بنية FlyAlongRoute. تعرض الدالة makeKeyFrame CubicKeyframe مع حالة الكاميرا. تأخذ الدالة makeCamera خطوة في تسلسل بيانات الرحلة وتُرجع عنصر Camera يمثّل الخطوة.

func makeKeyFrame(step: FlightPathLocation) -> CubicKeyframe<Camera> {
  return CubicKeyframe(
    makeCamera(step: step),
    duration: flyToDuration
  )
}

func makeCamera(step: FlightPathLocation) -> Camera {
  return .init(
    latitude: step.latitude,
    longitude: step.longitude,
    altitude: step.altitude,
    heading: step.bearing,
    tilt: 75,
    roll: 0,
    range: 200
  )
}

تجميع الصورة المتحركة

استخدِم keyframeAnimator بعد بدء Map وضبط القيم الأولية.

ستحتاج إلى حالة كاميرا أولية استنادًا إلى الموقع الجغرافي الأول في مسار الرحلة.

يجب أن يتم تشغيل الرسوم المتحركة استنادًا إلى حالة متغيّر متغيّر.

يجب أن يكون محتوى keyframeAnimator خريطة.

يتم إنشاء القائمة الفعلية للّقطات الرئيسية من خلال تكرار كل موقع في مسار الرحلة.

   VStack {
      Map(camera: $camera, mode: .hybrid)
        .keyframeAnimator(
          initialValue: makeCamera(step: flightData.flightPathData.flight[0]),
          trigger: animation,
          content: { view, value in
            Map(camera: .constant(value), mode: .hybrid)
          },
          keyframes: { _ in
            KeyframeTrack(content: {
              for i in  1...flightData.flightPathData.flight.count-1 {
                makeKeyFrame(step: flightData.flightPathData.flight[i])
              }
            })
          }
        )
   }

يمكنك معاينة تطبيقك وتشغيله.

افتح لوحة المعاينة لمعاينة "العرض".

أضِف NavigationLink جديدًا بوجهة FlightPathDemo() إلى GoogleMaps3DDemoApp.swift وشغِّل التطبيق لتجربته.

11. تهانينا

لقد أنشأت تطبيقًا بنجاح

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

ما تعلمته

  • كيفية إضافة حزمة GoogleMaps3D إلى تطبيق Xcode SwiftUI
  • كيفية إعداد خريطة ثلاثية الأبعاد باستخدام مفتاح واجهة برمجة التطبيقات وطريقة عرض تلقائية
  • كيفية إضافة علامات وتصاميم ثلاثية الأبعاد وخطوط ومضلّعات إلى خريطتك
  • كيفية التحكّم في الكاميرا لإنشاء صورة متحركة للانتقال إلى موقع آخر
  • كيفية التعامل مع أحداث النقر على علامات الأماكن

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

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

ما هي ورشات عمل الترميز الأخرى التي تريد رؤيتها؟

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