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 API และ 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
หรือจะคลิกปุ่มต่อไปนี้เพื่อดาวน์โหลดซอร์สโค้ดก็ได้
- เมื่อได้รับรหัส ในเทอร์มินัล
cd
เข้าสู่ไดเร็กstarter/GoogleMapsSwiftUI
- เรียกใช้
carthage update --platform iOS
เพื่อดาวน์โหลด Maps SDK สําหรับ iOS - สุดท้าย เปิดไฟล์
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
เพิ่มคีย์ 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
วิธีการมีดังนี้
- เปิดไฟล์
MapViewControllerBridge
ใน Xcode
คลาสนี้สอดคล้องกับ UIViewControllerControllerable ซึ่งเป็นโปรโตคอลที่จําเป็นสําหรับการรวม UIKit UIViewController
เพื่อให้ใช้เป็นมุมมอง SwiftUI ได้ กล่าวคือ การปฏิบัติตามโปรโตคอลนี้ช่วยให้คุณเชื่อมโยงมุมมอง UIKit กับมุมมอง SwiftUI ได้ การปฏิบัติตามโปรโตคอลนี้จําเป็นต้องใช้ 2 วิธี ดังนี้
makeUIViewController(context)
- เมธอดนี้จะเรียกใช้โดย SwiftUI เพื่อสร้างUIViewController
ที่เกี่ยวข้อง นี่คือที่ที่คุณจะสร้างอินสแตนซ์UIViewController
และส่งสถานะเริ่มต้นupdateUIViewController(_, context)
- เมธอดนี้จะเรียกใช้โดย SwiftUI เมื่อใดก็ตามที่มีการเปลี่ยนแปลงสถานะ ซึ่งเป็นที่ที่คุณจะทําการแก้ไขUIViewController
ที่เกี่ยวข้องเพื่อตอบสนองต่อการเปลี่ยนแปลงสถานะ
- สร้าง
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
เพื่อแสดงแผนที่
- เปิดไฟล์
ContentView
ใน Xcode
มีการสร้างอินสแตนซ์ ContentView
ใน SceneDelegate
และมีมุมมองแอปพลิเคชันระดับบนสุด แผนที่จะถูกเพิ่มจากภายในไฟล์นี้
- สร้าง
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()
} // ...
}
}
}
- ทีนี้แอปก็ทํางานได้เลย คุณจะเห็นการโหลดแผนที่บนหน้าจอของอุปกรณ์ รวมถึงรายชื่อเมืองที่ลากได้ไปยังด้านล่างของหน้าจอ
7. เพิ่มเครื่องหมายลงในแผนที่
ในขั้นตอนก่อนหน้า คุณเพิ่มแผนที่ควบคู่ไปกับรายการแบบอินเทอร์แอกทีฟที่แสดงรายการเมือง ในส่วนนี้ คุณจะได้เพิ่มเครื่องหมายสําหรับแต่ละเมืองในรายการ
ตัวทําเครื่องหมายเป็นสถานะ
ขณะนี้ 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
เพื่อให้ใช้แสดงเครื่องหมายเหล่านั้นบนแผนที่ได้ โดยดำเนินการดังนี้
- ประกาศพร็อพเพอร์ตี้
markers
ใหม่ภายในMapViewControllerBridge
ที่มีคําอธิบายประกอบ@Binding
ตัวเชื่อม MapViewController
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
เนื่องจากต้องการพร็อพเพอร์ตี้ที่เชื่อมโยง $
เป็นคํานําหน้าที่สงวนไว้เพื่อใช้กับ Wrapper ของพร็อพเพอร์ตี้ Swift เมื่อใช้กับรัฐ คุณจะเห็น การเชื่อมโยง
- ดําเนินการต่อและเรียกใช้แอปเพื่อดูเครื่องหมายบนแผนที่
8. เคลื่อนไหวไปยังเมืองที่เลือก
ในขั้นตอนก่อนหน้า คุณได้เพิ่มเครื่องหมายลงในแผนที่โดยส่งสถานะจากมุมมอง SwiftUI ไปยังมุมมองอื่น ในขั้นตอนนี้ คุณจะเคลื่อนไหวไปยังเมือง/เครื่องหมายหลังจากที่แตะในรายชื่อที่โต้ตอบได้ ในการแสดงภาพเคลื่อนไหว คุณจะต้องแสดงความรู้สึกต่อการเปลี่ยนแปลงสถานะโดยแก้ไขตําแหน่งกล้องในแผนที่เมื่อการเปลี่ยนแปลงเกิดขึ้น ดูข้อมูลเพิ่มเติมเกี่ยวกับแนวคิดของกล้องของกล้องได้ที่กล้องและมุมมอง
ทําให้แผนที่เคลื่อนไหวเป็นเมืองที่เลือก
วิธีทําให้แผนที่เคลื่อนไหวเป็นเมืองที่เลือก
- กําหนดการเชื่อมโยงใหม่ใน
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?
}
- อัปเดต
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
'
- ผ่าน
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 ทําให้มุมมองภาพเคลื่อนไหวเคลื่อนไหวได้ง่ายมาก เพราะจะจัดการภาพเคลื่อนไหวสําหรับการเปลี่ยนรัฐ ในการดูตัวอย่างนี้ คุณอาจเพิ่มภาพเคลื่อนไหวมากขึ้นโดยโฟกัสที่มุมมองเมืองที่เลือกหลังจากภาพเคลื่อนไหวบนแผนที่เสร็จสมบูรณ์ โดยทําตามขั้นตอนต่อไปนี้
- เพิ่มการปิด
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()
})
})
}
}
}
}
}
- นํา
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))
}
}
}
}
- ดําเนินการต่อและเปิดแอปเพื่อดูภาพเคลื่อนไหว
9. ส่งเหตุการณ์ไปยัง SwiftUI
ในขั้นตอนนี้ คุณจะฟังเหตุการณ์ที่ปล่อยจาก GMSMapView
และส่งเหตุการณ์นั้นไปยัง SwiftUI กล่าวโดยเจาะจงก็คือ คุณจะได้มอบสิทธิ์ในมุมมองแผนที่ และฟังเหตุการณ์ย้ายกล้อง เพื่อที่เมื่อเมืองถูกโฟกัสและกล้องแผนที่เคลื่อนไหวจากท่าทางสัมผัส มุมมองแผนที่จะไม่โฟกัสเพื่อให้คุณมองเห็นแผนที่ได้มากขึ้น
การใช้ผู้ประสานงาน SwiftUI
GMSMapView
จะปล่อยเหตุการณ์ เช่น การเปลี่ยนแปลงตําแหน่งของกล้อง หรือเมื่อมีการแตะเครื่องหมาย กลไกในการฟังเหตุการณ์เหล่านี้คือผ่านทางโปรโตคอล GMSMAPViewDelegate SwiftUI นําเสนอแนวคิดของ Coordinator ซึ่งใช้เป็นบทบาทผู้รับมอบสิทธิ์สําหรับตัวควบคุมมุมมอง UIKit โดยเฉพาะ ดังนั้น ในโลกของ SwiftUI ผู้ประสานงานควรรับผิดชอบการปฏิบัติตามโปรโตคอล GMSMapViewDelegate
โดยทําตามขั้นตอนต่อไปนี้
- สร้างผู้ประสานงานชื่อ
MapViewCoordinator
ภายในMapViewControllerBridge
สร้างชั้นเรียนที่ซ้อนกันภายในชั้นเรียน MapViewControllerBridge
และตั้งชื่อว่า MapViewCoordinator
คลาสนี้ต้องสอดคล้องกับ GMSMapViewDelegate
และควรประกาศ MapViewControllerBridge
เป็นพร็อพเพอร์ตี้
ตัวเชื่อม MapViewController
struct MapViewControllerBridge: UIViewControllerRepresentable {
// ...
final class MapViewCoordinator: NSObject, GMSMapViewDelegate {
var mapViewControllerBridge: MapViewControllerBridge
init(_ mapViewControllerBridge: MapViewControllerBridge) {
self.mapViewControllerBridge = mapViewControllerBridge
}
}
}
- นํา
makeCoordinator()
ไปใช้ในMapViewControllerBridge
ถัดไป ให้ใช้เมธอด makeCoordinator()
ภายใน MapViewControllerBridge
และแสดงผลอินสแตนซ์ของ MapViewCoodinator
ที่คุณสร้างในขั้นตอนก่อนหน้า
ตัวเชื่อม MapViewController
struct MapViewControllerBridge: UIViewControllerRepresentable {
// ...
func makeCoordinator() -> MapViewCoordinator {
return MapViewCoordinator(self)
}
}
- กําหนด
MapViewCoordinator
เป็นผู้รับมอบสิทธิ์มุมมองแผนที่
เมื่อสร้างผู้ประสานงานที่กําหนดเอง ขั้นตอนต่อไปคือการตั้งค่าผู้ประสานงานเป็นผู้รับมอบสิทธิ์สําหรับมุมมองแผนที่
"#39" หากต้องการอัปเดต ให้อัปเดตการเริ่มต้นมุมมองตัวควบคุมใน makeUIViewController(context)
ผู้ประสานงานที่สร้างจากขั้นตอนก่อนหน้าจะเข้าถึงได้จากออบเจ็กต์บริบท
ตัวเชื่อม MapViewController
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
ไปยังค่าปิดเพื่อให้มุมมอง SwiftUI แสดงได้เฉพาะเหตุการณ์การเคลื่อนไหวของกล้องที่เกี่ยวข้องกับท่าทางสัมผัสเท่านั้น
ตัวเชื่อม MapViewController
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
หากเหตุการณ์การย้ายเกี่ยวข้องกับท่าทางสัมผัส ซึ่งจะแสดงแผนที่ในรูปแบบเต็มอีกครั้งเมื่อเลื่อนแผนที่โดยใช้ท่าทางสัมผัส
ดูเนื้อหา
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 โดยใช้ UIViewControllerControllerable
- วิธีเปลี่ยนแปลงมุมมองแผนที่ด้วยสถานะและการเชื่อมโยง
- วิธีส่งเหตุการณ์จากมุมมองแผนที่ไปยัง SwiftUI โดยใช้ผู้ประสานงาน
มีอะไรอีกบ้าง
- Maps SDK สําหรับ iOS - เอกสารอย่างเป็นทางการสําหรับ Maps SDK สําหรับ iOS
- Places สําหรับ iOS - ค้นหาธุรกิจท้องถิ่นและจุดสนใจรอบตัวคุณ
- maps-sdk-for-ios-samples - โค้ดตัวอย่างใน GitHub ที่แสดงฟีเจอร์ทั้งหมดภายใน Maps SDK สําหรับ iOS
- SwiftUI - เอกสารอย่างเป็นทางการของ Apple' ใน SwiftUI
- ช่วยเราสร้างเนื้อหาที่คุณคิดว่าเป็นประโยชน์ที่สุดโดยตอบคําถามต่อไปนี้
คุณต้องการเห็น Codelab อื่นใดบ้าง
Codelab ที่คุณต้องการไม่อยู่ในรายการข้างต้นใช่ไหม ส่งคําขอเกี่ยวกับปัญหาใหม่ที่นี่