เพิ่มแผนที่ในแอป iOS ด้วย SwiftUI (Swift)

1. ก่อนที่คุณจะเริ่มต้น

Codelab นี้จะสอนวิธีใช้ Maps SDK สําหรับ iOS กับ SwiftUI

ภาพหน้าจอ-iphone-12-black@2x.png

สิ่งที่ต้องมีก่อน

  • ข้อมูลเบื้องต้นเกี่ยวกับ Swift
  • ทําความคุ้นเคยกับ SwiftUI

สิ่งที่คุณจะทํา

  • เปิดใช้และใช้ Maps SDK สําหรับ iOS เพื่อเพิ่ม Google Maps ไปยังแอป iOS โดยใช้ SwiftUI
  • เพิ่มเครื่องหมายลงในแผนที่
  • ส่งสถานะจากมุมมอง SwiftUI ไปยังออบเจ็กต์ GMSMapView และในทางกลับกัน

สิ่งที่ต้องมี

2. ตั้งค่า

สําหรับขั้นตอนการเปิดใช้ต่อไปนี้ ให้เปิดใช้ Maps SDK สําหรับ iOS

ตั้งค่า Google Maps Platform

หากยังไม่มีบัญชี Google Cloud Platform และโปรเจ็กต์ที่เปิดใช้การเรียกเก็บเงิน โปรดดูคู่มือการเริ่มต้นใช้งาน Google Maps Platform เพื่อสร้างบัญชีสําหรับการเรียกเก็บเงินและโปรเจ็กต์

  1. ใน Cloud Console ให้คลิกเมนูแบบเลื่อนลงของโปรเจ็กต์ แล้วเลือกโปรเจ็กต์ที่ต้องการใช้สําหรับ Codelab นี้

  1. เปิดใช้ Google Maps Platform API และ SDK ที่จําเป็นสําหรับ Codelab นี้ใน Google Cloud Marketplace โดยทําตามขั้นตอนในวิดีโอนี้หรือเอกสารนี้
  2. สร้างคีย์ API ในหน้าข้อมูลเข้าสู่ระบบของ Cloud Console คุณสามารถทําตามขั้นตอนในวิดีโอนี้หรือเอกสารนี้ คําขอทั้งหมดสําหรับ Google Maps Platform ต้องใช้คีย์ API

3. ดาวน์โหลดโค้ดเริ่มต้น

โค้ดเริ่มต้นเพื่อช่วยให้คุณเริ่มต้นทําสิ่งต่อไปนี้ได้พร้อมด้วย Codelab เพื่อช่วยให้คุณเริ่มต้นใช้งานได้เร็วที่สุด คุณยินดีข้ามไปที่โซลูชันนี้ แต่ถ้าต้องการทําตามขั้นตอนทั้งหมดในการสร้างด้วยตนเอง ให้อ่านต่อไป

  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. ภาพรวมโค้ด

ในโปรเจ็กต์เริ่มต้นที่คุณดาวน์โหลด เราได้จัดเตรียมและใช้งานคลาสต่อไปนี้ให้คุณแล้ว

  • AppDelegate - แอปพลิเคชันของ UIApplicationDelegate นี่คือที่ที่ Maps SDK สําหรับ iOS จะเริ่มต้น
  • City - โครงสร้างที่แสดงถึงเมือง (มีชื่อและพิกัดของเมือง)
  • MapViewController - UIKit แบบง่าย UIViewController ที่มี Google Maps (GMSMAPView)
  • SceneDelegate - แอปพลิเคชัน UIWindowSceneDelegate ที่มีการสร้างContentView

นอกจากนี้ คลาสต่อไปนี้มีการใช้งานบางส่วนและเราจะทําให้เสร็จภายใน Codelab นี้

  • ContentView - มุมมอง SwiftUI ระดับบนสุดที่มีแอปของคุณ
  • MapViewControllerBridge - คลาสที่เชื่อมมุมมอง UIKit ไปยังมุมมอง SwiftUI ซึ่งก็คือคลาสที่จะทําให้ MapViewController เข้าถึงได้ใน SwiftUI

