1. ก่อนที่คุณจะเริ่มต้น
Codelab นี้จะสอนวิธีใช้ Maps SDK สำหรับ iOS กับ SwiftUI

ข้อกำหนดเบื้องต้น
- ความรู้พื้นฐานเกี่ยวกับ Swift
- มีความคุ้นเคยกับ SwiftUI ในระดับพื้นฐาน
สิ่งที่คุณต้องดำเนินการ
- เปิดใช้และใช้ Maps SDK สำหรับ iOS เพื่อเพิ่ม Google Maps ลงในแอป iOS โดยใช้ SwiftUI
- เพิ่มเครื่องหมายลงในแผนที่
- ส่งสถานะระหว่างออบเจ็กต์ SwiftUI กับ
GMSMapView
สิ่งที่คุณต้องมี
- Xcode 11.0 ขึ้นไป
- บัญชี Google ที่เปิดใช้การเรียกเก็บเงิน
- Maps SDK สำหรับ iOS
- คาร์เธจ
2. ตั้งค่า
สำหรับขั้นตอนการเปิดใช้ต่อไปนี้ ให้เปิดใช้ Maps SDK สำหรับ iOS
ตั้งค่า Google Maps Platform
หากยังไม่มีบัญชี Google Cloud Platform และโปรเจ็กต์ที่เปิดใช้การเรียกเก็บเงิน โปรดดูคู่มือเริ่มต้นใช้งาน Google Maps Platform เพื่อสร้างบัญชีสำหรับการเรียกเก็บเงินและโปรเจ็กต์
- ใน Cloud Console ให้คลิกเมนูแบบเลื่อนลงของโปรเจ็กต์ แล้วเลือกโปรเจ็กต์ที่ต้องการใช้สำหรับ Codelab นี้

