הוספת מפה לאפליקציית iOS באמצעות SwiftUI ‏ (Swift)

1. לפני שתתחיל

בשיעור הזה תלמדו איך להשתמש ב-Maps SDK ל-iOS עם SwiftUI.

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

דרישות מוקדמות

  • ידע בסיסי ב-Swift
  • היכרות בסיסית עם SwiftUI

הפעולות שתבצעו:

  • אפשר להפעיל את Maps SDK for iOS ולהשתמש בו כדי להוסיף את מפות Google לאפליקציית iOS באמצעות SwiftUI.
  • מוסיפים סמנים למפה.
  • העברת מצב בין SwiftUI לבין אובייקט GMSMapView.

מה נדרש

2. להגדרה

בשלב הבא של ההפעלה, מפעילים את Maps SDK ל-iOS.

הגדרת הפלטפורמה של מפות Google

אם עדיין אין לכם חשבון ב-Google Cloud Platform ופרויקט עם חיוב מופעל, תוכלו לעיין במדריך תחילת העבודה עם הפלטפורמה של מפות Google כדי ליצור חשבון לחיוב ופרויקט.

  1. בCloud Console, לוחצים על התפריט הנפתח של הפרויקט ובוחרים את הפרויקט שבו רוצים להשתמש ב-codelab הזה.

  1. מפעילים ב-Google Cloud Marketplace את ממשקי ה-API וערכות ה-SDK של הפלטפורמה של מפות Google שנדרשים ל-codelab הזה. כדי לעשות זאת, פועלים לפי השלבים בסרטון הזה או בתיעוד הזה.
  2. יוצרים מפתח API בדף Credentials במסוף Cloud. אפשר לפעול לפי השלבים שמפורטים בסרטון הזה או בתיעוד הזה. כל הבקשות אל הפלטפורמה של מפות Google מחייבות מפתח API.

3. הורדת קוד לתחילת הדרך

כדי לעזור לכם להתחיל כמה שיותר מהר, הנה קוד התחלתי שיעזור לכם לעקוב אחרי ההוראות במעבדת התכנות הזו. אתם יכולים לדלג לפתרון, אבל אם אתם רוצים לבצע את כל השלבים בעצמכם, כדאי להמשיך לקרוא.

  1. משכפלים את המאגר אם git מותקן.
git clone https://github.com/googlecodelabs/maps-ios-swiftui.git

לחלופין, אפשר ללחוץ על הלחצן הבא כדי להוריד את קוד המקור.

  1. אחרי שמקבלים את הקוד, במסוף cd נכנסים לספרייה starter/GoogleMapsSwiftUI.
  2. מריצים את הפקודה carthage update --platform iOS כדי להוריד את Maps SDK ל-iOS
  3. לבסוף, פותחים את קובץ GoogleMapsSwiftUI.xcodeproj ב-Xcode.

4. סקירה כללית של הקוד

בפרויקט המתחיל שהורדתם, הכיתות הבאות סופקו והוטמעו עבורכם:

  • AppDelegateUIApplicationDelegate של האפליקציה. כאן יתבצע האתחול של Maps SDK ל-iOS.
  • City – מבנה שמייצג עיר (מכיל שם וקואורדינטות של העיר).
  • MapViewController – UIKit מצומצם UIViewController שמכיל מפת Google ‏ (GMSMapView)
    • SceneDelegateUIWindowSceneDelegate של האפליקציה שממנה נוצרת ContentView.

בנוסף, המחלקות הבאות כוללות הטמעות חלקיות, ואתם תצטרכו להשלים אותן עד סוף ה-Codelab הזה:

  • ContentView – התצוגה ברמה העליונה של SwiftUI שמכילה את האפליקציה.
  • MapViewControllerBridge – מחלקה שמגשרת בין תצוגת UIKit לתצוגת SwiftUI. באופן ספציפי, זהו המחלקה שתאפשר גישה ל-MapViewController ב-SwiftUI.

5. ‫SwiftUI לעומת UIKit

SwiftUI הוצגה ב-iOS 13 כחלופה ל-UIKit, שהיא מסגרת ממשק משתמש לפיתוח אפליקציות ל-iOS. ל-SwiftUI יש כמה יתרונות בהשוואה ל-UIKit, הגרסה הקודמת שלו. לדוגמה:

  • התצוגות מתעדכנות אוטומטית כשהמצב משתנה. באמצעות אובייקטים שנקראים State, כל שינוי בערך הבסיסי שהם מכילים יגרום לעדכון אוטומטי של ממשק המשתמש.
  • תצוגות מקדימות בזמן אמת מאפשרות פיתוח מהיר יותר. תצוגות מקדימות בזמן אמת מצמצמות את הצורך ליצור ולפרוס קוד לאמולטור כדי לראות שינויים חזותיים, כי אפשר לראות בקלות תצוגה מקדימה של תצוגת SwiftUI ב-Xcode.
  • מקור האמת נמצא ב-Swift. כל התצוגות ב-SwiftUI מוצהרות ב-Swift, כך שאין יותר צורך להשתמש ב-Interface Builder.
  • פועל בשילוב עם UIKit. התאימות ל-UIKit מבטיחה שאפליקציות קיימות יוכלו להשתמש ב-SwiftUI באופן הדרגתי עם התצוגות הקיימות שלהן. בנוסף, אפשר להשתמש ב-SwiftUI גם בספריות שעדיין לא תומכות ב-SwiftUI, כמו Maps SDK ל-iOS.