5. การใช้ SwiftUI กับ UIKit

SwiftUI เปิดตัวใน iOS 13 เป็นเฟรมเวิร์ก UI ทางเลือกเหนือ UIKit สําหรับการพัฒนาแอปพลิเคชัน iOS SwiftUI มีข้อดีมากมายเมื่อเทียบกับ UIKit รุ่นก่อนหน้า ข้อควรทราบมีดังนี้

  • มุมมองจะอัปเดตโดยอัตโนมัติเมื่อสถานะมีการเปลี่ยนแปลง การใช้ออบเจ็กต์ที่ชื่อว่า State การเปลี่ยนแปลงใดๆ กับค่าที่สําคัญจะทําให้ UI อัปเดตโดยอัตโนมัติ
  • ตัวอย่างแบบใช้งานจริงช่วยให้พัฒนาได้เร็วขึ้น การแสดงตัวอย่างที่ใช้จริงจะช่วยลดความจําเป็นในการสร้างและใช้โค้ดในโปรแกรมจําลองเพื่อให้เห็นการเปลี่ยนแปลงของภาพ ในขณะที่แสดงตัวอย่างของ SwiftUI จะสามารถดูได้ใน Xcode
  • แหล่งที่มาของความจริงอยู่ใน Swift ระบบจะประกาศมุมมองทั้งหมดใน SwiftUI ใน Swift ดังนั้นการใช้เครื่องมือสร้างอินเทอร์เฟซจึงไม่จําเป็นอีกต่อไป
  • ทํางานร่วมกับ UIKit ความสามารถในการทํางานร่วมกับ UIKit ช่วยให้แอปที่มีอยู่สามารถใช้ SwiftUI กับมุมมองที่มีอยู่ได้เพิ่มขึ้น นอกจากนี้ ไลบรารีที่ยังไม่รองรับ SwiftUI เช่น Maps SDK สําหรับ iOS ก็จะยังใช้ใน SwiftUI ได้

แต่ก็มีข้อเสียบางประการเช่นกัน

  • SwiftUI ใช้ได้เฉพาะใน iOS 13 ขึ้นไปเท่านั้น
  • ตรวจสอบลําดับชั้นของมุมมองในตัวอย่าง Xcode ไม่ได้

สถานะ SwiftUI และการถ่ายโอนข้อมูล

SwiftUI มีวิธีแปลกใหม่ในการสร้าง UI โดยใช้แนวทางที่เป็นการประกาศ กล่าวคือคุณจะบอก SwiftUI ว่าคุณต้องการให้มุมมองของคุณเป็นอย่างไร พร้อมกับสถานะต่างๆ ทั้งหมด และระบบจะจัดการส่วนที่เหลือให้ SwiftUI จัดการการอัปเดตมุมมองเมื่อใดก็ตามที่สถานะสําคัญมีการเปลี่ยนแปลงเนื่องจากเหตุการณ์หรือการดําเนินการของผู้ใช้ การออกแบบนี้โดยทั่วไปจะเรียกว่าการส่งข้อมูลแบบทิศทางเดียว แม้ว่าการออกแบบเฉพาะนี้จะอยู่นอกเหนือขอบเขตใน Codelab นี้ แต่เราขอแนะนําให้อ่านวิธีการทํางานของเอกสารประกอบใน State และ Data Flow ของ Apple

การเชื่อมโยง UIKit และ SwiftUI โดยใช้ UIViewAgentable หรือ UIViewControllerAgentable

