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
เปลี่ยนแปลง
เมื่อประกาศการเชื่อมโยงใหม่แล้ว คุณต้องอัปเดตฟังก์ชัน MapViewControllerBridge
updateUIViewController_, 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 อื่นๆ แบบไหน
หากไม่พบโค้ดแล็บที่คุณสนใจมากที่สุด ขอได้โดยแจ้งปัญหาใหม่ที่นี่