با SwiftUI (Swift) یک نقشه به برنامه iOS خود اضافه کنید

1. قبل از شروع

این کد لبه به شما می آموزد که چگونه از Maps SDK برای iOS با SwiftUI استفاده کنید.

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

پیش نیازها

  • دانش اولیه سوئیفت
  • آشنایی اولیه با SwiftUI

کاری که خواهی کرد

  • Maps SDK برای iOS را فعال کنید و از آن استفاده کنید تا Google Maps را با استفاده از SwiftUI به برنامه iOS اضافه کنید.
  • نشانگرها را به نقشه اضافه کنید.
  • حالت عبور بین یک SwiftUI و یک شی GMSMapView .

آنچه شما نیاز دارید

2. راه اندازی شوید

برای مرحله فعال سازی زیر، Maps SDK را برای iOS فعال کنید.

پلتفرم نقشه های گوگل را راه اندازی کنید

اگر قبلاً یک حساب Google Cloud Platform و پروژه‌ای با صورت‌حساب فعال ندارید، لطفاً راهنمای شروع به کار با Google Maps Platform را برای ایجاد یک حساب صورت‌حساب و یک پروژه ببینید.

  1. در Cloud Console ، روی منوی کشویی پروژه کلیک کنید و پروژه ای را که می خواهید برای این کد لبه استفاده کنید انتخاب کنید.

  1. APIها و SDKهای پلتفرم Google Maps مورد نیاز برای این لبه کد را در Google Cloud Marketplace فعال کنید. برای انجام این کار، مراحل این ویدیو یا این مستند را دنبال کنید.
  2. یک کلید API در صفحه Credentials در Cloud Console ایجاد کنید. می توانید مراحل این ویدئو یا این مستند را دنبال کنید. همه درخواست‌ها به پلتفرم Google Maps به یک کلید API نیاز دارند.

3. کد شروع را دانلود کنید

برای شروع هر چه سریع‌تر، در اینجا چند کد شروع وجود دارد که به شما کمک می‌کند تا این کد را دنبال کنید. از شما استقبال می شود که به سراغ راه حل بروید، اما اگر می خواهید تمام مراحل ساخت آن را خودتان دنبال کنید، به خواندن ادامه دهید.

  1. اگر git را نصب کرده اید، مخزن را کلون کنید.
git clone https://github.com/googlecodelabs/maps-ios-swiftui.git

همچنین می‌توانید روی دکمه زیر کلیک کنید تا کد منبع را دانلود کنید.

  1. پس از دریافت کد، در یک cd ترمینال وارد فهرست starter/GoogleMapsSwiftUI شوید.
  2. برای دانلود Maps SDK برای iOS، carthage update --platform iOS را اجرا کنید
  3. در نهایت فایل GoogleMapsSwiftUI.xcodeproj را در Xcode باز کنید

4. مروری بر کد

در پروژه استارتری که دانلود کردید، کلاس های زیر برای شما تهیه و اجرا شده است:

  • AppDelegate - UIApplicationDelegate برنامه. اینجاست که Maps SDK برای iOS راه اندازی می شود.
  • City - ساختاری که نشان دهنده یک شهر است (شامل نام و مختصات شهر).
  • MapViewController - یک UIKit UIViewController کوچک شده حاوی نقشه گوگل ( GMSMapView )
    • SceneDelegate - UIWindowSceneDelegate برنامه که ContentView از آن نمونه سازی می شود.

علاوه بر این، کلاس‌های زیر پیاده‌سازی جزئی دارند و تا پایان این Codelab توسط شما تکمیل خواهند شد:

  • ContentView - نمای سطح بالای SwiftUI حاوی برنامه شما.
  • MapViewControllerBridge - کلاسی که نمای UIKit را به نمای SwiftUI پیوند می دهد. به طور خاص، این کلاسی است که MapViewController در SwiftUI قابل دسترسی می کند.

5. SwiftUI در مقابل UIKit