เนื่องจาก Maps SDK สําหรับ iOS สร้างขึ้นจาก UIKit และยังไม่มีมุมมองที่ใช้งานร่วมกับ SwiftUI ได้ การใช้ใน SwiftUI จึงต้องเป็นไปตาม UIViewRepresentable หรือ UIViewControllerRepresentable โปรโตคอลเหล่านี้ช่วยให้ SwiftUI รวม UIView และ UIViewController ที่สร้างขึ้นจาก UIKit ตามลําดับได้ แม้ว่าคุณจะใช้โปรโตคอลใดก็ได้เพื่อเพิ่ม Google Maps ลงในมุมมอง SwiftUI แต่ในขั้นตอนถัดไป เราจะดูโดยใช้ UIViewControllerRepresentable เพื่อรวม UIViewController ที่มีแผนที่

6. เพิ่มแผนที่

ในส่วนนี้ คุณจะได้เพิ่ม Google Maps ลงในมุมมอง SwiftUI

เพิ่ม-แผนที่-ภาพหน้าจอ@2x.png

เพิ่มคีย์ API

คุณต้องใช้คีย์ API ที่คุณสร้างในขั้นตอนก่อนหน้าไปยัง Maps SDK สําหรับ iOS เพื่อเชื่อมโยงบัญชีกับแผนที่ที่จะแสดงในแอป

หากต้องการระบุคีย์ API ให้เปิดไฟล์ AppDelegate.swift แล้วไปยังเมธอด application(_, didFinishLaunchingWithOptions) ปัจจุบัน SDK เริ่มต้นผ่าน GMSServices.provideAPIKey() ด้วยสตริง "YOUR_API_KEY" แทนที่สตริงดังกล่าวด้วยคีย์ API การทําตามขั้นตอนนี้จะเริ่มต้น Maps SDK สําหรับ iOS เมื่อแอปพลิเคชันเริ่มทํางาน

เพิ่ม Google Maps โดยใช้ MapViewControllerBridge

เมื่อมีการให้คีย์ API แก่ SDK แล้ว ขั้นตอนถัดไปคือการแสดงแผนที่ในแอป

ตัวควบคุมการดูที่ระบุในโค้ดเริ่มต้น MapViewController ปัจจุบันมี GMSMapView ในข้อมูลพร็อพเพอร์ตี้ อย่างไรก็ตาม เนื่องจากตัวควบคุมมุมมองนี้สร้างขึ้นใน UIKit คุณจะต้องเชื่อมโยงคลาสนี้เข้ากับ SwiftUI เพื่อให้ใช้งานได้ภายใน ContentView วิธีการมีดังนี้

  1. เปิดไฟล์ MapViewControllerBridge ใน Xcode

คลาสนี้สอดคล้องกับ UIViewControllerControllerable ซึ่งเป็นโปรโตคอลที่จําเป็นสําหรับการรวม UIKit UIViewController เพื่อให้ใช้เป็นมุมมอง SwiftUI ได้ กล่าวคือ การปฏิบัติตามโปรโตคอลนี้ช่วยให้คุณเชื่อมโยงมุมมอง UIKit กับมุมมอง SwiftUI ได้ การปฏิบัติตามโปรโตคอลนี้จําเป็นต้องใช้ 2 วิธี ดังนี้

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

ใช้ MapViewControllerBridge ใน 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-Marks@2x.png

ตัวทําเครื่องหมายเป็นสถานะ

ขณะนี้ ContentView ประกาศพร็อพเพอร์ตี้ชื่อ markers ซึ่งเป็นรายการ GMSMarker ที่แสดงถึงแต่ละเมืองที่ประกาศในพร็อพเพอร์ตี้แบบคงที่ของ cities โปรดทราบว่าพร็อพเพอร์ตี้นี้จะมีคําอธิบายประกอบด้วย State Wrapper ของ SwiftUI เพื่อระบุว่าควรจัดการโดย 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)
      }
    }
  }
}

สถานะไปยัง MapsViewControllerBridge ผ่านการเชื่อมโยง