- เปิดใช้ Google Maps Platform APIs และ SDK ที่จำเป็นสำหรับ Codelab นี้ใน Google Cloud Marketplace โดยทำตามขั้นตอนในวิดีโอนี้หรือเอกสารประกอบนี้
- สร้างคีย์ API ในหน้าข้อมูลเข้าสู่ระบบของ Cloud Console คุณสามารถทำตามขั้นตอนในวิดีโอนี้หรือเอกสารประกอบนี้ คำขอทั้งหมดไปยัง Google Maps Platform ต้องใช้คีย์ API
3. ดาวน์โหลดโค้ดเริ่มต้น
เรามีโค้ดเริ่มต้นที่จะช่วยให้คุณเริ่มต้นใช้งานได้อย่างรวดเร็วที่สุด และช่วยให้คุณทำตาม Codelab นี้ได้ คุณสามารถข้ามไปยังโซลูชันได้ แต่หากต้องการทำตามขั้นตอนทั้งหมดเพื่อสร้างโซลูชันด้วยตนเอง โปรดอ่านต่อ
- โคลนที่เก็บหากคุณติดตั้ง
gitไว้
git clone https://github.com/googlecodelabs/maps-ios-swiftui.git
หรือจะคลิกปุ่มต่อไปนี้เพื่อดาวน์โหลดซอร์สโค้ดก็ได้
- เมื่อได้รับรหัสแล้ว ให้ไปที่ไดเรกทอรี
starter/GoogleMapsSwiftUIในเทอร์มินัลcd - เรียกใช้
carthage update --platform iOSเพื่อดาวน์โหลด Maps SDK สำหรับ iOS - สุดท้าย ให้เปิดไฟล์
GoogleMapsSwiftUI.xcodeprojใน Xcode
4. ภาพรวมของโค้ด
ในโปรเจ็กต์เริ่มต้นที่คุณดาวน์โหลดมา เราได้จัดเตรียมและใช้งานคลาสต่อไปนี้ให้คุณแล้ว
AppDelegate-UIApplicationDelegateของแอปพลิเคชัน ส่วนนี้คือที่ที่จะเริ่มต้น Maps SDK สำหรับ iOSCity- โครงสร้างที่แสดงถึงเมือง (มีชื่อและพิกัดของเมือง)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 จึงไม่จำเป็นต้องใช้ Interface Builder อีกต่อไป
- ทำงานร่วมกับ UIKit การทำงานร่วมกันกับ UIKit ช่วยให้มั่นใจได้ว่าแอปที่มีอยู่จะใช้ SwiftUI กับมุมมองที่มีอยู่ได้ทีละน้อย นอกจากนี้ คุณยังใช้ไลบรารีที่ยังไม่รองรับ SwiftUI เช่น Maps SDK สำหรับ iOS ใน SwiftUI ได้
แต่ก็มีข้อเสียบางประการเช่นกัน
- SwiftUI ใช้ได้ใน iOS 13 ขึ้นไปเท่านั้น
- ตรวจสอบลำดับชั้นของมุมมองในตัวอย่าง Xcode ไม่ได้
สถานะและการไหลของข้อมูลใน SwiftUI
SwiftUI มีวิธีใหม่ในการสร้าง UI โดยใช้แนวทางแบบประกาศ นั่นคือคุณบอก SwiftUI ว่าต้องการให้มุมมองมีลักษณะอย่างไรพร้อมกับสถานะต่างๆ ทั้งหมดของมุมมองนั้น แล้วระบบจะจัดการส่วนที่เหลือให้ SwiftUI จะจัดการการอัปเดตมุมมองทุกครั้งที่สถานะพื้นฐานเปลี่ยนแปลงเนื่องจากเหตุการณ์หรือการกระทําของผู้ใช้ การออกแบบนี้มักเรียกว่าการไหลของข้อมูลแบบทิศทางเดียว แม้ว่ารายละเอียดของการออกแบบนี้จะอยู่นอกขอบเขตของโค้ดแล็บนี้ แต่เราขอแนะนำให้อ่านวิธีการทำงานนี้ในเอกสารประกอบของ Apple เกี่ยวกับการไหลของสถานะและข้อมูล
เชื่อมต่อ UIKit และ SwiftUI โดยใช้ UIViewRepresentable หรือ UIViewControllerRepresentable
เนื่องจาก Maps SDK สำหรับ iOS สร้างขึ้นบน UIKit และไม่ได้มีมุมมองที่เข้ากันได้กับ SwiftUI การใช้ใน SwiftUI จึงต้องเป็นไปตาม UIViewRepresentable หรือ UIViewControllerRepresentable โปรโตคอลเหล่านี้ช่วยให้ SwiftUI สามารถรวม UIView และ UIViewController ที่สร้างด้วย UIKit ได้ตามลำดับ แม้ว่าคุณจะใช้โปรโตคอลใดก็ได้เพื่อเพิ่ม Google Maps ลงในมุมมอง SwiftUI แต่ในขั้นตอนถัดไป เราจะมาดูการใช้ UIViewControllerRepresentable เพื่อรวม UIViewController ที่มีแผนที่
6. เพิ่มแผนที่
ในส่วนนี้ คุณจะเพิ่ม Google Maps ลงในมุมมอง SwiftUI