SwiftUI در iOS 13 به عنوان یک فریم ورک جایگزین رابط کاربری نسبت به UIKit برای توسعه برنامه های iOS معرفی شد. در مقایسه با UIKit قبلی خود، SwiftUI چندین مزیت را ارائه می دهد. برای نام بردن از چند مورد:

  • هنگام تغییر وضعیت، نمایش ها به طور خودکار به روز می شوند. با استفاده از اشیایی به نام State ، هر تغییری در مقدار اساسی موجود در آن باعث به‌روزرسانی خودکار رابط کاربری می‌شود.
  • پیش نمایش های زنده توسعه سریع تر را امکان پذیر می کند. پیش‌نمایش‌های زنده نیاز به ساخت و استقرار کد روی یک شبیه‌ساز را برای مشاهده تغییرات بصری به حداقل می‌رسانند، زیرا پیش‌نمایش نمای SwiftUI به راحتی در Xcode قابل مشاهده است.
  • منبع حقیقت در سوئیفت است. همه نماها در SwiftUI در سوئیفت اعلان می شوند، بنابراین استفاده از Interface Builder دیگر ضروری نیست.
  • با UIKit تعامل دارد. قابلیت همکاری با UIKit تضمین می کند که برنامه های موجود می توانند به صورت تدریجی از SwiftUI با نماهای موجود خود استفاده کنند. علاوه بر این، کتابخانه‌هایی که هنوز از SwiftUI پشتیبانی نمی‌کنند، مانند Maps SDK برای iOS ، همچنان می‌توانند در SwiftUI استفاده شوند.

اشکالاتی هم دارد:

  • SwiftUI فقط در iOS 13 یا جدیدتر در دسترس است.
  • سلسله مراتب نمایش را نمی توان در پیش نمایش های Xcode بررسی کرد.

وضعیت SwiftUI و جریان داده

SwiftUI یک روش جدید برای ایجاد رابط کاربری با استفاده از یک رویکرد اعلانی ارائه می‌دهد—شما به SwiftUI می‌گویید که می‌خواهید نمای شما همراه با تمام حالت‌های مختلف برای آن چگونه به نظر برسد، و سیستم بقیه کارها را انجام خواهد داد. SwiftUI هر زمان که وضعیت اصلی به دلیل یک رویداد یا اقدام کاربر تغییر کند، نمای را به روز می کند. این طرح معمولاً به جریان داده های یک طرفه گفته می شود. در حالی که ویژگی‌های این طراحی در این نرم‌افزار کد خارج از محدوده است، توصیه می‌کنیم نحوه عملکرد آن را در اسناد وضعیت و جریان داده اپل مطالعه کنید.

پل UIKit و SwiftUI با استفاده از UIViewRepresentable یا UIViewControllerRepresentable

از آنجایی که Maps SDK برای iOS بر روی UIKit ساخته شده است و نمای سازگار با SwiftUI ارائه نمی دهد، استفاده از آن در SwiftUI مستلزم انطباق با UIViewRepresentable یا UIViewControllerRepresentable است. این پروتکل‌ها SwiftUI را قادر می‌سازد که به ترتیب UIView و UIViewController ساخته‌شده توسط UIKit را شامل شود. در حالی که می‌توانید از هر یک از پروتکل‌ها برای افزودن نقشه Google به نمای SwiftUI استفاده کنید، در مرحله بعد، استفاده از UIViewControllerRepresentable را برای گنجاندن یک UIViewController حاوی نقشه بررسی خواهیم کرد.

6. یک نقشه اضافه کنید

در این بخش، نقشه های گوگل را به نمای SwiftUI اضافه می کنید.

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

کلید API خود را اضافه کنید

کلید API که در مرحله قبلی ایجاد کردید باید به Maps SDK برای iOS ارائه شود تا حساب شما را با نقشه ای که در برنامه نمایش داده می شود مرتبط کند.

برای ارائه کلید API خود، فایل AppDelegate.swift باز کرده و به روش application(_, didFinishLaunchingWithOptions) بروید. SDK با استفاده از GMSServices.provideAPIKey() با رشته "YOUR_API_KEY" مقداردهی اولیه می شود. آن رشته را با کلید API خود جایگزین کنید. با تکمیل این مرحله، Maps SDK برای iOS هنگام راه‌اندازی برنامه، مقداردهی اولیه می‌شود.

با استفاده از MapViewControllerBridge یک نقشه گوگل اضافه کنید

اکنون که کلید API شما در حال ارائه به SDK است، مرحله بعدی نمایش نقشه در برنامه است.

کنترلر View که در کد شروع ارائه شده است، MapViewController حاوی یک GMSMapView در نمای خود است. با این حال، از آنجایی که این view controller در 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 شما اکنون باید به شکل زیر باشد:

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 است. توجه داشته باشید که این ویژگی با وضعیت بسته بندی ویژگی 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 استفاده می کند.

لیست شهرها

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

با استفاده از @Binding حالت را به MapViewControllerBridge منتقل کنید