יש גם כמה חסרונות:

  • ‫SwiftUI זמין רק ב-iOS 13 ואילך.
  • אי אפשר לבדוק את היררכיית התצוגה בתצוגות מקדימות של Xcode.

מצב וזרימת נתונים ב-SwiftUI

‫SwiftUI מציעה דרך חדשה ליצור ממשק משתמש באמצעות גישה הצהרתית – אתם אומרים ל-SwiftUI איך אתם רוצים שהתצוגה תיראה, כולל כל המצבים השונים שלה, והמערכת תעשה את השאר. ‫SwiftUI מטפל בעדכון התצוגה בכל פעם שמתרחש שינוי במצב הבסיסי בגלל אירוע או פעולת משתמש. העיצוב הזה נקרא בדרך כלל זרימת נתונים חד-כיוונית. הפרטים הספציפיים של העיצוב הזה לא נכללים ב-codelab הזה, אבל מומלץ לקרוא את התיעוד של Apple בנושא State and Data Flow כדי להבין איך זה עובד.

גישור בין UIKit ו-SwiftUI באמצעות UIViewRepresentable או UIViewControllerRepresentable

Maps SDK for iOS מבוסס על UIKit, ולא מספק תצוגה שתואמת ל-SwiftUI. לכן, כדי להשתמש בו ב-SwiftUI, צריך להתאים אותו ל-UIViewRepresentable או ל-UIViewControllerRepresentable. הפרוטוקולים האלה מאפשרים ל-SwiftUI לכלול רכיבי UIView ו-UIViewController שנבנו באמצעות UIKit. אפשר להשתמש בכל אחד מהפרוטוקולים כדי להוסיף מפת Google לתצוגת SwiftUI, אבל בשלב הבא נראה איך להשתמש ב-UIViewControllerRepresentable כדי לכלול UIViewController שמכיל מפה.

6. הוספת מפה

בקטע הזה תוסיפו את מפות Google לתצוגת SwiftUI.

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

הוספת מפתח API

צריך לספק את מפתח ה-API שיצרתם בשלב קודם ל-Maps SDK for iOS כדי לשייך את החשבון שלכם למפה שתוצג באפליקציה.

כדי לספק את מפתח ה-API, פותחים את הקובץ  ועוברים אל ה-method‏ application(_, didFinishLaunchingWithOptions).AppDelegate.swift ה-SDK מאותחל באמצעות GMSServices.provideAPIKey() עם המחרוזת YOUR_API_KEY. מחליפים את המחרוזת במפתח ה-API. השלמת השלב הזה תפעיל את Maps SDK for iOS כשהאפליקציה תופעל.

הוספה של מפת Google באמצעות MapViewControllerBridge

עכשיו, כשמפתח ה-API מסופק ל-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 אמור להיראות כך:

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. כשמחילים אותה על State, היא מחזירה Binding.

  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 חדש, צריך לעדכן את הפונקציה MapViewControllerBridgeupdateUIViewController_, 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) תבצע רצף של אנימציות במפה באמצעות הפונקציה 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

בשלב הזה, תפעילו listener לאירועים שמופקים מ-GMSMapView ותשלחו את האירוע הזה ל-SwiftUI. בפרט, תגדירו נציג לתצוגת המפה ותאזינו לאירועים של תנועת המצלמה, כך שכאשר מתמקדים בעיר ומצלמת המפה זזה בעקבות תנועת אצבע, תצוגת המפה תבטל את המיקוד כדי שתוכלו לראות חלק גדול יותר של המפה.

שימוש ב-SwiftUI Coordinators

GMSMapView שולח אירועים כמו שינויים במיקום המצלמה או הקשה על סמן. המנגנון להאזנה לאירועים האלה הוא באמצעות פרוטוקול GMSMapViewDelegate. ב-SwiftUI מוצג הרעיון של Coordinator, שמשמש באופן ספציפי כנציג של בקרי תצוגה ב-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 כנציג של תצוגת המפה

אחרי שיוצרים את רכיב ה-Coordinator בהתאמה אישית, השלב הבא הוא להגדיר את רכיב ה-Coordinator כנציג של תצוגת המפה של בקר התצוגה. כדי לעשות זאת, צריך לעדכן את האתחול של בקר התצוגה ב-makeUIViewController(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
  • Places SDK ל-iOS – חיפוש עסקים מקומיים ונקודות עניין בסביבה שלכם
  • maps-sdk-for-ios-samples
    • קוד לדוגמה ב-GitHub שמדגים את כל התכונות ב-Maps SDK ל-iOS.
  • SwiftUI – מסמכי התיעוד הרשמיים של Apple בנושא SwiftUI
  • כדי לעזור לנו ליצור את התוכן שהכי יעזור לך, נבקש ממך למלא את הסקר הבא:

אילו codelabs נוספים היית רוצה לראות?

הצגה חזותית של נתונים במפות מידע נוסף על התאמה אישית של סגנון המפות יצירת אינטראקציות תלת-ממדיות במפות

לא מוצאים את ה-codelab שהכי מעניין אתכם? כאן אפשר לשלוח בקשה בנושא בעיה חדשה.