เพิ่มคีย์ API
คุณต้องระบุคีย์ API ที่สร้างไว้ในขั้นตอนก่อนหน้าให้กับ Maps SDK สำหรับ iOS เพื่อเชื่อมโยงบัญชีกับแผนที่ที่จะแสดงในแอป
หากต้องการระบุคีย์ API ให้เปิดไฟล์ AppDelegate.swift แล้วไปที่เมธอด application(_, didFinishLaunchingWithOptions) ระบบจะเริ่มต้น SDK โดยใช้ GMSServices.provideAPIKey() ที่มีสตริง "YOUR_API_KEY" แทนที่สตริงดังกล่าวด้วยคีย์ API ของคุณ การทำขั้นตอนนี้ให้เสร็จจะเริ่มต้น Maps SDK สำหรับ iOS เมื่อแอปพลิเคชันเปิดตัว
เพิ่มแผนที่ของ Google โดยใช้ MapViewControllerBridge
ตอนนี้ SDK ได้รับคีย์ API แล้ว ขั้นตอนถัดไปคือการแสดงแผนที่ในแอป
ตัวควบคุมมุมมองที่ระบุไว้ในโค้ดเริ่มต้น MapViewController มี GMSMapView ในมุมมอง อย่างไรก็ตาม เนื่องจากตัวควบคุมมุมมองนี้สร้างขึ้นใน UIKit คุณจึงต้องเชื่อมต่อคลาสนี้กับ SwiftUI เพื่อให้ใช้ภายใน ContentView ได้ โดยทำดังนี้
- เปิดไฟล์
MapViewControllerBridgeใน Xcode
คลาสนี้เป็นไปตาม UIViewControllerRepresentable ซึ่งเป็นโปรโตคอลที่จำเป็นในการห่อหุ้ม UIKit UIViewController เพื่อให้ใช้เป็นมุมมอง SwiftUI ได้ กล่าวคือ การปฏิบัติตามโปรโตคอลนี้จะช่วยให้การเชื่อมโยงมุมมอง UIKit กับมุมมอง SwiftUI เป็นไปได้ง่ายขึ้น การปฏิบัติตามโปรโตคอลนี้ต้องใช้ 2 วิธีต่อไปนี้
makeUIViewController(context)- SwiftUI เรียกใช้เมธอดนี้เพื่อสร้างUIViewControllerที่อยู่เบื้องหลัง คุณจะสร้างอินสแตนซ์ของUIViewControllerและส่งสถานะเริ่มต้นได้ที่นี่updateUIViewController(_, context)- SwiftUI จะเรียกใช้เมธอดนี้ทุกครั้งที่สถานะเปลี่ยนแปลง คุณจะทำการแก้ไขใดๆ กับUIViewControllerพื้นฐานเพื่อตอบสนองต่อการเปลี่ยนแปลงสถานะได้ที่นี่
- สร้าง
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 เพื่อแสดงแผนที่
- เปิดไฟล์
ContentViewใน Xcode
ContentView จะได้รับการเริ่มต้นใน SceneDelegate และมีมุมมองแอปพลิเคชันระดับบนสุด ระบบจะเพิ่มแผนที่จากภายในไฟล์นี้
- สร้าง
MapViewControllerBridgeภายในพร็อพเพอร์ตี้body
ในพร็อพเพอร์ตี้ body ของไฟล์นี้ เราได้ระบุและใช้ ZStack ให้คุณแล้ว ZStack มีรายการเมืองที่โต้ตอบและลากได้ ซึ่งคุณจะใช้ในขั้นตอนถัดไป ตอนนี้ ให้สร้าง ZStack เป็นมุมมองย่อยแรกของ ZStack เพื่อให้แผนที่แสดงในแอปที่อยู่ด้านหลังมุมมองรายการเมืองMapViewControllerBridge เมื่อดำเนินการดังกล่าว เนื้อหาของพร็อพเพอร์ตี้ 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()
} // ...
}
}
}
- ตอนนี้ให้เรียกใช้แอป คุณควรเห็นแผนที่โหลดบนหน้าจอของอุปกรณ์พร้อมกับรายการเมืองที่ลากได้ที่ด้านล่างของหน้าจอ
7. เพิ่มเครื่องหมายลงในแผนที่
ในขั้นตอนก่อนหน้า คุณได้เพิ่มแผนที่ข้างรายการที่โต้ตอบได้ซึ่งแสดงรายชื่อเมือง ในส่วนนี้ คุณจะเพิ่มเครื่องหมายสำหรับแต่ละเมืองในรายการนั้น