علاوه بر لیست شهرهایی که داده ها را از ویژگی 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 است. وقتی برای یک ایالت اعمال می شود، یک Binding را برمی گرداند.

  1. ادامه دهید و برنامه را اجرا کنید تا نشانگرهای نمایش داده شده روی نقشه را ببینید.

8. به یک شهر انتخاب شده متحرک شوید

در مرحله قبل، با انتقال State از یک نمای SwiftUI به نمای دیگر، نشانگرها را به نقشه اضافه کردید. در این مرحله، یک شهر یا نشانگر را پس از اینکه در لیست تعاملی قرار گرفت، متحرک خواهید کرد. برای اجرای انیمیشن، با تغییر موقعیت دوربین نقشه هنگام تغییر، به تغییرات یک وضعیت واکنش نشان خواهید داد. برای کسب اطلاعات بیشتر در مورد مفهوم دوربین نقشه، به دوربین و مشاهده مراجعه کنید.

animate-city@2x.png

متحرک کردن نقشه به شهر انتخاب شده

برای متحرک سازی نقشه به یک شهر انتخاب شده:

  1. یک Binding جدید در MapViewControllerBridge تعریف کنید

ContentView دارای یک ویژگی State به نام selectedMarker است که مقدار اولیه آن صفر می شود و هر زمان که شهری در لیست انتخاب شود به روز می شود. این کار توسط CitiesList view 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 منتقل کنید

هنگامی که MapViewControllerBridge Binding جدید را اعلام کرد، ادامه دهید و 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 ارسال می کنید. به طور خاص، شما یک نماینده را برای نمای نقشه تنظیم می‌کنید و به رویدادهای حرکت دوربین گوش می‌دهید تا زمانی که یک شهر متمرکز است و دوربین نقشه از یک حرکت حرکت می‌کند، نمای نقشه از حالت فوکوس خارج می‌شود تا بتوانید نقشه بیشتری را ببینید.

از هماهنگ کننده های SwiftUI استفاده کنید

GMSMapView رویدادهایی مانند تغییر موقعیت دوربین یا زمانی که یک نشانگر ضربه می‌خورد را منتشر می‌کند. مکانیسم گوش دادن به این رویدادها از طریق پروتکل GMSMapViewDelegate است. SwiftUI مفهوم یک Coordinator را معرفی می کند که به طور خاص برای عمل به عنوان نماینده برای کنترل کننده های view UIKit استفاده می شود. بنابراین، در دنیای SwiftUI، یک Coordinator باید مسئول انطباق با پروتکل GMSMapViewDelegate باشد. برای این کار مراحل زیر را انجام دهید:

  1. یک Coordinator به نام 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 به عنوان نماینده نمای نقشه تنظیم کنید

با ایجاد هماهنگ کننده سفارشی، گام بعدی این است که هماهنگ کننده را به عنوان نماینده برای نمای نقشه view controller تنظیم کنید. برای انجام این کار، مقداردهی اولیه view controller را در 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 خود را با استفاده از Maps SDK برای iOS بسازید.

چیزی که یاد گرفتی

  • تفاوت بین SwiftUI و UIKit
  • نحوه پل زدن بین SwiftUI و UIKit با استفاده از UIViewControllerRepresentable
  • نحوه ایجاد تغییرات در نمای نقشه با State و Binding
  • نحوه ارسال یک رویداد از نمای نقشه به SwiftUI با استفاده از یک Coordinator

بعدش چی؟

  • Maps SDK برای iOS
    • اسناد رسمی برای Maps SDK برای iOS
  • مکان‌های SDK برای iOS - کسب‌وکارهای محلی و نقاط مورد علاقه را در اطراف خود پیدا کنید
  • maps-sdk-for-ios-samples
    • نمونه کد در GitHub که تمام ویژگی های Maps SDK برای iOS را نشان می دهد.
  • SwiftUI - مستندات رسمی اپل در مورد SwiftUI
  • با پاسخ دادن به نظرسنجی زیر به ما در ایجاد محتوایی که برای شما مفیدتر است کمک کنید:

دوست دارید چه کدهای دیگری را ببینید؟

تجسم داده ها بر روی نقشه ها بیشتر در مورد سفارشی کردن سبک نقشه های من ساختمان برای تعاملات سه بعدی در نقشه ها

نمی توانید کد لبه مورد علاقه خود را پیدا کنید؟ آن را با یک شماره جدید در اینجا درخواست کنید .