นอกเหนือจากรายการเมืองที่แสดงข้อมูลจากพร็อพเพอร์ตี้ markers แล้ว ให้ส่งพร็อพเพอร์ตี้นี้ไปยังโครงสร้าง MapViewControllerBridge เพื่อให้ใช้แสดงเครื่องหมายเหล่านั้นบนแผนที่ได้ โดยดำเนินการดังนี้

  1. ประกาศพร็อพเพอร์ตี้ markers ใหม่ภายใน MapViewControllerBridge ที่มีคําอธิบายประกอบ @Binding

ตัวเชื่อม MapViewController

struct MapViewControllerBridge: : UIViewControllerRepresentable {
  @Binding var markers: [GMSMarker]
  // ...
}
  1. ใน MapViewControllerBridge ให้อัปเดตวิธี updateUIViewController(_, context) เพื่อใช้พร็อพเพอร์ตี้ markers

ดังที่ได้อธิบายไว้ในขั้นตอนก่อนหน้า SwiftUI จะเรียกใช้ updateUIViewController(_, context) ทุกครั้งที่มีการเปลี่ยนแปลงสถานะ ภายในเมธอดที่เราต้องการอัปเดตแผนที่ ดังนั้นให้แสดงเครื่องหมายใน 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 เนื่องจากต้องการพร็อพเพอร์ตี้ที่เชื่อมโยง $ เป็นคํานําหน้าที่สงวนไว้เพื่อใช้กับ Wrapper ของพร็อพเพอร์ตี้ 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 มีการเปลี่ยนแปลง

เมื่อประกาศการเชื่อมโยงใหม่แล้ว คุณต้องอัปเดตฟังก์ชัน 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&#39

  1. ผ่าน selectedMarker ของ ContentView ไปยัง MapViewControllerBridge

เมื่อ MapViewControllerBridge ประกาศ Binding ใหม่แล้ว ให้อัปเดตและอัปเดต 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 นําเสนอแนวคิดของ Coordinator ซึ่งใช้เป็นบทบาทผู้รับมอบสิทธิ์สําหรับตัวควบคุมมุมมอง 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 เป็นผู้รับมอบสิทธิ์มุมมองแผนที่

เมื่อสร้างผู้ประสานงานที่กําหนดเอง ขั้นตอนต่อไปคือการตั้งค่าผู้ประสานงานเป็นผู้รับมอบสิทธิ์สําหรับมุมมองแผนที่ "#39" หากต้องการอัปเดต ให้อัปเดตการเริ่มต้นมุมมองตัวควบคุมใน 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 ได้โดยใช้ Maps SDK สําหรับ iOS

สิ่งที่คุณได้เรียนรู้

มีอะไรอีกบ้าง

  • Maps SDK สําหรับ iOS - เอกสารอย่างเป็นทางการสําหรับ Maps SDK สําหรับ iOS
  • Places สําหรับ iOS - ค้นหาธุรกิจท้องถิ่นและจุดสนใจรอบตัวคุณ
  • maps-sdk-for-ios-samples - โค้ดตัวอย่างใน GitHub ที่แสดงฟีเจอร์ทั้งหมดภายใน Maps SDK สําหรับ iOS
  • SwiftUI - เอกสารอย่างเป็นทางการของ Apple' ใน SwiftUI
  • ช่วยเราสร้างเนื้อหาที่คุณคิดว่าเป็นประโยชน์ที่สุดโดยตอบคําถามต่อไปนี้

คุณต้องการเห็น Codelab อื่นใดบ้าง

การแสดงภาพข้อมูลบนแผนที่ ข้อมูลเพิ่มเติมเกี่ยวกับการปรับแต่งรูปแบบของแผนที่ การสร้างการโต้ตอบแบบ 3 มิติในแผนที่

Codelab ที่คุณต้องการไม่อยู่ในรายการข้างต้นใช่ไหม ส่งคําขอเกี่ยวกับปัญหาใหม่ที่นี่