เครื่องหมายเป็นสถานะ
ContentView ประกาศพร็อพเพอร์ตี้ชื่อ markers ซึ่งเป็นรายการของ GMSMarker ที่แสดงถึงแต่ละเมืองที่ประกาศในพร็อพเพอร์ตี้แบบคงที่ cities โปรดสังเกตว่าพร็อพเพอร์ตี้นี้มีคำอธิบายประกอบด้วยตัวห่อหุ้มพร็อพเพอร์ตี้ SwiftUI State เพื่อระบุว่าควรให้ 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 เพื่อให้ใช้แสดงเครื่องหมายเหล่านั้นในแผนที่ได้ โดยสิ่งที่คุณต้องทำมีดังนี้
- ประกาศพร็อพเพอร์ตี้
markersใหม่ภายในMapViewControllerBridgeที่มีคำอธิบายประกอบด้วย@Binding
MapViewControllerBridge
struct MapViewControllerBridge: : UIViewControllerRepresentable {
@Binding var markers: [GMSMarker]
// ...
}
- ใน
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 }
}
}
- ส่งพร็อพเพอร์ตี้
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
- เรียกใช้แอปเพื่อดูเครื่องหมายที่แสดงบนแผนที่
8. เคลื่อนไหวไปยังเมืองที่เลือก
ในขั้นตอนก่อนหน้า คุณได้เพิ่มเครื่องหมายลงในแผนที่โดยส่งต่อสถานะจากมุมมอง SwiftUI หนึ่งไปยังอีกมุมมองหนึ่ง ในขั้นตอนนี้ คุณจะเคลื่อนไหวไปยังเมืองหรือเครื่องหมายหลังจากที่แตะในรายการที่โต้ตอบได้ หากต้องการสร้างภาพเคลื่อนไหว คุณจะต้องตอบสนองต่อการเปลี่ยนแปลงสถานะโดยการแก้ไขตำแหน่งกล้องของแผนที่เมื่อเกิดการเปลี่ยนแปลง ดูข้อมูลเพิ่มเติมเกี่ยวกับแนวคิดของกล้องในแผนที่ได้ที่กล้องและมุมมอง

เคลื่อนไหวแผนที่ไปยังเมืองที่เลือก
วิธีเคลื่อนไหวแผนที่ไปยังเมืองที่เลือก
- กำหนด Binding ใหม่ใน
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?
}
- อัปเดต
MapViewControllerBridgeเพื่อเคลื่อนไหวแผนที่ทุกครั้งที่selectedMarkerเปลี่ยนแปลง
เมื่อประกาศการเชื่อมโยงใหม่แล้ว คุณต้องอัปเดตฟังก์ชัน 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
- ส่ง
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 ช่วยลดความซับซ้อนของกระบวนการเคลื่อนไหวมุมมอง เนื่องจากจะจัดการการเคลื่อนไหวสำหรับการเปลี่ยนสถานะ คุณจะเพิ่มภาพเคลื่อนไหวมากขึ้นโดยโฟกัสมุมมองไปยังเมืองที่เลือกหลังจากภาพเคลื่อนไหวของแผนที่เสร็จสมบูรณ์ หากต้องการดำเนินการนี้ ให้ทำตามขั้นตอนต่อไปนี้
- เพิ่ม
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()
})
})
}
}
}
}
}
- ใช้
onAnimationEndedในMapViewControllerBridge
ใช้onAnimationEnded Closure ที่มีการสร้างอินสแตนซ์ 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))
}
}
}
}
- เรียกใช้แอปเพื่อดูภาพเคลื่อนไหวได้เลย
9. ส่งเหตุการณ์ไปยัง SwiftUI
ในขั้นตอนนี้ คุณจะได้ฟังเหตุการณ์ที่ปล่อยออกมาจาก GMSMapView และส่งเหตุการณ์นั้นไปยัง SwiftUI โดยคุณจะตั้งค่าผู้มอบสิทธิ์ให้กับมุมมองแผนที่และรับฟังเหตุการณ์การย้ายกล้องเพื่อให้เมื่อโฟกัสเมืองและกล้องแผนที่ย้ายจากการสัมผัส มุมมองแผนที่จะเลิกโฟกัสเพื่อให้คุณเห็นแผนที่มากขึ้น
ใช้ตัวประสาน SwiftUI
GMSMapView จะปล่อยเหตุการณ์ต่างๆ เช่น การเปลี่ยนแปลงตำแหน่งกล้องหรือเมื่อมีการแตะเครื่องหมาย กลไกการฟังเหตุการณ์เหล่านี้คือผ่านโปรโตคอล GMSMapViewDelegate SwiftUI นำเสนอแนวคิดของ Coordinator ซึ่งใช้เพื่อทำหน้าที่เป็นตัวแทนสำหรับตัวควบคุมมุมมอง UIKit โดยเฉพาะ ดังนั้นในโลกของ SwiftUI ตัวประสานควรมีหน้าที่รับผิดชอบในการปฏิบัติตามโปรโตคอล GMSMapViewDelegate โดยทำตามขั้นตอนต่อไปนี้
- สร้างผู้ประสานงานชื่อ
MapViewCoordinatorในMapViewControllerBridge
สร้างคลาสที่ซ้อนกันภายในคลาส MapViewControllerBridge แล้วตั้งชื่อว่า MapViewCoordinator คลาสนี้ควรเป็นไปตาม GMSMapViewDelegate และควรประกาศ MapViewControllerBridge เป็นพร็อพเพอร์ตี้
MapViewControllerBridge
struct MapViewControllerBridge: UIViewControllerRepresentable {
// ...
final class MapViewCoordinator: NSObject, GMSMapViewDelegate {
var mapViewControllerBridge: MapViewControllerBridge
init(_ mapViewControllerBridge: MapViewControllerBridge) {
self.mapViewControllerBridge = mapViewControllerBridge
}
}
}
- ใช้
makeCoordinator()ในMapViewControllerBridge
จากนั้นใช้เมธอด makeCoordinator() ภายใน MapViewControllerBridge และส่งคืนอินสแตนซ์ของ MapViewCoodinator ที่คุณสร้างในขั้นตอนก่อนหน้า
MapViewControllerBridge
struct MapViewControllerBridge: UIViewControllerRepresentable {
// ...
func makeCoordinator() -> MapViewCoordinator {
return MapViewCoordinator(self)
}
}
- ตั้งค่า
MapViewCoordinatorเป็นผู้มอบสิทธิ์ของมุมมองแผนที่
เมื่อสร้างโคออร์ดิเนเตอร์ที่กำหนดเองแล้ว ขั้นตอนถัดไปคือการตั้งค่าโคออร์ดิเนเตอร์เป็นผู้มอบสิทธิ์สำหรับมุมมองแผนที่ของตัวควบคุมมุมมอง โดยอัปเดตการเริ่มต้นตัวควบคุมมุมมองใน makeUIViewController(context) คุณจะเข้าถึงโคออร์ดิเนเตอร์ที่สร้างขึ้นจากขั้นตอนก่อนหน้าได้จากออบเจ็กต์ Context
MapViewControllerBridge
struct MapViewControllerBridge: UIViewControllerRepresentable {
// ...
func makeUIViewController(context: Context) -> MapViewController {
let uiViewController = MapViewController()
uiViewController.map.delegate = context.coordinator
return uiViewController
}
}
- เพิ่มการปิดไปยัง
MapViewControllerBridgeเพื่อให้กล้องจะย้ายเหตุการณ์สามารถเผยแพร่ขึ้นไปได้
เนื่องจากเป้าหมายคือการอัปเดตมุมมองเมื่อกล้องเคลื่อนที่ ให้ประกาศพร็อพเพอร์ตี้การปิดใหม่ที่ยอมรับบูลีนภายใน MapViewControllerBridge ที่เรียกว่า mapViewWillMove และเรียกใช้การปิดนี้ในเมธอดตัวแทน mapView(_, willMove) ภายใน MapViewCoordinator ส่งค่าของ gesture ไปยัง Closure เพื่อให้มุมมอง SwiftUI ตอบสนองต่อเหตุการณ์การเคลื่อนไหวของกล้องที่เกี่ยวข้องกับท่าทางสัมผัสเท่านั้น
MapViewControllerBridge
struct MapViewControllerBridge: UIViewControllerRepresentable {
var mapViewWillMove: (Bool) -> ()
//...
final class MapViewCoordinator: NSObject, GMSMapViewDelegate {
// ...
func mapView(_ mapView: GMSMapView, willMove gesture: Bool) {
self.mapViewControllerBridge.mapViewWillMove(gesture)
}
}
}
- อัปเดต 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
})
// ...
}
}
}
}
- เรียกใช้แอปเพื่อดูการเปลี่ยนแปลงใหม่กันเลย
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
- ช่วยเราสร้างเนื้อหาที่เป็นประโยชน์ต่อคุณมากที่สุดโดยตอบแบบสำรวจต่อไปนี้
คุณอยากเห็น Codelab อื่นๆ แบบไหน
หากไม่พบโค้ดแล็บที่คุณสนใจมากที่สุด ขอได้โดยแจ้งปัญหาใหม่ที่นี่